fix: golangci-lint 零告警通过 (57→0) + gofumpt/goimports 全量格式化
## CI 修复 (P0/P1) P0 — 编译阻塞: - interfaces.go: postUseCase ISP 接口移除不用的 Create/Update/Delete P1 — 必须修复: - ST1000: 为 13 个包添加包注释 (common/config/model/service/...) - ST1005: redis_store.go 全部错误消息改为小写开头 - errcheck (19处): defer Close()→闭包忽略, notifier.Create→_=, r.Run→检查error - errorlint (9处): switch-on-error→errors.Is 链, ==→errors.Is - ST1020/ST1022: 导出符号注释以符号名开头 P2 — 安全评审: - gosec (12处): G203/G301/G304/G306 添加 nolint 注释并附理由 P3 — 清理: - unused: 移除 hasUnicode/energyStore/current/energyAdminUseCase - gofumpt + goimports 全量格式化 (35+ 文件)
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
// Package service 包含核心业务逻辑实现,通过 Store 接口隔离数据访问层。
|
||||
package service
|
||||
|
||||
import (
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 构造函数
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 查询)
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
type ReactionType string
|
||||
|
||||
const (
|
||||
ReactionNone ReactionType = "none"
|
||||
ReactionLiked ReactionType = "liked"
|
||||
ReactionNone ReactionType = "none"
|
||||
ReactionLiked ReactionType = "liked"
|
||||
ReactionDisliked ReactionType = "disliked"
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -98,10 +98,11 @@ func parseShortcode(raw string) (model.Shortcode, bool) {
|
||||
// renderPlaceholder 根据 shortcode 类型生成前端占位 HTML
|
||||
//
|
||||
// 占位 div 结构约定:
|
||||
// <div class="zone-card" data-zone-type="类型" data-zone-id="参数"
|
||||
// data-zone-extra="附加信息">
|
||||
// <span class="zone-card-loading">卡片加载中...</span>
|
||||
// </div>
|
||||
//
|
||||
// <div class="zone-card" data-zone-type="类型" data-zone-id="参数"
|
||||
// data-zone-extra="附加信息">
|
||||
// <span class="zone-card-loading">卡片加载中...</span>
|
||||
// </div>
|
||||
//
|
||||
// 前端 shortcode.js 根据 data-zone-* 属性决定如何渲染。
|
||||
// "加载中..." 文本会被 shortcode.js 在渲染时替换,仅作降级显示。
|
||||
|
||||
Reference in New Issue
Block a user