支持动态更新管理员密码和下载次数逻辑完善,优化相关错误处理和配置更新流程

This commit is contained in:
2026-01-14 19:49:07 +08:00
parent 903d5b865e
commit e456d3a823
6 changed files with 75 additions and 19 deletions

View File

@@ -29,11 +29,12 @@ type ListBatchesResponse struct {
}
type UpdateBatchRequest struct {
Remark string `json:"remark"`
ExpireType string `json:"expire_type"`
ExpireAt *time.Time `json:"expire_at"`
MaxDownloads int `json:"max_downloads"`
Status string `json:"status"`
Remark *string `json:"remark"`
ExpireType *string `json:"expire_type"`
ExpireAt *time.Time `json:"expire_at"`
MaxDownloads *int `json:"max_downloads"`
DownloadCount *int `json:"download_count"`
Status *string `json:"status"`
}
// ListBatches 获取批次列表
@@ -136,15 +137,32 @@ func (h *BatchHandler) UpdateBatch(c *gin.Context) {
}
updates := make(map[string]interface{})
updates["remark"] = input.Remark
updates["expire_type"] = input.ExpireType
updates["expire_at"] = input.ExpireAt
updates["max_downloads"] = input.MaxDownloads
updates["status"] = input.Status
if input.Remark != nil {
updates["remark"] = *input.Remark
}
if input.ExpireType != nil {
updates["expire_type"] = *input.ExpireType
}
if input.ExpireAt != nil {
updates["expire_at"] = input.ExpireAt
}
if input.MaxDownloads != nil {
updates["max_downloads"] = *input.MaxDownloads
}
if input.DownloadCount != nil {
updates["download_count"] = *input.DownloadCount
}
if input.Status != nil {
updates["status"] = *input.Status
}
if err := bootstrap.DB.Model(&batch).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error()))
return
if len(updates) > 0 {
if err := bootstrap.DB.Model(&batch).Updates(updates).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error()))
return
}
// 重新从数据库读取,确保返回的是完整且最新的数据
bootstrap.DB.First(&batch, "id = ?", id)
}
c.JSON(http.StatusOK, model.SuccessResponse(batch))

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
type ConfigHandler struct{}
@@ -52,6 +53,16 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
newConfig.Database.Path = config.GlobalConfig.Database.Path
}
// 如果传入了明文密码,则重新生成 hash
if newConfig.Security.AdminPassword != "" {
hash, err := bcrypt.GenerateFromPassword([]byte(newConfig.Security.AdminPassword), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, "Failed to hash password: "+err.Error()))
return
}
newConfig.Security.AdminPasswordHash = string(hash)
}
// 检查取件码长度是否变化
pickupCodeLengthChanged := newConfig.Security.PickupCodeLength != config.GlobalConfig.Security.PickupCodeLength && newConfig.Security.PickupCodeLength > 0

View File

@@ -68,7 +68,9 @@ func (h *PickupHandler) DownloadBatch(c *gin.Context) {
}
// 增加下载次数
h.batchService.IncrementDownloadCount(batch.ID)
if err := h.batchService.IncrementDownloadCount(batch.ID); err != nil {
fmt.Printf("[DownloadBatch] Failed to increment download count for batch %s: %v\n", batch.ID, err)
}
}
type PickupHandler struct {
@@ -106,8 +108,11 @@ func (h *PickupHandler) Pickup(c *gin.Context) {
}
if batch.Type == "text" {
h.batchService.IncrementDownloadCount(batch.ID)
batch.DownloadCount++
if err := h.batchService.IncrementDownloadCount(batch.ID); err != nil {
fmt.Printf("[Pickup] Failed to increment download count for batch %s: %v\n", batch.ID, err)
} else {
batch.DownloadCount++
}
}
c.JSON(http.StatusOK, model.SuccessResponse(PickupResponse{
@@ -163,11 +168,19 @@ func (h *PickupHandler) DownloadFile(c *gin.Context) {
defer reader.Close()
// 增加下载次数
h.batchService.IncrementDownloadCount(batch.ID)
if err := h.batchService.IncrementDownloadCount(batch.ID); err != nil {
// 记录错误但不中断下载过程
fmt.Printf("[Download] Failed to increment download count for batch %s: %v\n", batch.ID, err)
}
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", item.OriginalName))
c.Header("Content-Type", item.MimeType)
c.Header("Content-Length", strconv.FormatInt(item.Size, 10))
// 如果是 HEAD 请求,只返回 Header
if c.Request.Method == http.MethodHead {
return
}
io.Copy(c.Writer, reader)
}

View File

@@ -24,6 +24,7 @@ type SiteConfig struct {
type SecurityConfig struct {
AdminPasswordHash string `yaml:"admin_password_hash" json:"admin_password_hash"` // 管理员密码哈希 (bcrypt)
AdminPassword string `yaml:"-" json:"admin_password,omitempty"` // 管理员密码明文 (仅用于更新请求,不保存到文件)
PickupCodeLength int `yaml:"pickup_code_length" json:"pickup_code_length"` // 取件码长度 (变更后将自动通过右侧补零或截取调整存量数据)
PickupFailLimit int `yaml:"pickup_fail_limit" json:"pickup_fail_limit"` // 取件失败尝试限制
JWTSecret string `yaml:"jwt_secret" json:"jwt_secret"` // JWT 签名密钥

View File

@@ -85,8 +85,18 @@ func (s *BatchService) DeleteBatch(ctx context.Context, batchID string) error {
}
func (s *BatchService) IncrementDownloadCount(batchID string) error {
return s.db.Model(&model.FileBatch{}).Where("id = ?", batchID).
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error
if batchID == "" {
return errors.New("batch id is empty")
}
result := s.db.Model(&model.FileBatch{}).Where("id = ?", batchID).
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1))
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("batch not found or already deleted")
}
return nil
}
func (s *BatchService) GeneratePickupCode(length int) (string, error) {