项目名调整,功能优化

This commit is contained in:
2026-01-26 22:20:27 +08:00
parent c6e5e655f9
commit 50275265ac
25 changed files with 413 additions and 66 deletions

4
.gitignore vendored
View File

@@ -50,6 +50,6 @@ desktop.ini
/data/
/picture/
/config.yaml
/bing_daily_image.db
/bing_paper.db
/req.txt
/BingDailyImage
/BingPaper

View File

@@ -5,15 +5,15 @@ COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o BingDailyImage .
RUN go build -o BingPaper .
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/BingDailyImage .
COPY --from=builder /app/BingPaper .
RUN mkdir -p data
COPY --from=builder /app/config.example.yaml ./data/config.yaml
COPY --from=builder /app/web ./web
EXPOSE 8080
ENTRYPOINT ["./BingDailyImage"]
ENTRYPOINT ["./BingPaper"]

View File

@@ -1,4 +1,4 @@
# BingDailyImage
# BingPaper
必应每日一图抓取、存储、多分辨率管理与公共 API 服务。
@@ -71,10 +71,10 @@ go run .
```bash
# 构建二进制
go build -o BingDailyImage .
go build -o BingPaper .
# 构建 Docker 镜像
docker build -t bing-daily-image .
docker build -t bing-paper .
```
## 许可证

47
config.example.yaml Normal file
View File

@@ -0,0 +1,47 @@
server:
port: 8080
base_url: ""
log:
level: info
api:
mode: local # local | redirect
cron:
enabled: true
daily_spec: "0 10 * * *"
retention:
days: 30
db:
type: sqlite # sqlite | mysql | postgres
dsn: data/bing_paper.db
storage:
type: local # local | s3 | webdav
local:
root: data/picture
s3:
endpoint: ""
region: ""
bucket: ""
access_key: ""
secret_key: ""
public_url_prefix: ""
force_path_style: false
webdav:
url: ""
username: ""
password: ""
public_url_prefix: ""
admin:
password_bcrypt: "$2a$10$fYHPeWHmwObephJvtlyH1O8DIgaLk5TINbi9BOezo2M8cSjmJchka" # 默认密码: admin123
token:
default_ttl: 168h
feature:
write_daily_files: true

View File

