支持动态更新管理员密码和下载次数逻辑完善,优化相关错误处理和配置更新流程
This commit is contained in:
3
go.mod
3
go.mod
@@ -14,6 +14,7 @@ require (
|
|||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/google/uuid v1.6.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/studio-b12/gowebdav v0.11.0
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.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 v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // 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/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // 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/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ type ListBatchesResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateBatchRequest struct {
|
type UpdateBatchRequest struct {
|
||||||
Remark string `json:"remark"`
|
Remark *string `json:"remark"`
|
||||||
ExpireType string `json:"expire_type"`
|
ExpireType *string `json:"expire_type"`
|
||||||
ExpireAt *time.Time `json:"expire_at"`
|
ExpireAt *time.Time `json:"expire_at"`
|
||||||
MaxDownloads int `json:"max_downloads"`
|
MaxDownloads *int `json:"max_downloads"`
|
||||||
Status string `json:"status"`
|
DownloadCount *int `json:"download_count"`
|
||||||
|
Status *string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBatches 获取批次列表
|
// ListBatches 获取批次列表
|
||||||
@@ -136,16 +137,33 @@ func (h *BatchHandler) UpdateBatch(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updates := make(map[string]interface{})
|
updates := make(map[string]interface{})
|
||||||
updates["remark"] = input.Remark
|
if input.Remark != nil {
|
||||||
updates["expire_type"] = input.ExpireType
|
updates["remark"] = *input.Remark
|
||||||
|
}
|
||||||
|
if input.ExpireType != nil {
|
||||||
|
updates["expire_type"] = *input.ExpireType
|
||||||
|
}
|
||||||
|
if input.ExpireAt != nil {
|
||||||
updates["expire_at"] = input.ExpireAt
|
updates["expire_at"] = input.ExpireAt
|
||||||
updates["max_downloads"] = input.MaxDownloads
|
}
|
||||||
updates["status"] = input.Status
|
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 len(updates) > 0 {
|
||||||
if err := bootstrap.DB.Model(&batch).Updates(updates).Error; err != nil {
|
if err := bootstrap.DB.Model(&batch).Updates(updates).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error()))
|
c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 重新从数据库读取,确保返回的是完整且最新的数据
|
||||||
|
bootstrap.DB.First(&batch, "id = ?", id)
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.SuccessResponse(batch))
|
c.JSON(http.StatusOK, model.SuccessResponse(batch))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigHandler struct{}
|
type ConfigHandler struct{}
|
||||||
@@ -52,6 +53,16 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
|||||||
newConfig.Database.Path = config.GlobalConfig.Database.Path
|
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
|
pickupCodeLengthChanged := newConfig.Security.PickupCodeLength != config.GlobalConfig.Security.PickupCodeLength && newConfig.Security.PickupCodeLength > 0
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
type PickupHandler struct {
|
||||||
@@ -106,9 +108,12 @@ func (h *PickupHandler) Pickup(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if batch.Type == "text" {
|
if batch.Type == "text" {
|
||||||
h.batchService.IncrementDownloadCount(batch.ID)
|
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++
|
batch.DownloadCount++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.SuccessResponse(PickupResponse{
|
c.JSON(http.StatusOK, model.SuccessResponse(PickupResponse{
|
||||||
Remark: batch.Remark,
|
Remark: batch.Remark,
|
||||||
@@ -163,11 +168,19 @@ func (h *PickupHandler) DownloadFile(c *gin.Context) {
|
|||||||
defer reader.Close()
|
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-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", item.OriginalName))
|
||||||
c.Header("Content-Type", item.MimeType)
|
c.Header("Content-Type", item.MimeType)
|
||||||
c.Header("Content-Length", strconv.FormatInt(item.Size, 10))
|
c.Header("Content-Length", strconv.FormatInt(item.Size, 10))
|
||||||
|
|
||||||
|
// 如果是 HEAD 请求,只返回 Header
|
||||||
|
if c.Request.Method == http.MethodHead {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
io.Copy(c.Writer, reader)
|
io.Copy(c.Writer, reader)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type SiteConfig struct {
|
|||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
AdminPasswordHash string `yaml:"admin_password_hash" json:"admin_password_hash"` // 管理员密码哈希 (bcrypt)
|
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"` // 取件码长度 (变更后将自动通过右侧补零或截取调整存量数据)
|
PickupCodeLength int `yaml:"pickup_code_length" json:"pickup_code_length"` // 取件码长度 (变更后将自动通过右侧补零或截取调整存量数据)
|
||||||
PickupFailLimit int `yaml:"pickup_fail_limit" json:"pickup_fail_limit"` // 取件失败尝试限制
|
PickupFailLimit int `yaml:"pickup_fail_limit" json:"pickup_fail_limit"` // 取件失败尝试限制
|
||||||
JWTSecret string `yaml:"jwt_secret" json:"jwt_secret"` // JWT 签名密钥
|
JWTSecret string `yaml:"jwt_secret" json:"jwt_secret"` // JWT 签名密钥
|
||||||
|
|||||||
@@ -85,8 +85,18 @@ func (s *BatchService) DeleteBatch(ctx context.Context, batchID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *BatchService) IncrementDownloadCount(batchID string) error {
|
func (s *BatchService) IncrementDownloadCount(batchID string) error {
|
||||||
return s.db.Model(&model.FileBatch{}).Where("id = ?", batchID).
|
if batchID == "" {
|
||||||
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error
|
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) {
|
func (s *BatchService) GeneratePickupCode(length int) (string, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user