Files
jiang13-forum/service/common.go
freefire 20aee9c566 chore: 清理 SSR 改造后的历史遗留死代码与误提交产物
- 删除图形验证码整条死链:service/captcha.go、APICaptcha、
  GET /api/captcha、CaptchaService 装配、ErrCaptchaInvalid 及前端
  api.captcha()(注册/登录已改用邮箱验证码,Verify 无任何调用方)
- 删除 GET /api/posts/hot 死路由:APIHotPosts + PostService.HotPosts
  (热门排序走 /api/posts?sort=hot)
- 删除 GET /api/posts/:id/revisions/:revId 死路由:
  APIPostRevisionDetail + PostService.GetRevision + ErrRevisionNotFound
  及前端 api.postRevision()(历史面板仅用列表接口)
- 停止跟踪误提交的 27.5MB Linux 二进制 jiang13-linux-amd64 与
  .vite 依赖缓存;.gitignore 补充对应规则并移除已失效的
  embed_static 条目
2026-09-11 00:39:58 +08:00

154 lines
4.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"errors"
"fmt"
"net/mail"
"strings"
"sync"
"unicode"
"unicode/utf8"
"golang.org/x/crypto/bcrypt"
)
var (
ErrUserExists = errors.New("用户名已存在")
ErrEmailExists = errors.New("邮箱已被注册")
ErrInvalidCred = errors.New("用户名或密码错误")
ErrUserBanned = errors.New("账号已被禁言")
ErrWeakPassword = errors.New("密码至少 6 位")
ErrInvalidUsername = errors.New("用户名 2-32 位,支持中文、字母、数字与下划线")
ErrInvalidEmail = errors.New("邮箱格式不正确")
ErrPostNotFound = errors.New("帖子不存在")
ErrCommentNotFound = errors.New("评论不存在")
ErrPermissionDenied = errors.New("无权操作")
ErrBoardNotFound = errors.New("板块不存在")
ErrPostEditLocked = errors.New("帖子已被管理员锁定,无法编辑")
ErrPostCommentsLocked = errors.New("该帖子已锁定讨论,无法评论")
ErrPostEditExpired = errors.New("已超过可编辑时限")
ErrInvalidSetting = errors.New("无效的设置值")
ErrSearchKeywordTooShort = errors.New("搜索关键词过短")
ErrSearchKeywordTooLong = errors.New("搜索关键词过长")
ErrPostTitleTooLong = errors.New("标题过长")
ErrPostTagsTooLong = errors.New("标签过长")
ErrPostContentTooLong = errors.New("正文过长")
ErrCommentTooLong = errors.New("评论内容过长")
ErrEmailCodeInvalid = errors.New("邮箱验证码错误或已过期")
ErrEmailCodeCooldown = errors.New("发送过于频繁,请稍后再试")
ErrMailNotConfigured = errors.New("邮件服务未配置或未启用")
ErrRegisterClosed = errors.New("论坛暂未开放注册,请联系管理员配置邮件服务")
)
// HashPassword 使用 bcrypt 加密密码
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// CheckPassword 校验密码
func CheckPassword(hash, password string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
// ValidateUsername 校验用户名:中文/字母/数字/下划线,2-32 个字符
func ValidateUsername(username string) error {
n := utf8.RuneCountInString(username)
if n < 2 || n > 32 {
return ErrInvalidUsername
}
for _, r := range username {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
continue
}
return ErrInvalidUsername
}
return nil
}
// NormalizeEmail 规范化邮箱(小写去空格)
func NormalizeEmail(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}
// ValidateEmail 校验邮箱格式
func ValidateEmail(email string) error {
email = NormalizeEmail(email)
if email == "" {
return ErrInvalidEmail
}
addr, err := mail.ParseAddress(email)
if err != nil || addr.Address != email {
return ErrInvalidEmail
}
return nil
}
// ValidatePassword 校验密码强度
func ValidatePassword(password string, minLen int) error {
if minLen <= 0 {
minLen = 6
}
if utf8.RuneCountInString(password) < minLen {
return fmt.Errorf("密码至少 %d 位", minLen)
}
return nil
}
// SensitiveFilter 敏感词过滤器
type SensitiveFilter struct {
mu sync.RWMutex
words []string
}
func NewSensitiveFilter() *SensitiveFilter {
return &SensitiveFilter{
words: []string{"违禁词示例", "广告刷单"},
}
}
// LoadFromFile 从配置文件加载敏感词,每行一个词
func (f *SensitiveFilter) LoadFromFile(path string) {
data, err := osReadFile(path)
if err != nil {
return
}
lines := strings.Split(string(data), "\n")
var words []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "#") {
words = append(words, line)
}
}
if len(words) > 0 {
f.mu.Lock()
f.words = words
f.mu.Unlock()
}
}
func (f *SensitiveFilter) Filter(text string) string {
f.mu.RLock()
defer f.mu.RUnlock()
result := text
for _, w := range f.words {
if w == "" {
continue
}
replacement := strings.Repeat("*", utf8.RuneCountInString(w))
result = strings.ReplaceAll(result, w, replacement)
}
return result
}
// osReadFile 避免循环依赖,简单封装
func osReadFile(path string) ([]byte, error) {
return readFile(path)
}
// readFile 由 filter_io.go 实现
var readFile = func(path string) ([]byte, error) {
return nil, errors.New("not implemented")
}