feat(config): add sensitive word filtering system with database support
All checks were successful
Build Backend / build (push) Successful in 12m54s
Build Backend / build-docker (push) Successful in 1m41s

Implement sensitive word filtering feature with configurable database and Redis
support. Add security enhancements including WebSocket origin validation, improved
password strength requirements, timing attack prevention, and production JWT secret
validation. Add batch operation validation limits and optimize user blocking
queries with subqueries.

BREAKING CHANGE: Password validation now requires minimum 8 characters with uppercase,
lowercase, and digit (was 6-50 characters without complexity requirements)
This commit is contained in:
lafay
2026-04-07 00:07:40 +08:00
parent 6429322217
commit 98b8abd26a
20 changed files with 263 additions and 84 deletions

View File

@@ -508,7 +508,7 @@ func (r *commentRepository) GetCommentsByCursor(ctx context.Context, postID stri
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Comment]([]*model.Comment{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Comment{}, "", "", false), nil
}
// 执行查询
@@ -629,7 +629,7 @@ func (r *commentRepository) GetRepliesByCursor(ctx context.Context, rootID strin
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Comment]([]*model.Comment{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Comment{}, "", "", false), nil
}
// 执行查询

View File

@@ -492,7 +492,7 @@ func (r *groupRepository) GetGroupsByCursor(ctx context.Context, req *cursor.Pag
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Group]([]*model.Group{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Group{}, "", "", false), nil
}
// 执行查询
@@ -551,7 +551,7 @@ func (r *groupRepository) GetUserGroupsByCursor(ctx context.Context, userID stri
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Group]([]*model.Group{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Group{}, "", "", false), nil
}
// 执行查询
@@ -605,7 +605,7 @@ func (r *groupRepository) GetMembersByCursor(ctx context.Context, groupID string
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.GroupMember]([]*model.GroupMember{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.GroupMember{}, "", "", false), nil
}
// 执行查询
@@ -660,7 +660,7 @@ func (r *groupRepository) GetAnnouncementsByCursor(ctx context.Context, groupID
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.GroupAnnouncement]([]*model.GroupAnnouncement{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.GroupAnnouncement{}, "", "", false), nil
}
// 执行查询(置顶的排在前面)

View File

@@ -747,7 +747,7 @@ func (r *messageRepository) GetMessagesByCursor(ctx context.Context, conversatio
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Message]([]*model.Message{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Message{}, "", "", false), nil
}
// 执行查询
@@ -810,7 +810,7 @@ func (r *messageRepository) GetConversationsByCursor(ctx context.Context, userID
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Conversation]([]*model.Conversation{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Conversation{}, "", "", false), nil
}
// 执行查询 - 需要添加置顶排序

View File

@@ -111,7 +111,7 @@ func (r *notificationRepository) GetNotificationsByCursor(ctx context.Context, u
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Notification]([]*model.Notification{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Notification{}, "", "", false), nil
}
// 执行查询

View File

