From e456d3a82370fd5b52ec7e553ba43e1760df2ea3 Mon Sep 17 00:00:00 2001 From: hxuanyu <2252193204@qq.com> Date: Wed, 14 Jan 2026 19:49:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=AE=A1=E7=90=86=E5=91=98=E5=AF=86=E7=A0=81=E5=92=8C?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=AC=A1=E6=95=B0=E9=80=BB=E8=BE=91=E5=AE=8C?= =?UTF-8?q?=E5=96=84=EF=BC=8C=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C=E9=85=8D=E7=BD=AE=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 3 +++ internal/api/admin/batch.go | 44 ++++++++++++++++++++++--------- internal/api/admin/config.go | 11 ++++++++ internal/api/public/pickup.go | 21 ++++++++++++--- internal/config/config.go | 1 + internal/service/batch_service.go | 14 ++++++++-- 6 files changed, 75 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 30bb891..32ab3dd 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/glebarez/sqlite v1.11.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 + github.com/stretchr/testify v1.11.1 github.com/studio-b12/gowebdav v0.11.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 @@ -45,6 +46,7 @@ require ( github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gin-contrib/sse v1.1.0 // indirect @@ -69,6 +71,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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect diff --git a/internal/api/admin/batch.go b/internal/api/admin/batch.go index 4f78015..cb1d2d8 100644 --- a/internal/api/admin/batch.go +++ b/internal/api/admin/batch.go @@ -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)) diff --git a/internal/api/admin/config.go b/internal/api/admin/config.go index 088327a..3d33b2d 100644 --- a/internal/api/admin/config.go +++ b/internal/api/admin/config.go @@ -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 diff --git a/internal/api/public/pickup.go b/internal/api/public/pickup.go index 6ea7e29..7a935a2 100644 --- a/internal/api/public/pickup.go +++ b/internal/api/public/pickup.go @@ -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) } diff --git a/internal/config/config.go b/internal/config/config.go index 636946a..2522c58 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 签名密钥 diff --git a/internal/service/batch_service.go b/internal/service/batch_service.go index 256e360..8dd03af 100644 --- a/internal/service/batch_service.go +++ b/internal/service/batch_service.go @@ -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) {