基本功能实现

This commit is contained in:
2026-01-26 21:53:34 +08:00
commit c6e5e655f9
28 changed files with 4803 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
package handlers
import (
"context"
"net/http"
"strconv"
"time"
"BingDailyImage/internal/config"
"BingDailyImage/internal/service/fetcher"
"BingDailyImage/internal/service/image"
"BingDailyImage/internal/service/token"
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
Password string `json:"password" binding:"required"`
}
// AdminLogin 管理员登录
// @Summary 管理员登录
// @Description 使用密码登录并获取临时 Token
// @Tags admin
// @Accept json
// @Produce json
// @Param request body LoginRequest true "登录请求"
// @Success 200 {object} model.Token
// @Failure 401 {object} map[string]string
// @Router /admin/login [post]
func AdminLogin(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
t, err := token.Login(req.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, t)
}
// ListTokens 获取 Token 列表
// @Summary 获取 Token 列表
// @Description 获取所有已创建的 API Token 列表
// @Tags admin
// @Security BearerAuth
// @Produce json
// @Success 200 {array} model.Token
// @Router /admin/tokens [get]
func ListTokens(c *gin.Context) {
tokens, err := token.ListTokens()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, tokens)
}
type CreateTokenRequest struct {
Name string `json:"name" binding:"required"`
ExpiresAt string `json:"expires_at"` // optional
ExpiresIn string `json:"expires_in"` // optional, e.g. 168h
}
// CreateToken 创建 Token
// @Summary 创建 Token
// @Description 创建一个新的 API Token
// @Tags admin
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body CreateTokenRequest true "创建请求"
// @Success 200 {object} model.Token
// @Router /admin/tokens [post]
func CreateToken(c *gin.Context) {
var req CreateTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
expiresAt := time.Now().Add(config.GetTokenTTL())
if req.ExpiresAt != "" {
t, err := time.Parse(time.RFC3339, req.ExpiresAt)
if err == nil {
expiresAt = t
}
} else if req.ExpiresIn != "" {
d, err := time.ParseDuration(req.ExpiresIn)
if err == nil {
expiresAt = time.Now().Add(d)
}
}
t, err := token.CreateToken(req.Name, expiresAt)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, t)
}
type UpdateTokenRequest struct {
Disabled bool `json:"disabled"`
}
// UpdateToken 更新 Token 状态
// @Summary 更新 Token 状态
// @Description 启用或禁用指定的 API Token
// @Tags admin
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param id path int true "Token ID"
// @Param request body UpdateTokenRequest true "更新请求"
// @Success 200 {object} map[string]string
// @Router /admin/tokens/{id} [patch]
func UpdateToken(c *gin.Context) {
idStr := c.Param("id")
id, _ := strconv.ParseUint(idStr, 10, 32)
var req UpdateTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
if err := token.UpdateToken(uint(id), req.Disabled); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
// DeleteToken 删除 Token
// @Summary 删除 Token
// @Description 永久删除指定的 API Token
// @Tags admin
// @Security BearerAuth
// @Param id path int true "Token ID"
// @Success 200 {object} map[string]string
// @Router /admin/tokens/{id} [delete]
func DeleteToken(c *gin.Context) {
idStr := c.Param("id")
id, _ := strconv.ParseUint(idStr, 10, 32)
if err := token.DeleteToken(uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
// GetConfig 获取当前配置
// @Summary 获取当前配置
// @Description 获取服务的当前运行配置 (脱敏)
// @Tags admin
// @Security BearerAuth
// @Produce json
// @Success 200 {object} config.Config
// @Router /admin/config [get]
func GetConfig(c *gin.Context) {
c.JSON(http.StatusOK, config.GetConfig())
}
// UpdateConfig 更新配置
// @Summary 更新配置
// @Description 在线更新服务配置并保存
// @Tags admin
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body config.Config true "配置对象"
// @Success 200 {object} config.Config
// @Router /admin/config [put]
func UpdateConfig(c *gin.Context) {
var cfg config.Config
if err := c.ShouldBindJSON(&cfg); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
if err := config.SaveConfig(&cfg); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if c.Query("reload") == "true" {
// 实际上 viper 会 watch config但这里可以触发一些重新初始化逻辑
// 这里暂不实现复杂的 reload
}
c.JSON(http.StatusOK, config.GetConfig())
}
type ManualFetchRequest struct {
N int `json:"n"`
}
// ManualFetch 手动触发抓取
// @Summary 手动触发抓取
// @Description 立即启动抓取 Bing 任务
// @Tags admin
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param request body ManualFetchRequest false "抓取天数"
// @Success 200 {object} map[string]string
// @Router /admin/fetch [post]
func ManualFetch(c *gin.Context) {
var req ManualFetchRequest
if err := c.ShouldBindJSON(&req); err != nil {
req.N = config.BingFetchN
}
if req.N <= 0 {
req.N = config.BingFetchN
}
f := fetcher.NewFetcher()
go func() {
f.Fetch(context.Background(), req.N)
}()
c.JSON(http.StatusOK, gin.H{"status": "task started"})
}
// ManualCleanup 手动触发清理
// @Summary 手动触发清理
// @Description 立即启动旧图片清理任务
// @Tags admin
// @Security BearerAuth
// @Produce json
// @Success 200 {object} map[string]string
// @Router /admin/cleanup [post]
func ManualCleanup(c *gin.Context) {
go func() {
image.CleanupOldImages(context.Background())
}()
c.JSON(http.StatusOK, gin.H{"status": "task started"})
}