@@ -127,9 +127,10 @@ func (r *operationLogRepository) GetStatistics(ctx context.Context, startTime, e
// 汇总结果
for _, r := range results {
stats.TotalCount += r.Count
if r.Status == "success" {
switch r.Status {
case "success":
stats.SuccessCount = r.Count
} else if r.Status == "fail" {
case "fail":
stats.FailCount = r.Count
}
}

View File

@@ -996,7 +996,7 @@ func (r *postRepository) GetPostsByCursor(ctx context.Context, userID string, in
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Post]([]*model.Post{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Post{}, "", "", false), nil
}
// 执行查询
@@ -1064,7 +1064,7 @@ func (r *postRepository) SearchPostsByCursor(ctx context.Context, keyword string
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Post]([]*model.Post{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Post{}, "", "", false), nil
}
// 执行查询
@@ -1124,7 +1124,7 @@ func (r *postRepository) GetUserPostsByCursor(ctx context.Context, userID string
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Post]([]*model.Post{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Post{}, "", "", false), nil
}
// 执行查询
@@ -1180,7 +1180,7 @@ func (r *postRepository) GetFollowingPostsByCursor(ctx context.Context, userID s
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Post]([]*model.Post{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Post{}, "", "", false), nil
}
// 执行查询
@@ -1234,7 +1234,7 @@ func (r *postRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.Pa
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult[*model.Post]([]*model.Post{}, "", "", false), nil
return cursor.NewCursorPageResult([]*model.Post{}, "", "", false), nil
}
// 执行查询

View File

@@ -0,0 +1,66 @@
package repository
import (
"context"
"carrot_bbs/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type SensitiveWordRepository interface {
Create(ctx context.Context, word *model.SensitiveWord) error
GetByWord(ctx context.Context, word string) (*model.SensitiveWord, error)
Update(ctx context.Context, word *model.SensitiveWord) error
Upsert(ctx context.Context, word *model.SensitiveWord) error
DeactivateByWord(ctx context.Context, word string) error
ListActive(ctx context.Context) ([]model.SensitiveWord, error)
}
type sensitiveWordRepository struct {
db *gorm.DB
}
func NewSensitiveWordRepository(db *gorm.DB) SensitiveWordRepository {
return &sensitiveWordRepository{db: db}
}
func (r *sensitiveWordRepository) Create(ctx context.Context, word *model.SensitiveWord) error {
return r.db.WithContext(ctx).Create(word).Error
}
func (r *sensitiveWordRepository) GetByWord(ctx context.Context, word string) (*model.SensitiveWord, error) {
var sw model.SensitiveWord
err := r.db.WithContext(ctx).Where("word = ?", word).First(&sw).Error
if err != nil {
return nil, err
}
return &sw, nil
}
func (r *sensitiveWordRepository) Update(ctx context.Context, word *model.SensitiveWord) error {
return r.db.WithContext(ctx).Save(word).Error
}
func (r *sensitiveWordRepository) Upsert(ctx context.Context, word *model.SensitiveWord) error {
return r.db.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "word"}},
DoUpdates: clause.AssignmentColumns([]string{
"category", "level", "is_active", "updated_by", "remark", "updated_at",
}),
}).Create(word).Error
}
func (r *sensitiveWordRepository) DeactivateByWord(ctx context.Context, word string) error {
return r.db.WithContext(ctx).
Model(&model.SensitiveWord{}).
Where("word = ?", word).
Update("is_active", false).Error
}
func (r *sensitiveWordRepository) ListActive(ctx context.Context) ([]model.SensitiveWord, error) {
var words []model.SensitiveWord
err := r.db.WithContext(ctx).Where("is_active = ?", true).Find(&words).Error
return words, err
}

View File

@@ -377,21 +377,15 @@ func (r *userRepository) BlockUserAndCleanupRelations(blockerID, blockedID strin
}
for _, uid := range []string{blockerID, blockedID} {
var followersCount int64
if err := tx.Model(&model.Follow{}).Where("following_id = ?", uid).Count(&followersCount).Error; err != nil {
return err
}
if err := tx.Model(&model.User{}).Where("id = ?", uid).
UpdateColumn("followers_count", followersCount).Error; err != nil {
UpdateColumn("followers_count", tx.Model(&model.Follow{}).
Select("COUNT(*)").Where("following_id = ?", uid)).Error; err != nil {
return err
}
var followingCount int64
if err := tx.Model(&model.Follow{}).Where("follower_id = ?", uid).Count(&followingCount).Error; err != nil {
return err
}
if err := tx.Model(&model.User{}).Where("id = ?", uid).
UpdateColumn("following_count", followingCount).Error; err != nil {
UpdateColumn("following_count", tx.Model(&model.Follow{}).
Select("COUNT(*)").Where("follower_id = ?", uid)).Error; err != nil {
return err
}
}