@@ -189,6 +189,66 @@ const docTemplate = `{
}
}
},
"/admin/password": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "验证旧密码并设置新密码,自动更新配置文件",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "修改管理员密码",
"parameters": [
{
"description": "修改密码请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.ChangePasswordRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/admin/tokens": {
"get": {
"security": [
@@ -747,6 +807,21 @@ const docTemplate = `{
}
}
},
"handlers.ChangePasswordRequest": {
"type": "object",
"required": [
"new_password",
"old_password"
],
"properties": {
"new_password": {
"type": "string"
},
"old_password": {
"type": "string"
}
}
},
"handlers.CreateTokenRequest": {
"type": "object",
"required": [
@@ -835,7 +910,7 @@ var SwaggerInfo = &swag.Spec{
Host: "localhost:8080",
BasePath: "/api/v1",
Schemes: []string{},
Title: "BingDailyImage API",
Title: "BingPaper API",
Description: "必应每日一图抓取、存储、管理与公共 API 服务。",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,

View File

@@ -2,7 +2,7 @@
"swagger": "2.0",
"info": {
"description": "必应每日一图抓取、存储、管理与公共 API 服务。",
"title": "BingDailyImage API",
"title": "BingPaper API",
"contact": {},
"version": "1.0"
},
@@ -183,6 +183,66 @@
}
}
},
"/admin/password": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "验证旧密码并设置新密码,自动更新配置文件",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "修改管理员密码",
"parameters": [
{
"description": "修改密码请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.ChangePasswordRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/admin/tokens": {
"get": {
"security": [
@@ -741,6 +801,21 @@
}
}
},
"handlers.ChangePasswordRequest": {
"type": "object",
"required": [
"new_password",
"old_password"
],
"properties": {
"new_password": {
"type": "string"
},
"old_password": {
"type": "string"
}
}
},
"handlers.CreateTokenRequest": {
"type": "object",
"required": [

View File

@@ -121,6 +121,16 @@ definitions:
username:
type: string
type: object
handlers.ChangePasswordRequest:
properties:
new_password:
type: string
old_password:
type: string
required:
- new_password
- old_password
type: object
handlers.CreateTokenRequest:
properties:
expires_at:
@@ -172,7 +182,7 @@ host: localhost:8080
info:
contact: {}
description: 必应每日一图抓取、存储、管理与公共 API 服务。
title: BingDailyImage API
title: BingPaper API
version: "1.0"
paths:
/admin/cleanup:
@@ -283,6 +293,44 @@ paths:
summary: 管理员登录
tags:
- admin
/admin/password:
post:
consumes:
- application/json
description: 验证旧密码并设置新密码,自动更新配置文件
parameters:
- description: 修改密码请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/handlers.ChangePasswordRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: 修改管理员密码
tags:
- admin
/admin/tokens:
get:
description: 获取所有已创建的 API Token 列表

6
go.mod
View File

@@ -1,4 +1,4 @@
module BingDailyImage
module BingPaper
go 1.25.5
@@ -13,6 +13,7 @@ require (
github.com/chai2010/webp v1.4.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
@@ -46,6 +47,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
@@ -57,6 +59,7 @@ require (
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/studio-b12/gowebdav v0.12.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files v1.0.1 // indirect
@@ -80,6 +83,7 @@ require (
golang.org/x/tools v0.40.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.6.0 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/driver/sqlite v1.6.0 // indirect

5
go.sum
View File

@@ -21,6 +21,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
@@ -101,6 +102,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
@@ -133,6 +135,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/studio-b12/gowebdav v0.12.0 h1:kFRtQECt8jmVAvA6RHBz3geXUGJHUZA6/IKpOVUs5kM=
github.com/studio-b12/gowebdav v0.12.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@@ -220,6 +224,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=

View File

@@ -6,16 +6,16 @@ import (
"log"
"os"
"BingDailyImage/internal/config"
"BingDailyImage/internal/cron"
"BingDailyImage/internal/http"
"BingDailyImage/internal/repo"
"BingDailyImage/internal/service/fetcher"
"BingDailyImage/internal/storage"
"BingDailyImage/internal/storage/local"
"BingDailyImage/internal/storage/s3"
"BingDailyImage/internal/storage/webdav"
"BingDailyImage/internal/util"
"BingPaper/internal/config"
"BingPaper/internal/cron"
"BingPaper/internal/http"
"BingPaper/internal/repo"
"BingPaper/internal/service/fetcher"
"BingPaper/internal/storage"
"BingPaper/internal/storage/local"
"BingPaper/internal/storage/s3"
"BingPaper/internal/storage/webdav"
"BingPaper/internal/util"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
@@ -93,7 +93,7 @@ func LogWelcomeInfo() {
}
fmt.Println("\n---------------------------------------------------------")
fmt.Println(" BingDailyImage 服务已启动!")
fmt.Println(" BingPaper 服务已启动!")
fmt.Printf(" - 首页地址: %s/\n", baseURL)
fmt.Printf(" - 管理后台: %s/admin\n", baseURL)
fmt.Printf(" - API 文档: %s/swagger/index.html\n", baseURL)

View File

@@ -120,7 +120,7 @@ func Init(configPath string) error {
v.SetDefault("cron.daily_spec", "0 10 * * *")
v.SetDefault("retention.days", 30)
v.SetDefault("db.type", "sqlite")
v.SetDefault("db.dsn", "data/bing_daily_image.db")
v.SetDefault("db.dsn", "data/bing_paper.db")
v.SetDefault("storage.type", "local")
v.SetDefault("storage.local.root", "data/picture")
v.SetDefault("token.default_ttl", "168h")

View File

@@ -3,10 +3,10 @@ package cron
import (
"context"
"BingDailyImage/internal/config"
"BingDailyImage/internal/service/fetcher"
"BingDailyImage/internal/service/image"
"BingDailyImage/internal/util"
"BingPaper/internal/config"
"BingPaper/internal/service/fetcher"
"BingPaper/internal/service/image"
"BingPaper/internal/util"
"github.com/robfig/cron/v3"
"go.uber.org/zap"

View File

@@ -6,12 +6,13 @@ import (
"strconv"
"time"
"BingDailyImage/internal/config"
"BingDailyImage/internal/service/fetcher"
"BingDailyImage/internal/service/image"
"BingDailyImage/internal/service/token"
"BingPaper/internal/config"
"BingPaper/internal/service/fetcher"
"BingPaper/internal/service/image"
"BingPaper/internal/service/token"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
type LoginRequest struct {
@@ -109,6 +110,55 @@ type UpdateTokenRequest struct {
Disabled bool `json:"disabled"`
}
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
// ChangePassword 修改管理员密码
// @Summary 修改管理员密码
// @Description 验证旧密码并设置新密码,自动更新配置文件
// @Tags admin
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body ChangePasswordRequest true "修改密码请求"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Router /admin/password [post]
func ChangePassword(c *gin.Context) {
var req ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
cfg := config.GetConfig()
// 验证旧密码
err := bcrypt.CompareHashAndPassword([]byte(cfg.Admin.PasswordBcrypt), []byte(req.OldPassword))
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid old password"})
return
}
// 生成新密码 Hash
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
return
}
// 更新配置
cfg.Admin.PasswordBcrypt = string(hash)
if err := config.SaveConfig(cfg); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save config"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok", "message": "password updated successfully"})
}
// UpdateToken 更新 Token 状态
// @Summary 更新 Token 状态
// @Description 启用或禁用指定的 API Token

View File

@@ -6,10 +6,10 @@ import (
"io"
"net/http"
"BingDailyImage/internal/config"
"BingDailyImage/internal/model"
"BingDailyImage/internal/service/image"
"BingDailyImage/internal/storage"
"BingPaper/internal/config"
"BingPaper/internal/model"
"BingPaper/internal/service/image"
"BingPaper/internal/storage"
"github.com/gin-gonic/gin"
)

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"strings"
"BingDailyImage/internal/service/token"
"BingPaper/internal/service/token"
"github.com/gin-gonic/gin"
)

View File

@@ -1,9 +1,9 @@
package http
import (
_ "BingDailyImage/docs"
"BingDailyImage/internal/http/handlers"
"BingDailyImage/internal/http/middleware"
_ "BingPaper/docs"
"BingPaper/internal/http/handlers"
"BingPaper/internal/http/middleware"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@@ -50,6 +50,8 @@ func SetupRouter() *gin.Engine {
authorized.PATCH("/tokens/:id", handlers.UpdateToken)
authorized.DELETE("/tokens/:id", handlers.DeleteToken)
authorized.POST("/password", handlers.ChangePassword)
authorized.GET("/config", handlers.GetConfig)
authorized.PUT("/config", handlers.UpdateConfig)

View File

@@ -1,9 +1,9 @@
package repo
import (
"BingDailyImage/internal/config"
"BingDailyImage/internal/model"
"BingDailyImage/internal/util"
"BingPaper/internal/config"
"BingPaper/internal/model"
"BingPaper/internal/util"
"fmt"
"go.uber.org/zap"

View File

@@ -13,11 +13,11 @@ import (
"path/filepath"
"time"
"BingDailyImage/internal/config"
"BingDailyImage/internal/model"
"BingDailyImage/internal/repo"
"BingDailyImage/internal/storage"
"BingDailyImage/internal/util"
"BingPaper/internal/config"
"BingPaper/internal/model"
"BingPaper/internal/repo"
"BingPaper/internal/storage"
"BingPaper/internal/util"
"github.com/chai2010/webp"
"github.com/disintegration/imaging"

View File

@@ -5,11 +5,11 @@ import (
"fmt"
"time"
"BingDailyImage/internal/config"
"BingDailyImage/internal/model"
"BingDailyImage/internal/repo"
"BingDailyImage/internal/storage"
"BingDailyImage/internal/util"
"BingPaper/internal/config"
"BingPaper/internal/model"
"BingPaper/internal/repo"
"BingPaper/internal/storage"
"BingPaper/internal/util"
"go.uber.org/zap"
)

View File

@@ -6,9 +6,9 @@ import (
"errors"
"time"
"BingDailyImage/internal/config"
"BingDailyImage/internal/model"
"BingDailyImage/internal/repo"
"BingPaper/internal/config"
"BingPaper/internal/model"
"BingPaper/internal/repo"
"golang.org/x/crypto/bcrypt"
)
@@ -51,7 +51,21 @@ func Login(password string) (*model.Token, error) {
}
ttl := config.GetTokenTTL()
return CreateToken("login-token", time.Now().Add(ttl))
expiresAt := time.Now().Add(ttl)
name := "login-token"
// 如果已存在同名 token则刷新时间并返回
var t model.Token
if err := repo.DB.Where("name = ?", name).First(&t).Error; err == nil {
t.ExpiresAt = expiresAt
t.Disabled = false
if err := repo.DB.Save(&t).Error; err != nil {
return nil, err
}
return &t, nil
}
return CreateToken(name, expiresAt)
}
func ListTokens() ([]model.Token, error) {

View File

@@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"BingDailyImage/internal/storage"
"BingPaper/internal/storage"
)
type LocalStorage struct {

View File

@@ -6,7 +6,7 @@ import (
"io"
"strings"
"BingDailyImage/internal/storage"
"BingPaper/internal/storage"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"

View File

@@ -7,7 +7,7 @@ import (
"path"
"strings"
"BingDailyImage/internal/storage"
"BingPaper/internal/storage"
"github.com/studio-b12/gowebdav"
)

View File

@@ -3,14 +3,14 @@ package main
import (
"fmt"
"BingDailyImage/internal/bootstrap"
"BingDailyImage/internal/config"
"BingDailyImage/internal/util"
"BingPaper/internal/bootstrap"
"BingPaper/internal/config"
"BingPaper/internal/util"
"go.uber.org/zap"
)
// @title BingDailyImage API
// @title BingPaper API
// @version 1.0
// @description 必应每日一图抓取、存储、管理与公共 API 服务。
// @host localhost:8080

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BingDailyImage - 必应每日一图</title>
<title>BingPaper - 必应每日一图</title>
<style>
:root {
--primary-color: #007bff;
@@ -67,7 +67,7 @@
<body>
<nav>
<a href="/" class="logo">BingDailyImage</a>
<a href="/" class="logo">BingPaper</a>
<div>
<a href="/">首页</a>
<a href="/admin">管理</a>
@@ -138,6 +138,13 @@
<button onclick="triggerFetch()" style="margin-bottom: 10px;">手动抓取 (最近8天)</button>
<button class="secondary" onclick="triggerCleanup()" style="margin-bottom: 10px;">手动清理旧图</button>
</div>
<div class="admin-card">
<h3>修改密码</h3>
<input type="password" id="old-password" placeholder="旧密码" style="margin-bottom: 10px;">
<input type="password" id="new-password" placeholder="新密码" style="margin-bottom: 10px;">
<button onclick="changePassword()">提交修改</button>
</div>
<div class="admin-card">
<h3>今日图预览</h3>
@@ -354,6 +361,26 @@
alert('清理任务已启动');
}
async function changePassword() {
const oldPassword = document.getElementById('old-password').value;
const newPassword = document.getElementById('new-password').value;
if (!oldPassword || !newPassword) return alert('请输入完整信息');
const resp = await fetch('/api/v1/admin/password', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
body: JSON.stringify({ old_password: oldPassword, new_password: newPassword })
});
const data = await resp.json();
if (resp.ok) {
alert('密码修改成功,请重新登录');
logout();
} else {
alert('修改失败: ' + (data.error || '未知错误'));
}
}
// 初始化
router();
</script>