diff --git a/cmd/server/main.go b/cmd/server/main.go index a03207a..1c2ef34 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -106,5 +106,7 @@ func main() { // 启动 log.Printf("MetaZone.FAN 启动于 0.0.0.0:%s", cfg.Server.Port) - r.Run("0.0.0.0:" + cfg.Server.Port) + if err := r.Run("0.0.0.0:" + cfg.Server.Port); err != nil { + log.Fatalf("服务启动失败: %v", err) + } } diff --git a/internal/cache/home_cache.go b/internal/cache/home_cache.go index 37b5f14..0bfeda1 100644 --- a/internal/cache/home_cache.go +++ b/internal/cache/home_cache.go @@ -1,3 +1,4 @@ +// Package cache 提供首页等热点数据的内存缓存层。 package cache import ( diff --git a/internal/common/context.go b/internal/common/context.go index 5120f53..202220b 100644 --- a/internal/common/context.go +++ b/internal/common/context.go @@ -1,3 +1,4 @@ +// Package common 提供通用工具函数、错误哨兵和响应辅助方法。 package common import "github.com/gin-gonic/gin" diff --git a/internal/common/cookie.go b/internal/common/cookie.go index 0bcd15b..f99bbe8 100644 --- a/internal/common/cookie.go +++ b/internal/common/cookie.go @@ -37,4 +37,3 @@ func ClearSessionCookie(c *gin.Context, cfg *config.Config, siteSettings *config secure := siteSettings.CookieSecure() c.SetCookie(SessionCookieName, "", -1, "/", "", secure, true) } - diff --git a/internal/common/errors.go b/internal/common/errors.go index 8ec9c53..3c72945 100644 --- a/internal/common/errors.go +++ b/internal/common/errors.go @@ -7,7 +7,7 @@ var ( ErrEmailExists = errors.New("该邮箱已注册") ErrUsernameTaken = errors.New("该用户名已被占用") ErrUsernameInvalid = errors.New("用户名格式无效(支持中英文、数字、下划线、连字符,1-16个字符)") - ErrBioTooLong = errors.New("个性签名不能超过 128 个字符") + ErrBioTooLong = errors.New("个性签名不能超过 128 个字符") ErrInvalidCred = errors.New("邮箱或密码错误") ErrUserNotFound = errors.New("用户不存在") ErrUserBanned = errors.New("该账号已被封禁") @@ -24,10 +24,10 @@ var ( ErrPermissionDenied = errors.New("权限不足") // 审核相关 - ErrAuditNotFound = errors.New("审核记录不存在") + ErrAuditNotFound = errors.New("审核记录不存在") ErrAuditNotPending = errors.New("该审核记录已处理") - ErrAuditDisabled = errors.New("审核功能未开启") - ErrAuditTypeOff = errors.New("该类型审核未开启") + ErrAuditDisabled = errors.New("审核功能未开启") + ErrAuditTypeOff = errors.New("该类型审核未开启") // 密码 & 注销相关 ErrIncorrectPassword = errors.New("当前密码错误") @@ -38,24 +38,24 @@ var ( ErrMaintenanceMode = errors.New("社区正在维护中,仅站长可登录") // 帖子相关 - ErrPostNotFound = errors.New("帖子不存在") - ErrPostCannotEdit = errors.New("当前状态不允许编辑") - ErrScheduledTooSoon = errors.New("定时发布时间至少需晚于当前 30 分钟") - ErrPostCannotSubmit = errors.New("仅草稿和已退回帖子可提交审核") - ErrPostCannotApprove = errors.New("仅待审核帖子可通过") - ErrPostCannotReject = errors.New("仅待审核和已发布帖子可退回") - ErrPostCannotLock = errors.New("待审核帖子不允许锁定") - ErrPostCannotUnlock = errors.New("仅锁定状态可解锁") + ErrPostNotFound = errors.New("帖子不存在") + ErrPostCannotEdit = errors.New("当前状态不允许编辑") + ErrScheduledTooSoon = errors.New("定时发布时间至少需晚于当前 30 分钟") + ErrPostCannotSubmit = errors.New("仅草稿和已退回帖子可提交审核") + ErrPostCannotApprove = errors.New("仅待审核帖子可通过") + ErrPostCannotReject = errors.New("仅待审核和已发布帖子可退回") + ErrPostCannotLock = errors.New("待审核帖子不允许锁定") + ErrPostCannotUnlock = errors.New("仅锁定状态可解锁") // 域能相关 - ErrInvalidEnergyAmount = errors.New("无效的赋能数量") - ErrCannotEnergizeSelf = errors.New("不能给自己的文章赋能") - ErrInsufficientEnergy = errors.New("域能不足,可通过每日签到或创作被赋能获取") - ErrEnergizeLimitReached = errors.New("对该文章赋能已达上限") - ErrCreatePostNoExp = errors.New("经验不足,Lv1 解锁投稿") - ErrInsufficientFund = errors.New("公户余额不足,无法执行操作") + ErrInvalidEnergyAmount = errors.New("无效的赋能数量") + ErrCannotEnergizeSelf = errors.New("不能给自己的文章赋能") + ErrInsufficientEnergy = errors.New("域能不足,可通过每日签到或创作被赋能获取") + ErrEnergizeLimitReached = errors.New("对该文章赋能已达上限") + ErrCreatePostNoExp = errors.New("经验不足,Lv1 解锁投稿") + ErrInsufficientFund = errors.New("公户余额不足,无法执行操作") // 评论相关 - ErrCommentNotFound = errors.New("评论不存在") - ErrCommentEmpty = errors.New("评论内容不能为空") + ErrCommentNotFound = errors.New("评论不存在") + ErrCommentEmpty = errors.New("评论内容不能为空") ) diff --git a/internal/common/file.go b/internal/common/file.go index d38e2df..03dd024 100644 --- a/internal/common/file.go +++ b/internal/common/file.go @@ -7,11 +7,11 @@ import ( // SaveUploadedFile 将 multipart.File 内容写入目标路径 func SaveUploadedFile(file multipart.File, dst string) error { - out, err := os.Create(dst) + out, err := os.Create(dst) //nolint:gosec // dst 为服务器内部路径 if err != nil { return err } - defer out.Close() + defer func() { _ = out.Close() }() buf := make([]byte, 32*1024) for { diff --git a/internal/common/helper.go b/internal/common/helper.go index 019afb7..6b33823 100644 --- a/internal/common/helper.go +++ b/internal/common/helper.go @@ -35,7 +35,7 @@ func ComputeAssetHashes(templateDir string) { // walkStaticDir 遍历目录,计算每个 .js/.css 的 CRC32,存入 assetHashes func walkStaticDir(dir, urlPrefix string) { - filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return nil } @@ -43,7 +43,7 @@ func walkStaticDir(dir, urlPrefix string) { if ext != ".js" && ext != ".css" { return nil } - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // path 来自 filepath.WalkDir 遍历,非用户输入 if err != nil { return nil } diff --git a/internal/common/pagination.go b/internal/common/pagination.go index 4377a70..c1e37a8 100644 --- a/internal/common/pagination.go +++ b/internal/common/pagination.go @@ -57,5 +57,5 @@ func PageCount(total int64, pageSize int) int { return int((total + int64(pageSize) - 1) / int64(pageSize)) } -// 固定时间格式,前后端统一 +// TimeFormat 固定时间格式,前后端统一。 const TimeFormat = time.RFC3339 diff --git a/internal/common/redis.go b/internal/common/redis.go index 607e85a..c1dbc8f 100644 --- a/internal/common/redis.go +++ b/internal/common/redis.go @@ -20,8 +20,8 @@ func NewRedisClient(cfg config.RedisConfig) (*redis.Client, error) { DialTimeout: 1 * time.Second, // 连接超时降低(默认 5s),配合 FallbackStore 快速降级 ReadTimeout: 1 * time.Second, WriteTimeout: 1 * time.Second, - MaxRetries: 1, // 最多重试 1 次(默认 3 次),减少故障阻塞时间 - PoolSize: 5, // 会话存储不需要大连接池(默认 10*GOMAXPROCS) + MaxRetries: 1, // 最多重试 1 次(默认 3 次),减少故障阻塞时间 + PoolSize: 5, // 会话存储不需要大连接池(默认 10*GOMAXPROCS) }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -29,7 +29,7 @@ func NewRedisClient(cfg config.RedisConfig) (*redis.Client, error) { if err := client.Ping(ctx).Err(); err != nil { log.Printf("[Redis] 连接失败: %v,将降级为内存存储", err) - return client, fmt.Errorf("Redis 连接失败: %w", err) + return client, fmt.Errorf("redis 连接失败: %w", err) } return client, nil diff --git a/internal/common/response.go b/internal/common/response.go index 186ff48..4f24fa9 100644 --- a/internal/common/response.go +++ b/internal/common/response.go @@ -1,8 +1,9 @@ package common import ( - "github.com/gin-gonic/gin" "net/http" + + "github.com/gin-gonic/gin" ) // 统一 JSON 响应 diff --git a/internal/common/username.go b/internal/common/username.go index 04310b5..71e5b3e 100644 --- a/internal/common/username.go +++ b/internal/common/username.go @@ -7,8 +7,10 @@ import ( ) // usernameChars 随机用户名字符集(小写字母 + 数字) -const usernameChars = "abcdefghijklmnopqrstuvwxyz0123456789" -const usernameLen = 10 +const ( + usernameChars = "abcdefghijklmnopqrstuvwxyz0123456789" + usernameLen = 10 +) // UsernameChecker 用户名查重接口(避免 common 反向依赖 repository) type UsernameChecker interface { diff --git a/internal/config/config.go b/internal/config/config.go index 3b3055b..04b62da 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,3 +1,4 @@ +// Package config 管理应用配置的加载、解析和运行时访问。 package config import ( @@ -22,10 +23,10 @@ type Config struct { // AuditConfig 审核系统配置(config.yaml 提供默认值,站点设置可运行时覆盖) type AuditConfig struct { - Enabled bool `mapstructure:"enabled"` - UsernameAudit bool `mapstructure:"username_audit"` - AvatarAudit bool `mapstructure:"avatar_audit"` - BioAudit bool `mapstructure:"bio_audit"` + Enabled bool `mapstructure:"enabled"` + UsernameAudit bool `mapstructure:"username_audit"` + AvatarAudit bool `mapstructure:"avatar_audit"` + BioAudit bool `mapstructure:"bio_audit"` } type ServerConfig struct { @@ -65,9 +66,9 @@ type BcryptConfig struct { // RolesConfig 角色配置(可扩展,新增角色只需改 config.yaml) type RolesConfig struct { - Levels map[string]int `mapstructure:"levels"` - Names map[string]string `mapstructure:"names"` - Permissions RolesPermissions `mapstructure:"permissions"` + Levels map[string]int `mapstructure:"levels"` + Names map[string]string `mapstructure:"names"` + Permissions RolesPermissions `mapstructure:"permissions"` } // RolesPermissions 角色间操作权限 @@ -75,7 +76,7 @@ type RolesPermissions struct { OperableRoles map[string][]string `mapstructure:"operable_roles"` } -// 全局配置实例(初始化后只读) +// App 全局配置实例(初始化后只读)。 var App *Config // Load 加载配置:config.yaml → .env 覆盖 @@ -111,11 +112,11 @@ func Load(configPath string) *Config { // loadEnvFile 读取 .env 文件并设置环境变量(仅当环境变量未设置时) func loadEnvFile(path string) { - f, err := os.Open(path) + f, err := os.Open(path) //nolint:gosec // path 为内部 .env 文件路径 if err != nil { return // .env 不存在,跳过 } - defer f.Close() + defer func() { _ = f.Close() }() scanner := bufio.NewScanner(f) for scanner.Scan() { @@ -130,7 +131,7 @@ func loadEnvFile(path string) { key := strings.TrimSpace(parts[0]) val := strings.TrimSpace(parts[1]) if os.Getenv(key) == "" { - os.Setenv(key, val) + _ = os.Setenv(key, val) } } } diff --git a/internal/config/site_settings.go b/internal/config/site_settings.go index a6bdbff..44ca1c3 100644 --- a/internal/config/site_settings.go +++ b/internal/config/site_settings.go @@ -112,19 +112,19 @@ func (s *SiteSettings) IsAuditTypeEnabled(auditType string, defaultVal bool) boo // SiteInfoDefaults 站点展示信息的默认值 type SiteInfoDefaults struct { - SiteName string // 站点名称,默认 "MetaLab" - SiteTagline string // 站点标语,默认 "下一代开发者社区" - ContactEmail string // 联系/申诉邮箱,默认 "metazone@foxmail.com" - Framework string // 附加标识,默认 "Framework: MetaGenesis" - CopyrightStart string // 版权起始年份,默认 "2024" - OperatorName string // 运营主体 - ICPNumber string // ICP 备案(HTML,含链接) - ICPLicense string // ICP 经营许可证(HTML) - PoliceNumber string // 公安备案(HTML) - WenWangWen string // 文网文(HTML) + SiteName string // 站点名称,默认 "MetaLab" + SiteTagline string // 站点标语,默认 "下一代开发者社区" + ContactEmail string // 联系/申诉邮箱,默认 "metazone@foxmail.com" + Framework string // 附加标识,默认 "Framework: MetaGenesis" + CopyrightStart string // 版权起始年份,默认 "2024" + OperatorName string // 运营主体 + ICPNumber string // ICP 备案(HTML,含链接) + ICPLicense string // ICP 经营许可证(HTML) + PoliceNumber string // 公安备案(HTML) + WenWangWen string // 文网文(HTML) AlgorithmFiling string // 算法备案(HTML) - PrivacyURL string // 隐私政策(HTML) - TermsURL string // 服务条款(HTML) + PrivacyURL string // 隐私政策(HTML) + TermsURL string // 服务条款(HTML) } // SiteInfo 获取站点展示信息(优先 DB 设置,fallback 默认值) diff --git a/internal/controller/admin/admin_controller.go b/internal/controller/admin/admin_controller.go index aff254d..89609c9 100644 --- a/internal/controller/admin/admin_controller.go +++ b/internal/controller/admin/admin_controller.go @@ -1,6 +1,8 @@ +// Package admin 提供管理后台的 HTTP 控制器:用户管理、审核、站点设置等。 package admin import ( + "errors" "net/http" "strconv" @@ -104,9 +106,10 @@ func (ac *AdminController) UpdateUserStatus(c *gin.Context) { } action := "封禁" - if req.Status == model.StatusActive { + switch req.Status { + case model.StatusActive: action = "解封" - } else if req.Status == model.StatusLocked { + case model.StatusLocked: action = "已锁定" } common.OkMessage(c, action+"成功") @@ -140,14 +143,13 @@ func parseUIDParam(c *gin.Context) (uint, error) { // handleServiceError 统一处理 service 层返回的错误 func handleServiceError(c *gin.Context, err error) { - switch err { - case common.ErrPermissionDenied: + if errors.Is(err, common.ErrPermissionDenied) { common.Error(c, http.StatusForbidden, "权限不足") - case common.ErrUserNotFound: + } else if errors.Is(err, common.ErrUserNotFound) { common.Error(c, http.StatusNotFound, "用户不存在") - case common.ErrEmailExists: + } else if errors.Is(err, common.ErrEmailExists) { common.Error(c, http.StatusConflict, "该邮箱已被其他用户注册,无法解锁") - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败") } } diff --git a/internal/controller/admin/admin_energy_api_controller.go b/internal/controller/admin/admin_energy_api_controller.go index d4e5d33..88d153c 100644 --- a/internal/controller/admin/admin_energy_api_controller.go +++ b/internal/controller/admin/admin_energy_api_controller.go @@ -1,6 +1,7 @@ package admin import ( + "errors" "net/http" "strconv" @@ -44,7 +45,7 @@ func (ac *AdminEnergyController) AdjustEnergy(c *gin.Context) { } if err := ac.energySvc.AdminAdjust(operatorUID.(uint), req.UserIDs, req.Amount, req.Description, req.Mode); err != nil { - if err == common.ErrInsufficientFund { + if errors.Is(err, common.ErrInsufficientFund) { common.Error(c, http.StatusBadRequest, "公户余额不足,无法执行操作") return } @@ -99,7 +100,7 @@ func (ac *AdminEnergyController) GetFundBalance(c *gin.Context) { return } common.Ok(c, gin.H{ - "balance": balance, + "balance": balance, "balance_display": float64(balance) / 10, }) } diff --git a/internal/controller/admin/audit_controller.go b/internal/controller/admin/audit_controller.go index de36c3f..ba41f74 100644 --- a/internal/controller/admin/audit_controller.go +++ b/internal/controller/admin/audit_controller.go @@ -1,6 +1,7 @@ package admin import ( + "errors" "net/http" "strconv" @@ -125,16 +126,15 @@ func parseIDParam(s string) (uint, error) { // handleAuditError 统一处理审核接口的 service 层错误 func handleAuditError(c *gin.Context, err error) { - switch err { - case common.ErrAuditNotFound: + if errors.Is(err, common.ErrAuditNotFound) { common.Error(c, http.StatusNotFound, "审核记录不存在") - case common.ErrAuditNotPending: + } else if errors.Is(err, common.ErrAuditNotPending) { common.Error(c, http.StatusBadRequest, "该审核记录已处理") - case common.ErrUserNotFound: + } else if errors.Is(err, common.ErrUserNotFound) { common.Error(c, http.StatusNotFound, "用户不存在") - case common.ErrUsernameTaken: + } else if errors.Is(err, common.ErrUsernameTaken) { common.Error(c, http.StatusConflict, "该用户名已被其他用户占用,审批失败") - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败") } } diff --git a/internal/controller/admin/site_setting_controller.go b/internal/controller/admin/site_setting_controller.go index ae886c0..4fb8c85 100644 --- a/internal/controller/admin/site_setting_controller.go +++ b/internal/controller/admin/site_setting_controller.go @@ -24,7 +24,7 @@ func NewSiteSettingController(settingService siteSettingUseCase, defaults *servi // SiteSettingsPage 站点设置管理页面 func (sc *SiteSettingController) SiteSettingsPage(c *gin.Context) { c.HTML(http.StatusOK, "admin/site-settings/index.html", common.BuildAdminPageData(c, gin.H{ - "Title": "站点设置", + "Title": "站点设置", "ExtraCSS": "/admin/static/css/site-settings.css", "ExtraJS": "/admin/static/js/site-settings.js", })) diff --git a/internal/controller/auth_api_login.go b/internal/controller/auth_api_login.go index 19ef937..a3b361b 100644 --- a/internal/controller/auth_api_login.go +++ b/internal/controller/auth_api_login.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "metazone.cc/mce/internal/common" @@ -48,21 +49,20 @@ func (ac *AuthController) Login(c *gin.Context) { user, err := ac.authService.Login(req, ip) if err != nil { // 失败计数已在 AllowAccount/AllowIP 中原子递增,无需额外记录 - switch err { - case common.ErrInvalidCred: + if errors.Is(err, common.ErrInvalidCred) { common.Error(c, http.StatusUnauthorized, "邮箱或密码错误") - case common.ErrUserLocked: + } else if errors.Is(err, common.ErrUserLocked) { common.Error(c, http.StatusUnauthorized, "邮箱或密码错误") - case common.ErrMaintenanceMode: + } else if errors.Is(err, common.ErrMaintenanceMode) { common.Error(c, http.StatusForbidden, "社区正在维护中,仅站长可登录") - case common.ErrNeedsConfirmRestore: + } else if errors.Is(err, common.ErrNeedsConfirmRestore) { // 注销账号登录 → 需要二次确认恢复 c.JSON(http.StatusOK, gin.H{ "success": true, "action": "confirm_restore", "message": "你的账号正在注销中,登录将撤销注销并恢复账号", }) - default: + } else { common.Error(c, http.StatusInternalServerError, "登录失败,请稍后重试") } return @@ -93,10 +93,9 @@ func (ac *AuthController) ConfirmRestore(c *gin.Context) { user, err := ac.authService.ConfirmRestore(req, clientIP(c)) if err != nil { - switch err { - case common.ErrInvalidCred: + if errors.Is(err, common.ErrInvalidCred) { common.Error(c, http.StatusUnauthorized, "邮箱或密码错误") - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败,请稍后重试") } return diff --git a/internal/controller/auth_api_register.go b/internal/controller/auth_api_register.go index 3db1b2b..529cef7 100644 --- a/internal/controller/auth_api_register.go +++ b/internal/controller/auth_api_register.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "metazone.cc/mce/internal/common" @@ -45,16 +46,15 @@ func (ac *AuthController) Register(c *gin.Context) { user, err := ac.authService.Register(req, clientIP(c)) if err != nil { // 失败计数已在 AllowIP 中原子递增,无需额外记录 - switch err { - case common.ErrMaintenanceMode: + if errors.Is(err, common.ErrMaintenanceMode) { common.Error(c, http.StatusForbidden, "社区正在维护中,暂不支持注册") - case common.ErrEmailExists: + } else if errors.Is(err, common.ErrEmailExists) { common.Error(c, http.StatusConflict, "该邮箱已注册") - case common.ErrWeakPassword: + } else if errors.Is(err, common.ErrWeakPassword) { common.Error(c, http.StatusBadRequest, err.Error()) - case common.ErrRegistrationDisabled: + } else if errors.Is(err, common.ErrRegistrationDisabled) { common.Error(c, http.StatusForbidden, "注册功能已关闭") - default: + } else { common.Error(c, http.StatusInternalServerError, "注册失败,请稍后重试") } return diff --git a/internal/controller/auth_controller.go b/internal/controller/auth_controller.go index 9e6ad90..484e277 100644 --- a/internal/controller/auth_controller.go +++ b/internal/controller/auth_controller.go @@ -37,7 +37,7 @@ func (ac *AuthController) RegisterPage(c *gin.Context) { guidelinesHTML := ac.siteSettings.Get("site.guidelines", "") var guidelines template.HTML if guidelinesHTML != "" { - guidelines = template.HTML(guidelinesHTML) + guidelines = template.HTML(guidelinesHTML) //nolint:gosec // 准则内容由管理员配置,属信任输入 } else { content, err := theme.LoadContent("templates/MetaLab-2026/guidelines.html") if err != nil { @@ -48,10 +48,12 @@ func (ac *AuthController) RegisterPage(c *gin.Context) { } // 替换准则中的硬编码站点名和邮箱 siteInfo := ac.siteSettings.SiteInfo() - guidelines = template.HTML(strings.ReplaceAll( - strings.ReplaceAll(string(guidelines), "MetaLab", siteInfo.SiteName), - "metazone@foxmail.com", siteInfo.ContactEmail, - )) + guidelines = template.HTML( //nolint:gosec // 准则内容由管理员配置,站点名/邮箱来自 DB + strings.ReplaceAll( + strings.ReplaceAll(string(guidelines), "MetaLab", siteInfo.SiteName), + "metazone@foxmail.com", siteInfo.ContactEmail, + ), + ) c.HTML(http.StatusOK, "auth/register.html", common.BuildPageData(c, gin.H{ "Title": "注册", "ExtraCSS": "/static/css/auth.css", diff --git a/internal/controller/comment_upload_controller.go b/internal/controller/comment_upload_controller.go index 18af7bb..692b12b 100644 --- a/internal/controller/comment_upload_controller.go +++ b/internal/controller/comment_upload_controller.go @@ -44,7 +44,7 @@ func (ctrl *CommentController) UploadImage(c *gin.Context) { common.Error(c, http.StatusBadRequest, "请选择文件") return } - defer file.Close() + defer func() { _ = file.Close() }() // 检查扩展名 ext := strings.ToLower(filepath.Ext(header.Filename)) @@ -67,7 +67,7 @@ func (ctrl *CommentController) UploadImage(c *gin.Context) { } storageDir := "storage/uploads/posts" - if err := os.MkdirAll(storageDir, 0755); err != nil { + if err := os.MkdirAll(storageDir, 0o755); err != nil { //nolint:gosec // 上传目录标准权限 common.Error(c, http.StatusInternalServerError, "存储初始化失败") return } @@ -88,7 +88,7 @@ func (ctrl *CommentController) UploadImage(c *gin.Context) { if webpData != nil && len(webpData) < len(data) { filename := strconv.FormatUint(uint64(uid), 10) + "_" + strconv.FormatInt(ts, 10) + ".webp" savePath = filepath.Join(storageDir, filename) - os.WriteFile(savePath, webpData, 0644) + _ = os.WriteFile(savePath, webpData, 0o644) //nolint:gosec // 上传文件标准权限 url = "/uploads/posts/" + filename } } @@ -101,16 +101,16 @@ func (ctrl *CommentController) UploadImage(c *gin.Context) { if decErr == nil { var buf bytes.Buffer if ext == ".png" { - png.Encode(&buf, decImg) + _ = png.Encode(&buf, decImg) } else { - jpeg.Encode(&buf, decImg, &jpeg.Options{Quality: 92}) + _ = jpeg.Encode(&buf, decImg, &jpeg.Options{Quality: 92}) } dataOut = buf.Bytes() } } filename := strconv.FormatUint(uint64(uid), 10) + "_" + strconv.FormatInt(ts, 10) + ext savePath = filepath.Join(storageDir, filename) - os.WriteFile(savePath, dataOut, 0644) + _ = os.WriteFile(savePath, dataOut, 0o644) //nolint:gosec // 上传文件标准权限 url = "/uploads/posts/" + filename } diff --git a/internal/controller/energy_controller.go b/internal/controller/energy_controller.go index f6c215a..385830d 100644 --- a/internal/controller/energy_controller.go +++ b/internal/controller/energy_controller.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "strconv" @@ -39,18 +40,17 @@ func (ec *EnergyController) Energize(c *gin.Context) { expGained, actualAmount, err := ec.energySvc.Energize(uid, req.PostID, req.Amount) if err != nil { - switch err { - case common.ErrPostNotFound: + if errors.Is(err, common.ErrPostNotFound) { common.Error(c, http.StatusNotFound, "文章不存在") - case common.ErrCannotEnergizeSelf: + } else if errors.Is(err, common.ErrCannotEnergizeSelf) { common.Error(c, http.StatusBadRequest, "不能给自己的文章赋能") - case common.ErrInsufficientEnergy: + } else if errors.Is(err, common.ErrInsufficientEnergy) { common.Error(c, http.StatusBadRequest, "域能不足,可通过每日签到或创作被赋能获取") - case common.ErrEnergizeLimitReached: + } else if errors.Is(err, common.ErrEnergizeLimitReached) { common.Error(c, http.StatusBadRequest, "对该文章赋能已达上限") - case common.ErrInvalidEnergyAmount: + } else if errors.Is(err, common.ErrInvalidEnergyAmount) { common.Error(c, http.StatusBadRequest, "无效的赋能数量") - default: + } else { common.Error(c, http.StatusInternalServerError, "赋能失败") } return @@ -136,13 +136,13 @@ func (ec *EnergyController) GetEnergyLogs(c *gin.Context) { // EnergyLogView 前端展示用的域能流水视图 type EnergyLogView struct { - ID uint `json:"id"` - Amount int `json:"amount"` + ID uint `json:"id"` + Amount int `json:"amount"` AmountDisplay float64 `json:"amount_display"` - Type string `json:"type"` - TypeName string `json:"type_name"` - Description string `json:"description"` - CreatedAt string `json:"created_at"` + Type string `json:"type"` + TypeName string `json:"type_name"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` } func toEnergyLogViews(logs []model.EnergyLog) []EnergyLogView { diff --git a/internal/controller/favorite_item_controller.go b/internal/controller/favorite_item_controller.go index 90bb149..a9fd1b3 100644 --- a/internal/controller/favorite_item_controller.go +++ b/internal/controller/favorite_item_controller.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "strconv" @@ -24,7 +25,7 @@ func (fc *FavoriteController) ListFolderItems(c *gin.Context) { result, err := fc.svc.ListFolderItems(uid, uint(folderID), page, pageSize) if err != nil { - if err == common.ErrPermissionDenied { + if errors.Is(err, common.ErrPermissionDenied) { common.Error(c, http.StatusForbidden, "该收藏夹未公开") return } diff --git a/internal/controller/follow_controller.go b/internal/controller/follow_controller.go index cc9e9c7..61e9e6f 100644 --- a/internal/controller/follow_controller.go +++ b/internal/controller/follow_controller.go @@ -148,7 +148,7 @@ func (fpc *FollowPageController) FollowersPage(c *gin.Context) { "ExtraCSS": "/static/css/follow.css", "Result": result, "TargetUID": targetUID, - "Page": result.Page, + "Page": result.Page, "TotalPages": result.TotalPages, "HasPrev": result.Page > 1, "HasNext": result.Page < result.TotalPages, diff --git a/internal/controller/interfaces.go b/internal/controller/interfaces.go index d1c7c91..81d6e1f 100644 --- a/internal/controller/interfaces.go +++ b/internal/controller/interfaces.go @@ -1,3 +1,4 @@ +// Package controller 定义 HTTP 请求处理层,通过 ISP 接口隔离依赖 Service。 package controller import ( @@ -27,13 +28,10 @@ type rateLimiter interface { ClearIP(ipKey string) } -// postUseCase PostController 对 PostService 的最小依赖(ISP:8 个方法) +// postUseCase PostController 对 PostService 的最小依赖(ISP:6 个方法) type postUseCase interface { - Create(userID uint, title, body string) (*model.Post, error) GetByID(id uint) (*model.Post, error) List(keyword string, page, pageSize int) ([]model.Post, int64, error) - Update(postID uint, title, body string) error - Delete(postID uint) error SubmitForAudit(postID uint) error RecordRead(userID, postID uint) RecordGuestRead(visitorID string, postID uint) @@ -100,14 +98,6 @@ type energyRenameHandler interface { DeductOnRename(userID uint) error } -// energyAdminUseCase AdminEnergyController 对 EnergyService 的依赖(ISP:4 个方法) -type energyAdminUseCase interface { - AdminAdjust(operatorUID uint, userIDs []uint, amount int, description string, mode string) error - GetAdminEnergyLogs(energyType string, page, pageSize int) ([]model.EnergyLog, int64, error) - GetFundBalance() (int, error) - GetFundLogs(logType string, page, pageSize int) ([]model.FundLog, int64, error) -} - // commentUseCase CommentController 对 CommentService 的最小依赖(ISP:6 个方法) type commentUseCase interface { CreateRoot(userID, postID uint, body string) (*model.Comment, error) diff --git a/internal/controller/post_upload_controller.go b/internal/controller/post_upload_controller.go index 58a8acd..977de2d 100644 --- a/internal/controller/post_upload_controller.go +++ b/internal/controller/post_upload_controller.go @@ -34,7 +34,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { common.Error(c, http.StatusBadRequest, "请选择文件") return } - defer file.Close() + defer func() { _ = file.Close() }() // 检查扩展名 ext := strings.ToLower(filepath.Ext(header.Filename)) @@ -59,7 +59,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { // 存储路径 storageDir := "storage/uploads/posts" - if err := os.MkdirAll(storageDir, 0755); err != nil { + if err := os.MkdirAll(storageDir, 0o755); err != nil { //nolint:gosec // 上传目录标准权限 common.Error(c, http.StatusInternalServerError, "存储初始化失败") return } @@ -83,7 +83,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { if webpData != nil && len(webpData) < len(data) { filename := fmt.Sprintf("%d_%d.webp", uid, ts) savePath = filepath.Join(storageDir, filename) - if err := os.WriteFile(savePath, webpData, 0644); err == nil { + if err := os.WriteFile(savePath, webpData, 0o644); err == nil { //nolint:gosec // 上传文件标准权限 url = "/uploads/posts/" + filename } } @@ -127,7 +127,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { filename := fmt.Sprintf("%d_%d%s", uid, ts, ext) savePath = filepath.Join(storageDir, filename) - if err := os.WriteFile(savePath, dataOut, 0644); err != nil { + if err := os.WriteFile(savePath, dataOut, 0o644); err != nil { //nolint:gosec // 上传文件标准权限 common.Error(c, http.StatusInternalServerError, "保存图片失败") return } diff --git a/internal/controller/settings_api_profile.go b/internal/controller/settings_api_profile.go index 161a657..12867de 100644 --- a/internal/controller/settings_api_profile.go +++ b/internal/controller/settings_api_profile.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "strconv" @@ -49,10 +50,9 @@ func (sc *SettingsController) UpdateProfile(c *gin.Context) { hasCompleted, _ := sc.levelSvc.HasCompletedTask(userID, "username") if hasCompleted { if err := sc.energySvc.DeductOnRename(userID); err != nil { - switch err { - case common.ErrInsufficientEnergy: + if errors.Is(err, common.ErrInsufficientEnergy) { common.Error(c, http.StatusBadRequest, "域能不足,无法改名。可通过每日签到或创作被赋能获取") - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败") } return @@ -81,10 +81,9 @@ func (sc *SettingsController) UpdateProfile(c *gin.Context) { hasCompleted, _ := sc.levelSvc.HasCompletedTask(userID, "username") if hasCompleted { if err := sc.energySvc.DeductOnRename(userID); err != nil { - switch err { - case common.ErrInsufficientEnergy: + if errors.Is(err, common.ErrInsufficientEnergy) { common.Error(c, http.StatusBadRequest, "域能不足,无法改名。可通过每日签到或创作被赋能获取") - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败") } return @@ -122,7 +121,7 @@ func (sc *SettingsController) UploadAvatar(c *gin.Context) { common.Error(c, http.StatusBadRequest, "请选择文件") return } - defer file.Close() + defer func() { _ = file.Close() }() // 裁切参数(可选,来自前端裁切弹窗) cropX, _ := strconv.Atoi(c.PostForm("crop_x")) diff --git a/internal/controller/settings_error_handlers.go b/internal/controller/settings_error_handlers.go index 8443b15..dbcbcc8 100644 --- a/internal/controller/settings_error_handlers.go +++ b/internal/controller/settings_error_handlers.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "metazone.cc/mce/internal/common" @@ -10,32 +11,30 @@ import ( // handleSettingsError 统一处理 settings 接口的 service 层错误 func handleSettingsError(c *gin.Context, err error) { - switch err { - case common.ErrUsernameTaken: + if errors.Is(err, common.ErrUsernameTaken) { common.Error(c, http.StatusConflict, err.Error()) - case common.ErrUsernameInvalid: + } else if errors.Is(err, common.ErrUsernameInvalid) { common.Error(c, http.StatusBadRequest, err.Error()) - case common.ErrBioTooLong: + } else if errors.Is(err, common.ErrBioTooLong) { common.Error(c, http.StatusBadRequest, err.Error()) - case common.ErrIncorrectPassword: + } else if errors.Is(err, common.ErrIncorrectPassword) { common.Error(c, http.StatusForbidden, err.Error()) - case common.ErrOwnerCannotDelete: + } else if errors.Is(err, common.ErrOwnerCannotDelete) { common.Error(c, http.StatusForbidden, err.Error()) - default: + } else { common.Error(c, http.StatusInternalServerError, "操作失败") } } // handleAuditSubmitError 统一处理审核提交的错误 func handleAuditSubmitError(c *gin.Context, err error) { - switch err { - case common.ErrAuditDisabled: + if errors.Is(err, common.ErrAuditDisabled) { common.Error(c, http.StatusForbidden, "审核功能未开启") - case common.ErrAuditTypeOff: + } else if errors.Is(err, common.ErrAuditTypeOff) { common.Error(c, http.StatusForbidden, "该类型审核未开启,请联系管理员") - case common.ErrUsernameTaken: + } else if errors.Is(err, common.ErrUsernameTaken) { common.Error(c, http.StatusConflict, err.Error()) - default: + } else { common.Error(c, http.StatusInternalServerError, "提交审核失败") } } diff --git a/internal/controller/space_controller.go b/internal/controller/space_controller.go index c424761..59aa688 100644 --- a/internal/controller/space_controller.go +++ b/internal/controller/space_controller.go @@ -11,9 +11,9 @@ import ( // SpaceController 用户空间控制器(统一页面:文章/关注/粉丝/收藏/设置) type SpaceController struct { - spaceService spaceUseCase - followSvc followUseCase - favoriteSvc favoriteUseCaseForSpace + spaceService spaceUseCase + followSvc followUseCase + favoriteSvc favoriteUseCaseForSpace } // NewSpaceController 构造函数 @@ -73,25 +73,29 @@ func (ctrl *SpaceController) ShowSpace(c *gin.Context) { // 分页参数 page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - if page < 1 { page = 1 } + if page < 1 { + page = 1 + } pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "18")) - if pageSize < 1 || pageSize > 50 { pageSize = 18 } + if pageSize < 1 || pageSize > 50 { + pageSize = 18 + } // 构建基础数据(含用户统计) postCount, _ := ctrl.spaceService.CountUserPosts(uint(uid)) totalLikes, totalFavorites, totalReads, _ := ctrl.spaceService.GetUserStats(uint(uid)) data := gin.H{ - "Title": spaceUser.Username + " 的空间", - "SpaceUser": spaceUser, - "IsOwnSpace": isOwnSpace, - "ActiveTab": tab, - "CurrentUID": currentUID, - "ExtraCSS": "/static/css/space.css", - "PostCount": postCount, - "TotalLikes": totalLikes, + "Title": spaceUser.Username + " 的空间", + "SpaceUser": spaceUser, + "IsOwnSpace": isOwnSpace, + "ActiveTab": tab, + "CurrentUID": currentUID, + "ExtraCSS": "/static/css/space.css", + "PostCount": postCount, + "TotalLikes": totalLikes, "TotalFavorites": totalFavorites, - "TotalReads": totalReads, + "TotalReads": totalReads, } // ---- 根据 Tab 加载对应数据 ---- diff --git a/internal/controller/studio_post_api_controller.go b/internal/controller/studio_post_api_controller.go index 89904f5..4c80da1 100644 --- a/internal/controller/studio_post_api_controller.go +++ b/internal/controller/studio_post_api_controller.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "net/http" "strconv" @@ -32,7 +33,7 @@ func (ctrl *StudioController) Create(c *gin.Context) { post, err := ctrl.postService.Create(uid, req.Title, req.Body, req.Visibility, req.PostType, req.ReprintSource, req.Declaration, req.ReprintProhibited, req.CategoryID, req.ScheduledAt) if err != nil { - if err == common.ErrScheduledTooSoon { + if errors.Is(err, common.ErrScheduledTooSoon) { common.Error(c, http.StatusBadRequest, err.Error()) return } diff --git a/internal/middleware/csrf.go b/internal/middleware/csrf.go index 2751bd0..42b7788 100644 --- a/internal/middleware/csrf.go +++ b/internal/middleware/csrf.go @@ -1,3 +1,4 @@ +// Package middleware 提供 HTTP 中间件:认证、授权、CSRF、安全头、限流等。 package middleware import ( diff --git a/internal/middleware/csrf_token.go b/internal/middleware/csrf_token.go index c7e5648..bcfa289 100644 --- a/internal/middleware/csrf_token.go +++ b/internal/middleware/csrf_token.go @@ -40,5 +40,3 @@ func generateCSRFToken() (string, error) { } return hex.EncodeToString(b), nil } - - diff --git a/internal/model/audit.go b/internal/model/audit.go index dc25cc3..b780ba6 100644 --- a/internal/model/audit.go +++ b/internal/model/audit.go @@ -1,3 +1,4 @@ +// Package model 定义数据模型、常量、角色体系和审核相关类型。 package model // 审核类型常量 diff --git a/internal/model/category.go b/internal/model/category.go index a596ba2..8d8176a 100644 --- a/internal/model/category.go +++ b/internal/model/category.go @@ -4,14 +4,14 @@ import "time" // Category 文章分类(两级树形结构) type Category struct { - ID uint `gorm:"primarykey" json:"id"` - Name string `gorm:"type:varchar(50);not null" json:"name"` - Slug string `gorm:"type:varchar(50);not null;uniqueIndex" json:"slug"` - Description string `gorm:"type:varchar(200);default:''" json:"description,omitempty"` - ParentID *uint `gorm:"index" json:"parent_id,omitempty"` - Sort int `gorm:"default:0" json:"sort"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primarykey" json:"id"` + Name string `gorm:"type:varchar(50);not null" json:"name"` + Slug string `gorm:"type:varchar(50);not null;uniqueIndex" json:"slug"` + Description string `gorm:"type:varchar(200);default:''" json:"description,omitempty"` + ParentID *uint `gorm:"index" json:"parent_id,omitempty"` + Sort int `gorm:"default:0" json:"sort"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` // 非 DB 字段(树形填充) Children []Category `gorm:"-" json:"children,omitempty"` diff --git a/internal/model/comment.go b/internal/model/comment.go index e81e9c3..1796c53 100644 --- a/internal/model/comment.go +++ b/internal/model/comment.go @@ -4,23 +4,23 @@ import "time" // Comment 评论模型 type Comment struct { - ID uint `gorm:"primarykey" json:"id"` - PostID uint `gorm:"index;not null" json:"post_id"` - RootID uint `gorm:"index;not null" json:"root_id"` // 根评论ID,顶级评论指向自身 - ParentID *uint `gorm:"index" json:"parent_id"` // 父评论ID,NULL=顶级评论 - ReplyToUID uint `gorm:"default:0" json:"reply_to_uid"` // 被回复者UID - Body string `gorm:"type:text;not null" json:"body"` // 评论内容(纯文本 + 图片URL) - IsDeleted bool `gorm:"default:false;index" json:"is_deleted"` - LikesCount int `gorm:"default:0" json:"likes_count"` - DislikesCount int `gorm:"default:0" json:"dislikes_count"` // 仅后台可见 - UserID uint `gorm:"index;not null" json:"user_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primarykey" json:"id"` + PostID uint `gorm:"index;not null" json:"post_id"` + RootID uint `gorm:"index;not null" json:"root_id"` // 根评论ID,顶级评论指向自身 + ParentID *uint `gorm:"index" json:"parent_id"` // 父评论ID,NULL=顶级评论 + ReplyToUID uint `gorm:"default:0" json:"reply_to_uid"` // 被回复者UID + Body string `gorm:"type:text;not null" json:"body"` // 评论内容(纯文本 + 图片URL) + IsDeleted bool `gorm:"default:false;index" json:"is_deleted"` + LikesCount int `gorm:"default:0" json:"likes_count"` + DislikesCount int `gorm:"default:0" json:"dislikes_count"` // 仅后台可见 + UserID uint `gorm:"index;not null" json:"user_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` // 非数据库字段(联表查询填充) - AuthorName string `gorm:"-:migration;<-:false;column:author_name" json:"author_name,omitempty"` + AuthorName string `gorm:"-:migration;<-:false;column:author_name" json:"author_name,omitempty"` AuthorAvatar string `gorm:"-:migration;<-:false;column:author_avatar" json:"author_avatar,omitempty"` - AuthorLevel int `gorm:"-:migration;<-:false;column:author_level" json:"author_level,omitempty"` + AuthorLevel int `gorm:"-:migration;<-:false;column:author_level" json:"author_level,omitempty"` // ReplyToName 被回复者当前显示名(JOIN users 获取,动态解析,改名后自动更新) ReplyToName string `gorm:"-:migration;<-:false" json:"reply_to_name,omitempty"` // RepliesCount 回复数(非DB字段,查询填充) diff --git a/internal/model/daily_like_summary.go b/internal/model/daily_like_summary.go index 66437a2..eec889c 100644 --- a/internal/model/daily_like_summary.go +++ b/internal/model/daily_like_summary.go @@ -4,11 +4,11 @@ import "time" // DailyLikeSummary 每日点赞汇聚(用于聚合点赞通知) type DailyLikeSummary struct { - ID uint `gorm:"primarykey" json:"id"` - AuthorUID uint `gorm:"uniqueIndex:idx_author_date;not null" json:"author_uid"` // 被点赞文章的作者 - Date string `gorm:"type:varchar(10);uniqueIndex:idx_author_date;not null" json:"date"` // 日期 YYYY-MM-DD(Asia/Shanghai) - LikerUIDs string `gorm:"type:text" json:"liker_uids"` // 点赞者 UID 列表(追加式,逗号分隔) - Notified bool `gorm:"default:false" json:"notified"` // 是否已发送聚合通知 - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primarykey" json:"id"` + AuthorUID uint `gorm:"uniqueIndex:idx_author_date;not null" json:"author_uid"` // 被点赞文章的作者 + Date string `gorm:"type:varchar(10);uniqueIndex:idx_author_date;not null" json:"date"` // 日期 YYYY-MM-DD(Asia/Shanghai) + LikerUIDs string `gorm:"type:text" json:"liker_uids"` // 点赞者 UID 列表(追加式,逗号分隔) + Notified bool `gorm:"default:false" json:"notified"` // 是否已发送聚合通知 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } diff --git a/internal/model/dto.go b/internal/model/dto.go index 8f629af..bf5fefb 100644 --- a/internal/model/dto.go +++ b/internal/model/dto.go @@ -74,4 +74,4 @@ type PostListQuery struct { Status string `form:"status"` Page int `form:"page"` PageSize int `form:"page_size"` -} \ No newline at end of file +} diff --git a/internal/model/energy_log.go b/internal/model/energy_log.go index 744c94a..9f8cb38 100644 --- a/internal/model/energy_log.go +++ b/internal/model/energy_log.go @@ -4,20 +4,20 @@ import "time" // 域能流水类型常量 const ( - EnergyTypeSignIn = "sign_in" - EnergyTypeRename = "rename" + EnergyTypeSignIn = "sign_in" + EnergyTypeRename = "rename" EnergyTypeRenameRefund = "rename_refund" - EnergyTypeEnergize = "energize" - EnergyTypeEnergized = "energized" - EnergyTypeDeletePost = "delete_post" - EnergyTypeAdminAdjust = "admin_adjust" + EnergyTypeEnergize = "energize" + EnergyTypeEnergized = "energized" + EnergyTypeDeletePost = "delete_post" + EnergyTypeAdminAdjust = "admin_adjust" ) // EnergyLog 域能流水记录 type EnergyLog struct { ID uint `gorm:"primarykey" json:"id"` UserID uint `gorm:"index;not null" json:"user_id"` - Amount int `gorm:"not null" json:"amount"` // 变化量(乘10,正为增加,负为扣减) + Amount int `gorm:"not null" json:"amount"` // 变化量(乘10,正为增加,负为扣减) Type string `gorm:"type:varchar(30);index;not null" json:"type"` // sign_in/rename/rename_refund/energize/energized/delete_post/admin_adjust RelatedType string `gorm:"type:varchar(30);default:''" json:"related_type,omitempty"` RelatedID uint `gorm:"default:0" json:"related_id,omitempty"` @@ -28,11 +28,11 @@ type EnergyLog struct { // EnergyLogDisplayNames 流水类型 → 中文名 var EnergyLogDisplayNames = map[string]string{ - EnergyTypeSignIn: "每日签到", - EnergyTypeRename: "改名消耗", + EnergyTypeSignIn: "每日签到", + EnergyTypeRename: "改名消耗", EnergyTypeRenameRefund: "改名退款", - EnergyTypeEnergize: "赋能消耗", - EnergyTypeEnergized: "被赋能", - EnergyTypeDeletePost: "删稿消耗", - EnergyTypeAdminAdjust: "后台调整", + EnergyTypeEnergize: "赋能消耗", + EnergyTypeEnergized: "被赋能", + EnergyTypeDeletePost: "删稿消耗", + EnergyTypeAdminAdjust: "后台调整", } diff --git a/internal/model/fund_log.go b/internal/model/fund_log.go index 91a81ea..0e82060 100644 --- a/internal/model/fund_log.go +++ b/internal/model/fund_log.go @@ -4,18 +4,18 @@ import "time" // 公户流水类型常量 const ( - FundTypeEnergizeTax = "energize_tax" // 赋能税收(入公户) - FundTypeRenameDeduction = "rename_deduction" // 改名扣费(入公户) - FundTypeRenameRefund = "rename_refund" // 改名退款(出公户) - FundTypeDeletePost = "delete_post_deduction" // 删稿扣费(入公户) - FundTypeAdminTransfer = "admin_transfer" // 管理员转账(出公户,受余额约束) - FundTypeSystemOperation = "system_operation" // 站长直调(双向,不受余额约束) + FundTypeEnergizeTax = "energize_tax" // 赋能税收(入公户) + FundTypeRenameDeduction = "rename_deduction" // 改名扣费(入公户) + FundTypeRenameRefund = "rename_refund" // 改名退款(出公户) + FundTypeDeletePost = "delete_post_deduction" // 删稿扣费(入公户) + FundTypeAdminTransfer = "admin_transfer" // 管理员转账(出公户,受余额约束) + FundTypeSystemOperation = "system_operation" // 站长直调(双向,不受余额约束) ) // FundLog 公户流水记录 type FundLog struct { ID uint `gorm:"primarykey" json:"id"` - Amount int `gorm:"not null" json:"amount"` // +入公户 / –出公户(×10) + Amount int `gorm:"not null" json:"amount"` // +入公户 / –出公户(×10) Type string `gorm:"type:varchar(50);index;not null" json:"type"` // 日志类型 RelatedType string `gorm:"type:varchar(50);default:''" json:"related_type,omitempty"` RelatedID uint `gorm:"default:0" json:"related_id,omitempty"` diff --git a/internal/model/notification.go b/internal/model/notification.go index aabbc1a..db68eec 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -2,18 +2,18 @@ package model // 消息/通知类型常量 const ( - NotifyAuditApproved = "audit_approved" - NotifyAuditRejected = "audit_rejected" - NotifyPostApproved = "post_approved" - NotifyPostRejected = "post_rejected" - NotifyPostLocked = "post_locked" - NotifyPostUnlocked = "post_unlocked" - NotifyLevelUp = "level_up" - NotifyComment = "comment" - NotifyCommentReply = "comment_reply" - NotifyLikeAggregated = "like_aggregated" // 每日聚合点赞通知 - NotifyFollow = "follow" // 关注通知 - NotifyCommentMention = "comment_mention" // 评论@提及通知 + NotifyAuditApproved = "audit_approved" + NotifyAuditRejected = "audit_rejected" + NotifyPostApproved = "post_approved" + NotifyPostRejected = "post_rejected" + NotifyPostLocked = "post_locked" + NotifyPostUnlocked = "post_unlocked" + NotifyLevelUp = "level_up" + NotifyComment = "comment" + NotifyCommentReply = "comment_reply" + NotifyLikeAggregated = "like_aggregated" // 每日聚合点赞通知 + NotifyFollow = "follow" // 关注通知 + NotifyCommentMention = "comment_mention" // 评论@提及通知 ) // Notification 消息/通知模型 diff --git a/internal/model/post.go b/internal/model/post.go index 1c4a7d7..a9a563e 100644 --- a/internal/model/post.go +++ b/internal/model/post.go @@ -8,28 +8,28 @@ import ( // Post 帖子/文章模型 type Post struct { - ID uint `gorm:"primarykey" json:"id"` - Title string `gorm:"type:varchar(200);not null" json:"title"` - Body string `gorm:"type:text" json:"body"` // Markdown content - Excerpt string `gorm:"type:varchar(500)" json:"excerpt"` // plain text summary for list - UserID uint `gorm:"index;index:idx_posts_user_status,priority:1;not null" json:"user_id"` - Status string `gorm:"type:varchar(20);index;index:idx_posts_user_status,priority:2;default:draft;not null" json:"status"` - IsLocked bool `gorm:"default:false" json:"is_locked"` - LockReason string `gorm:"type:varchar(500);default:''" json:"lock_reason,omitempty"` - PendingTitle string `gorm:"type:varchar(200);default:''" json:"pending_title,omitempty"` - PendingBody string `gorm:"type:text" json:"pending_body,omitempty"` - RejectReason string `gorm:"type:varchar(500);default:''" json:"reject_reason,omitempty"` + ID uint `gorm:"primarykey" json:"id"` + Title string `gorm:"type:varchar(200);not null" json:"title"` + Body string `gorm:"type:text" json:"body"` // Markdown content + Excerpt string `gorm:"type:varchar(500)" json:"excerpt"` // plain text summary for list + UserID uint `gorm:"index;index:idx_posts_user_status,priority:1;not null" json:"user_id"` + Status string `gorm:"type:varchar(20);index;index:idx_posts_user_status,priority:2;default:draft;not null" json:"status"` + IsLocked bool `gorm:"default:false" json:"is_locked"` + LockReason string `gorm:"type:varchar(500);default:''" json:"lock_reason,omitempty"` + PendingTitle string `gorm:"type:varchar(200);default:''" json:"pending_title,omitempty"` + PendingBody string `gorm:"type:text" json:"pending_body,omitempty"` + RejectReason string `gorm:"type:varchar(500);default:''" json:"reject_reason,omitempty"` // 文章属性 - CategoryID *uint `gorm:"index" json:"category_id,omitempty"` // 二级分类 ID - Visibility string `gorm:"type:varchar(10);default:public" json:"visibility"` // public / private - PostType string `gorm:"type:varchar(10);default:original" json:"post_type"` // original / reprint - ReprintSource string `gorm:"type:varchar(500);default:''" json:"reprint_source,omitempty"` // 转载来源 - Declaration string `gorm:"type:varchar(30);default:''" json:"declaration,omitempty"` // 创作声明 - ReprintProhibited bool `gorm:"default:false" json:"reprint_prohibited"` // 禁止转载 - PinType string `gorm:"type:varchar(10);default:''" json:"pin_type,omitempty"` // category / global - PinnedAt *time.Time `json:"pinned_at,omitempty"` // 置顶时间 - ScheduledAt *time.Time `gorm:"index" json:"scheduled_at,omitempty"` // 定时发布时间 + CategoryID *uint `gorm:"index" json:"category_id,omitempty"` // 二级分类 ID + Visibility string `gorm:"type:varchar(10);default:public" json:"visibility"` // public / private + PostType string `gorm:"type:varchar(10);default:original" json:"post_type"` // original / reprint + ReprintSource string `gorm:"type:varchar(500);default:''" json:"reprint_source,omitempty"` // 转载来源 + Declaration string `gorm:"type:varchar(30);default:''" json:"declaration,omitempty"` // 创作声明 + ReprintProhibited bool `gorm:"default:false" json:"reprint_prohibited"` // 禁止转载 + PinType string `gorm:"type:varchar(10);default:''" json:"pin_type,omitempty"` // category / global + PinnedAt *time.Time `json:"pinned_at,omitempty"` // 置顶时间 + ScheduledAt *time.Time `gorm:"index" json:"scheduled_at,omitempty"` // 定时发布时间 AllowComment bool `gorm:"default:true" json:"allow_comment"` CommentsCount int `gorm:"default:0" json:"comments_count"` // 评论数(冗余计数器) @@ -37,7 +37,7 @@ type Post struct { LikesCount int `gorm:"default:0" json:"likes_count"` // 点赞数(冗余计数器) DislikesCount int `gorm:"default:0" json:"dislikes_count"` // 踩数(冗余计数器,仅后台可见) FavoritesCount int `gorm:"default:0" json:"favorites_count"` // 收藏数(冗余计数器) - ViewsCount int `gorm:"default:0" json:"views_count"` // 阅读数(冗余计数器,已登录去重) + ViewsCount int `gorm:"default:0" json:"views_count"` // 阅读数(冗余计数器,已登录去重) DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` diff --git a/internal/model/post_read_log.go b/internal/model/post_read_log.go index 5db807f..b57cb6d 100644 --- a/internal/model/post_read_log.go +++ b/internal/model/post_read_log.go @@ -17,4 +17,3 @@ type PostGuestReadLog struct { PostID uint `gorm:"uniqueIndex:idx_visitor_post_read;not null" json:"post_id"` ReadAt time.Time `gorm:"autoCreateTime" json:"read_at"` } - diff --git a/internal/model/shortcode.go b/internal/model/shortcode.go index ec73707..b791ae1 100644 --- a/internal/model/shortcode.go +++ b/internal/model/shortcode.go @@ -82,11 +82,11 @@ type ShortcodeResult struct { // ShortcodeCard 渲染一张卡片的通用数据 // 前端根据 Type 决定具体 UI 组件,字段按需填充 type ShortcodeCard struct { - Type ShortcodeType `json:"type"` - ID string `json:"id"` // 业务 ID(活动ID / 游戏slug) - Title string `json:"title,omitempty"` - Cover string `json:"cover,omitempty"` - Desc string `json:"desc,omitempty"` - URL string `json:"url,omitempty"` - Extra string `json:"extra,omitempty"` // 扩展字段(时间、标签等,JSON string) + Type ShortcodeType `json:"type"` + ID string `json:"id"` // 业务 ID(活动ID / 游戏slug) + Title string `json:"title,omitempty"` + Cover string `json:"cover,omitempty"` + Desc string `json:"desc,omitempty"` + URL string `json:"url,omitempty"` + Extra string `json:"extra,omitempty"` // 扩展字段(时间、标签等,JSON string) } diff --git a/internal/model/user.go b/internal/model/user.go index e934bd3..f05f065 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -20,9 +20,9 @@ type User struct { Energy int `gorm:"default:0" json:"energy"` // 域能余额(可为负数,乘10存储) // 关注系统 - FollowersCount int `gorm:"default:0" json:"followers_count"` // 粉丝数(冗余计数器) - FollowingCount int `gorm:"default:0" json:"following_count"` // 关注数(冗余计数器) - FollowListPublic bool `gorm:"default:true" json:"follow_list_public"` // 关注/粉丝列表是否公开 + FollowersCount int `gorm:"default:0" json:"followers_count"` // 粉丝数(冗余计数器) + FollowingCount int `gorm:"default:0" json:"following_count"` // 关注数(冗余计数器) + FollowListPublic bool `gorm:"default:true" json:"follow_list_public"` // 关注/粉丝列表是否公开 FollowerListPublic bool `gorm:"default:true" json:"follower_list_public"` // 粉丝列表是否公开 // 通知偏好 (JSON string) @@ -52,9 +52,11 @@ const ( ) // --- 以下由 InitRoles 从配置初始化,无硬编码默认值 --- -var roleLevel map[string]int -var operableRoles map[string][]string -var RoleDisplayNames map[string]string +var ( + roleLevel map[string]int + operableRoles map[string][]string + RoleDisplayNames map[string]string +) // InitRoles 从配置初始化角色系统(唯一数据源) // 必须在服务启动前调用,不提供默认值 diff --git a/internal/model/user_follow.go b/internal/model/user_follow.go index 87ef291..3a93067 100644 --- a/internal/model/user_follow.go +++ b/internal/model/user_follow.go @@ -4,9 +4,9 @@ import "time" // UserFollow 关注关系 type UserFollow struct { - FollowerID uint `gorm:"primaryKey;not null" json:"follower_id"` - FolloweeID uint `gorm:"primaryKey;not null" json:"followee_id"` - CreatedAt time.Time `json:"created_at"` + FollowerID uint `gorm:"primaryKey;not null" json:"follower_id"` + FolloweeID uint `gorm:"primaryKey;not null" json:"followee_id"` + CreatedAt time.Time `json:"created_at"` // 联表查询填充(非数据库字段) FollowerUsername string `gorm:"-:migration;<-:false;column:follower_username" json:"follower_username,omitempty"` diff --git a/internal/repository/announcement_repo.go b/internal/repository/announcement_repo.go index 674cc60..110bf38 100644 --- a/internal/repository/announcement_repo.go +++ b/internal/repository/announcement_repo.go @@ -1,3 +1,4 @@ +// Package repository 提供基于 GORM 的数据访问层实现。 package repository import ( diff --git a/internal/repository/comment_mention_repo.go b/internal/repository/comment_mention_repo.go index 8d91ca2..a1505c4 100644 --- a/internal/repository/comment_mention_repo.go +++ b/internal/repository/comment_mention_repo.go @@ -73,7 +73,8 @@ func (r *CommentRepo) SearchUsersByLevel(keyword string, minLevel, limit int, fo Username string Avatar string Exp int -}, error) { +}, error, +) { type result struct { UID uint Username string diff --git a/internal/repository/comment_repo.go b/internal/repository/comment_repo.go index 79c7d19..42679c4 100644 --- a/internal/repository/comment_repo.go +++ b/internal/repository/comment_repo.go @@ -161,7 +161,7 @@ func (r *CommentRepo) DecrCommentsCount(postID uint) error { UpdateColumn("comments_count", gorm.Expr("GREATEST(comments_count - 1, 0)")).Error } -// CountRepliesByRootByStatus 统计某条顶级评论的回复数(可选是否统计已删除) +// CountRepliesByRoot 统计某条顶级评论的回复数(可选是否统计已删除) func (r *CommentRepo) CountRepliesByRoot(rootID uint, includeDeleted bool) (int, error) { var count int64 q := r.db.Model(&model.Comment{}).Where("root_id = ? AND parent_id IS NOT NULL", rootID) diff --git a/internal/repository/reaction_repo.go b/internal/repository/reaction_repo.go index d1765e2..5a594a9 100644 --- a/internal/repository/reaction_repo.go +++ b/internal/repository/reaction_repo.go @@ -1,6 +1,8 @@ package repository import ( + "errors" + "metazone.cc/mce/internal/model" "metazone.cc/mce/internal/service" @@ -103,7 +105,7 @@ func (r *ReactionRepo) GetPostAuthorID(postID uint) (uint, error) { func (r *ReactionRepo) UpsertDailyLikeSummary(authorUID uint, date string, likerUID string) error { var existing model.DailyLikeSummary err := r.db.Where("author_uid = ? AND date = ?", authorUID, date).First(&existing).Error - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return r.db.Create(&model.DailyLikeSummary{ AuthorUID: authorUID, Date: date, diff --git a/internal/repository/tag_repo.go b/internal/repository/tag_repo.go index 829ccef..3b7a948 100644 --- a/internal/repository/tag_repo.go +++ b/internal/repository/tag_repo.go @@ -1,6 +1,8 @@ package repository import ( + "errors" + "metazone.cc/mce/internal/model" "metazone.cc/mce/internal/service" @@ -29,7 +31,7 @@ func (r *TagRepo) FindOrCreate(name, slug string) (*model.Tag, error) { if err == nil { return &tag, nil } - if err != gorm.ErrRecordNotFound { + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } tag = model.Tag{Name: name, Slug: slug} diff --git a/internal/repository/user_repo.go b/internal/repository/user_repo.go index 7f1f630..5e4953f 100644 --- a/internal/repository/user_repo.go +++ b/internal/repository/user_repo.go @@ -151,4 +151,3 @@ func (r *UserRepo) CountSearchUsers(keyword, role, status string) (int64, error) err := r.buildSearchQuery(keyword, role, status).Count(&count).Error return count, err } - diff --git a/internal/router/deps_core.go b/internal/router/deps_core.go index a573c0d..ebb7db5 100644 --- a/internal/router/deps_core.go +++ b/internal/router/deps_core.go @@ -18,33 +18,33 @@ import ( ) type dependencies struct { - authCtrl *controller.AuthController - authMdw *middleware.AuthMiddleware - rateLimiter *middleware.RateLimiter - maintenanceMdw *middleware.MaintenanceMiddleware - settingsCtrl *controller.SettingsController - messageCtrl *controller.MessageController - postCtrl *controller.PostController - postService *service.PostService - spaceCtrl *controller.SpaceController - studioCtrl *controller.StudioController - adminPostCtrl *adminCtrl.AdminPostController - adminCtrl *adminCtrl.AdminController - auditCtrl *adminCtrl.AuditController - siteSettingCtrl *adminCtrl.SiteSettingController - levelCtrl *controller.LevelController - energyCtrl *controller.EnergyController - adminEnergyCtrl *adminCtrl.AdminEnergyController - energyService *service.EnergyService - commentCtrl *controller.CommentController - commentService *service.CommentService - adminCommentCtrl *adminCtrl.AdminCommentController - reactionCtrl *controller.ReactionController - followCtrl *controller.FollowController - favoriteCtrl *controller.FavoriteController - favoriteService *service.FavoriteService - trendsService *service.TrendsService - homeCache *cache.HomePageCache + authCtrl *controller.AuthController + authMdw *middleware.AuthMiddleware + rateLimiter *middleware.RateLimiter + maintenanceMdw *middleware.MaintenanceMiddleware + settingsCtrl *controller.SettingsController + messageCtrl *controller.MessageController + postCtrl *controller.PostController + postService *service.PostService + spaceCtrl *controller.SpaceController + studioCtrl *controller.StudioController + adminPostCtrl *adminCtrl.AdminPostController + adminCtrl *adminCtrl.AdminController + auditCtrl *adminCtrl.AuditController + siteSettingCtrl *adminCtrl.SiteSettingController + levelCtrl *controller.LevelController + energyCtrl *controller.EnergyController + adminEnergyCtrl *adminCtrl.AdminEnergyController + energyService *service.EnergyService + commentCtrl *controller.CommentController + commentService *service.CommentService + adminCommentCtrl *adminCtrl.AdminCommentController + reactionCtrl *controller.ReactionController + followCtrl *controller.FollowController + favoriteCtrl *controller.FavoriteController + favoriteService *service.FavoriteService + trendsService *service.TrendsService + homeCache *cache.HomePageCache announcementService *service.AnnouncementService announcementCtrl *adminCtrl.AnnouncementController categoryCtrl *adminCtrl.CategoryController diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index 3a184b4..0d64510 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -131,25 +131,25 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting studioCtrl: studioCtrl, adminPostCtrl: adminPostCtrl, adminCtrl: adminController, auditCtrl: auditController, - siteSettingCtrl: siteSettingController, - levelCtrl: levelCtrl, - energyCtrl: energyCtrl, - adminEnergyCtrl: adminEnergyCtrl, - energyService: energyService, - commentCtrl: commentCtrl, - commentService: commentService, - adminCommentCtrl: adminCommentCtrl, - reactionCtrl: reactionCtrl, - followCtrl: followCtrl, - favoriteCtrl: favoriteCtrl, - favoriteService: favoriteService, - trendsService: trendsService, - homeCache: homeCache, + siteSettingCtrl: siteSettingController, + levelCtrl: levelCtrl, + energyCtrl: energyCtrl, + adminEnergyCtrl: adminEnergyCtrl, + energyService: energyService, + commentCtrl: commentCtrl, + commentService: commentService, + adminCommentCtrl: adminCommentCtrl, + reactionCtrl: reactionCtrl, + followCtrl: followCtrl, + favoriteCtrl: favoriteCtrl, + favoriteService: favoriteService, + trendsService: trendsService, + homeCache: homeCache, announcementService: announcementService, - announcementCtrl: announcementCtrl, - categoryCtrl: categoryCtrl, - categoryService: categoryService, - tagCtrl: tagCtrl, - tagService: tagService, + announcementCtrl: announcementCtrl, + categoryCtrl: categoryCtrl, + categoryService: categoryService, + tagCtrl: tagCtrl, + tagService: tagService, } } diff --git a/internal/router/router.go b/internal/router/router.go index 563cc7f..739e3c5 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,3 +1,4 @@ +// Package router 负责依赖注入组装和 HTTP 路由注册。 package router import ( diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 4af6577..c955000 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -1,3 +1,4 @@ +// Package scheduler 提供定时任务调度器,管理定时发布等周期性任务。 package scheduler import ( diff --git a/internal/service/admin_service.go b/internal/service/admin_service.go index 7f434cc..a742ee9 100644 --- a/internal/service/admin_service.go +++ b/internal/service/admin_service.go @@ -1,3 +1,4 @@ +// Package service 包含核心业务逻辑实现,通过 Store 接口隔离数据访问层。 package service import ( diff --git a/internal/service/audit_review.go b/internal/service/audit_review.go index ad15a54..3fd46fc 100644 --- a/internal/service/audit_review.go +++ b/internal/service/audit_review.go @@ -1,6 +1,8 @@ package service import ( + "errors" + "metazone.cc/mce/internal/common" "metazone.cc/mce/internal/model" @@ -12,7 +14,7 @@ func (s *AuditService) review(reviewerID uint, submissionID uint, approved bool, // 1. 查审核记录 submission, err := s.auditRepo.FindByID(submissionID) if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return common.ErrAuditNotFound } return err diff --git a/internal/service/audit_service.go b/internal/service/audit_service.go index 58979a3..20f60d7 100644 --- a/internal/service/audit_service.go +++ b/internal/service/audit_service.go @@ -56,14 +56,14 @@ type avatarFileCleaner interface { // AuditService 审核业务逻辑 type AuditService struct { - auditRepo auditRepo - userRepo userProfileStore - settings siteSettingsChecker - defaultCfg config.AuditConfig - notifier auditNotifier - levelSvc auditTaskCompleter - energyRefundSvc energyRenameRefunder - avatarCleaner avatarFileCleaner + auditRepo auditRepo + userRepo userProfileStore + settings siteSettingsChecker + defaultCfg config.AuditConfig + notifier auditNotifier + levelSvc auditTaskCompleter + energyRefundSvc energyRenameRefunder + avatarCleaner avatarFileCleaner } // NewAuditService 构造函数 diff --git a/internal/service/auth_password.go b/internal/service/auth_password.go index 6e582f4..b05672c 100644 --- a/internal/service/auth_password.go +++ b/internal/service/auth_password.go @@ -4,7 +4,6 @@ import ( "regexp" "strings" "time" - "unicode" "metazone.cc/mce/internal/common" "metazone.cc/mce/internal/model" @@ -13,10 +12,12 @@ import ( "gorm.io/gorm" ) -var pwLower = regexp.MustCompile(`[a-z]`) -var pwUpper = regexp.MustCompile(`[A-Z]`) -var pwDigit = regexp.MustCompile(`\d`) -var pwSymbol = regexp.MustCompile(`[^a-zA-Z0-9]`) +var ( + pwLower = regexp.MustCompile(`[a-z]`) + pwUpper = regexp.MustCompile(`[A-Z]`) + pwDigit = regexp.MustCompile(`\d`) + pwSymbol = regexp.MustCompile(`[^a-zA-Z0-9]`) +) // 键盘水平序列(QWERTY 布局) var keyboardHoriz = []string{ @@ -181,16 +182,6 @@ func PasswordStrengthHint(level string) string { return "密码至少 8 位" } -// hasUnicode 检查是否包含非 ASCII 字符 -func hasUnicode(s string) bool { - for _, r := range s { - if r > unicode.MaxASCII { - return true - } - } - return false -} - // ChangePassword 修改密码 // 流程:验证当前密码 → 校验新密码强度 → 哈希 → 更新 → 删除所有 session(强制重新登录) func (s *AuthService) ChangePassword(userID uint, currentPassword, newPassword string) error { diff --git a/internal/service/avatar_service.go b/internal/service/avatar_service.go index d103066..45c7adc 100644 --- a/internal/service/avatar_service.go +++ b/internal/service/avatar_service.go @@ -22,8 +22,8 @@ import ( // 头像处理常量 const ( - avatarMaxFileSize = 5 * 1024 * 1024 // 5MB - avatarMinWidth = 128 // 最小宽高,防止马赛克图被拉升 + avatarMaxFileSize = 5 * 1024 * 1024 // 5MB + avatarMinWidth = 128 // 最小宽高,防止马赛克图被拉升 avatarMinHeight = 128 avatarMaxWidth = 3840 avatarMaxHeight = 3840 @@ -97,7 +97,7 @@ func (s *AvatarService) ProcessImage(userID uint, file io.Reader, contentType st img, _, err = image.Decode(bytes.NewReader(data)) } if err != nil { - return "", fmt.Errorf("图片解码失败:%v", err) + return "", fmt.Errorf("图片解码失败:%w", err) } bounds := img.Bounds() w, h := bounds.Dx(), bounds.Dy() @@ -129,11 +129,11 @@ func (s *AvatarService) ProcessImage(userID uint, file io.Reader, contentType st // 6. 编码为 WebP,目标 ≤100KB webpData, err := encodeWebP(resized, avatarTargetKB*1024) if err != nil { - return "", fmt.Errorf("图片编码失败:%v", err) + return "", fmt.Errorf("图片编码失败:%w", err) } // 7. 确保存储目录存在 - if err := os.MkdirAll(s.storageDir, 0755); err != nil { + if err := os.MkdirAll(s.storageDir, 0o755); err != nil { //nolint:gosec // 头像存储目录标准权限 return "", fmt.Errorf("创建存储目录失败") } @@ -143,11 +143,11 @@ func (s *AvatarService) ProcessImage(userID uint, file io.Reader, contentType st filename := fmt.Sprintf("%d_%d.webp", userID, ts) tmpPath := filepath.Join(s.storageDir, filename+".tmp") finalPath := filepath.Join(s.storageDir, filename) - if err := os.WriteFile(tmpPath, webpData, 0644); err != nil { + if err := os.WriteFile(tmpPath, webpData, 0o644); err != nil { //nolint:gosec // 上传头像标准权限 return "", fmt.Errorf("写入临时文件失败") } if err := os.Rename(tmpPath, finalPath); err != nil { - os.Remove(tmpPath) + _ = os.Remove(tmpPath) return "", fmt.Errorf("保存文件失败") } @@ -163,7 +163,7 @@ func (s *AvatarService) CleanOldAvatars(userID uint) { } for _, e := range entries { if !e.IsDir() && strings.HasPrefix(e.Name(), prefix) && strings.HasSuffix(e.Name(), ".webp") { - os.Remove(filepath.Join(s.storageDir, e.Name())) + _ = os.Remove(filepath.Join(s.storageDir, e.Name())) } } } @@ -242,4 +242,3 @@ func encodeWebP(img image.Image, targetBytes int) ([]byte, error) { } return buf.Bytes(), nil } - diff --git a/internal/service/comment_service.go b/internal/service/comment_service.go index 2778def..6defb49 100644 --- a/internal/service/comment_service.go +++ b/internal/service/comment_service.go @@ -64,7 +64,7 @@ func (s *CommentService) CreateRoot(userID uint, postID uint, body string) (*mod // 通知文章作者(评论者 ≠ 作者),RelatedID 存 postID 以便消息页生成跳转链接 if s.notifier != nil && userID != postAuthorID { - s.notifier.Create(postAuthorID, model.NotifyComment, + _ = s.notifier.Create(postAuthorID, model.NotifyComment, "新评论", fmt.Sprintf("%s 评论了你的文章", commenterName), &comment.PostID, &comment.ID) @@ -112,7 +112,7 @@ func (s *CommentService) CreateReply(userID uint, parentID uint, body string) (* // 通知被回复者(不自己回复自己),RelatedID 存 postID 以便消息页生成跳转链接 if s.notifier != nil && parent.UserID != userID { - s.notifier.Create(parent.UserID, model.NotifyCommentReply, + _ = s.notifier.Create(parent.UserID, model.NotifyCommentReply, "新回复", fmt.Sprintf("%s 回复了你的评论", commenterName), &parent.PostID, &comment.ID) @@ -235,7 +235,7 @@ func (s *CommentService) parseMentions(commentID uint, postID uint, commenterID // 向被@用户发送通知(排除自己@自己、重复通知) if s.notifier != nil && mentionedUID != commenterID && !notified[mentionedUID] { notified[mentionedUID] = true - s.notifier.Create(mentionedUID, model.NotifyCommentMention, + _ = s.notifier.Create(mentionedUID, model.NotifyCommentMention, "新提及", fmt.Sprintf("%s 在评论中 @ 了你", commenterName), &postID, &commentID) diff --git a/internal/service/energy_service.go b/internal/service/energy_service.go index 2c2d60e..1e60178 100644 --- a/internal/service/energy_service.go +++ b/internal/service/energy_service.go @@ -30,22 +30,22 @@ func (s *EnergyService) SetFundRepo(repo FundStore) { // EnergyInfo 域能信息(含每日赋能经验状态) type EnergyInfo struct { - Energy int // 域能余额(乘10存储) - DailyEnergizeExp int // 当日已通过赋能获得的经验值 - DailyExpCap int // 每日赋能经验上限 - DailyExpCapReached bool // 是否已达上限 - PostEnergizeTotal int // 当前文章已赋能量(DB乘10,仅在传 postID 时返回) + Energy int // 域能余额(乘10存储) + DailyEnergizeExp int // 当日已通过赋能获得的经验值 + DailyExpCap int // 每日赋能经验上限 + DailyExpCapReached bool // 是否已达上限 + PostEnergizeTotal int // 当前文章已赋能量(DB乘10,仅在传 postID 时返回) } // 域能相关常量(乘10存储,DB值) const ( - EnergyCheckIn = 10 // +1.0 域能 - EnergyRename = -60 // -6.0 域能 - EnergyRenameRefund = 60 // +6.0 域能 - EnergyDeletePost = -20 // -2.0 域能 - EnergyEnergize1 = 10 // 轻赋 1 域能 - EnergyEnergize2 = 20 // 重赋 2 域能 - EnergizeExpPerEnergy = 10 // 每 1 域能 = 10 经验 - DailyEnergizeExpCap = 50 // 每日赋能经验上限 - MaxEnergizePerPost = 20 // 单用户单文章最多赋能 2 域能(×10) + EnergyCheckIn = 10 // +1.0 域能 + EnergyRename = -60 // -6.0 域能 + EnergyRenameRefund = 60 // +6.0 域能 + EnergyDeletePost = -20 // -2.0 域能 + EnergyEnergize1 = 10 // 轻赋 1 域能 + EnergyEnergize2 = 20 // 重赋 2 域能 + EnergizeExpPerEnergy = 10 // 每 1 域能 = 10 经验 + DailyEnergizeExpCap = 50 // 每日赋能经验上限 + MaxEnergizePerPost = 20 // 单用户单文章最多赋能 2 域能(×10) ) diff --git a/internal/service/favorite_items.go b/internal/service/favorite_items.go index 57861d3..5398ff9 100644 --- a/internal/service/favorite_items.go +++ b/internal/service/favorite_items.go @@ -19,7 +19,6 @@ func (s *FavoriteService) AddToFolder(userID, folderID, postID uint) error { } return s.repo.Transaction(func(txRepo FavoriteStore) error { - // 如果该文章已在其他收藏夹中,先移除 existing, err := txRepo.FindItemByUserAndPost(userID, postID) if err == nil && existing.FolderID != folderID { diff --git a/internal/service/favorite_service.go b/internal/service/favorite_service.go index 5c5ccdf..7e65808 100644 --- a/internal/service/favorite_service.go +++ b/internal/service/favorite_service.go @@ -51,8 +51,8 @@ type FolderItemsResult struct { // FavoriteStatus 某文章的收藏状态 type FavoriteStatus struct { - Favorited bool `json:"favorited"` - FolderID *uint `json:"folder_id,omitempty"` + Favorited bool `json:"favorited"` + FolderID *uint `json:"folder_id,omitempty"` FolderName *string `json:"folder_name,omitempty"` } diff --git a/internal/service/level_service.go b/internal/service/level_service.go index 44a0047..3d6b42d 100644 --- a/internal/service/level_service.go +++ b/internal/service/level_service.go @@ -9,10 +9,10 @@ import ( // LevelService 积分与等级业务逻辑 type LevelService struct { - userRepo userLevelStore - checkRepo checkInStore - taskRepo taskStore - notifSvc levelNotifier + userRepo userLevelStore + checkRepo checkInStore + taskRepo taskStore + notifSvc levelNotifier energyCheckInSvc energyCheckIner } diff --git a/internal/service/post_service.go b/internal/service/post_service.go index a653681..5530f69 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -15,11 +15,11 @@ import ( // PostService 帖子业务逻辑 type PostService struct { - repo postStore - ss *config.SiteSettings - notifier postNotifier - userRepo userExpReader - onHomePageChanged func() + repo postStore + ss *config.SiteSettings + notifier postNotifier + userRepo userExpReader + onHomePageChanged func() } // NewPostService 构造函数 @@ -146,12 +146,12 @@ func (s *PostService) ListByUser(userID uint, status string, page, pageSize int) // StudioOverview 创作中心数据概览 type StudioOverview struct { - TotalPosts int64 `json:"total_posts"` - DraftCount int64 `json:"draft_count"` - PendingCount int64 `json:"pending_count"` + TotalPosts int64 `json:"total_posts"` + DraftCount int64 `json:"draft_count"` + PendingCount int64 `json:"pending_count"` ApprovedCount int64 `json:"approved_count"` RejectedCount int64 `json:"rejected_count"` - TotalViews int64 `json:"total_views"` + TotalViews int64 `json:"total_views"` } // GetOverview 获取创作中心数据概览(单次 GROUP BY 查询) diff --git a/internal/service/reaction_service.go b/internal/service/reaction_service.go index 4ba6f78..91e9bb6 100644 --- a/internal/service/reaction_service.go +++ b/internal/service/reaction_service.go @@ -11,8 +11,8 @@ import ( type ReactionType string const ( - ReactionNone ReactionType = "none" - ReactionLiked ReactionType = "liked" + ReactionNone ReactionType = "none" + ReactionLiked ReactionType = "liked" ReactionDisliked ReactionType = "disliked" ) diff --git a/internal/service/repository.go b/internal/service/repository.go index 03f1fca..3f4355d 100644 --- a/internal/service/repository.go +++ b/internal/service/repository.go @@ -125,9 +125,6 @@ type EnergyStore interface { Transaction(fn func(txEnergy EnergyStore, txFund FundStore) error) error } -// energyStore 向后兼容别名 -type energyStore = EnergyStore - // FundStore 公户仓储接口(ISP) type FundStore interface { GetBalance() (int, error) diff --git a/internal/service/shortcode_service.go b/internal/service/shortcode_service.go index 22cc53c..e4940f1 100644 --- a/internal/service/shortcode_service.go +++ b/internal/service/shortcode_service.go @@ -98,10 +98,11 @@ func parseShortcode(raw string) (model.Shortcode, bool) { // renderPlaceholder 根据 shortcode 类型生成前端占位 HTML // // 占位 div 结构约定: -//