From 229ed2254ba5f195a4c96b6cf8e79961ce885eb1 Mon Sep 17 00:00:00 2001 From: Rodger-Wang <1367893453@qq.com> Date: Tue, 29 Sep 2026 14:12:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(services)=EF=BC=9A=E6=94=B9=E6=98=B5?= =?UTF-8?q?=E7=A7=B0=E6=97=B6=E5=8A=A0=E8=BD=BD=E5=86=85=E7=BD=AE=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E8=AF=8D=E5=BA=93=EF=BC=8C=E6=8B=A6=E4=BD=8F=E6=AF=92?= =?UTF-8?q?=E5=93=81=E7=B1=BB=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部署配置里的词库默认不加载,校验等于空跑。把词库打进 home,命中即拒绝保存。 Co-authored-by: Cursor --- apps/services/lego/sys/wordfilter/embed.go | 25 +++++++++++++++++++ apps/services/lego/sys/wordfilter/sys.go | 11 +++++++- apps/services/lego/sys/wordfilter/sys_test.go | 25 ++++++++++++++++++- apps/services/modules/user/api_setting.go | 2 +- apps/services/modules/user/api_sgin.go | 20 ++++++++++----- deploy/app/confs/home.yaml.example | 3 ++- 6 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 apps/services/lego/sys/wordfilter/embed.go diff --git a/apps/services/lego/sys/wordfilter/embed.go b/apps/services/lego/sys/wordfilter/embed.go new file mode 100644 index 00000000..c0769a8c --- /dev/null +++ b/apps/services/lego/sys/wordfilter/embed.go @@ -0,0 +1,25 @@ +package wordfilter + +import ( + _ "embed" + "strings" +) + +// 内置词库随二进制打进 home。部署配置里的 WorldFile 默认全部注释, +// 不内置的话改昵称时词树是空的,Validate 恒通过。 +// +//go:embed wordfilter.txt +var builtinWordDict string + +func (s *Sys) loadBuiltin() (int, error) { + n := 0 + for _, line := range strings.Split(builtinWordDict, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + s.trie.Add(line) + n++ + } + return n, nil +} diff --git a/apps/services/lego/sys/wordfilter/sys.go b/apps/services/lego/sys/wordfilter/sys.go index 8628e838..fa5e62d0 100644 --- a/apps/services/lego/sys/wordfilter/sys.go +++ b/apps/services/lego/sys/wordfilter/sys.go @@ -2,7 +2,6 @@ package wordfilter import ( "bufio" - "yunyan/lego/sys/log" "encoding/json" "fmt" "io" @@ -13,6 +12,7 @@ import ( "regexp" "strings" "time" + "yunyan/lego/sys/log" ) func newSys(options *Options) (sys *Sys, err error) { @@ -21,6 +21,15 @@ func newSys(options *Options) (sys *Sys, err error) { trie: NewTrie(), noise: regexp.MustCompile(`[\|\s&%$@*]+`), } + // 内置词库始终加载。WorldFile 只作追加;配了但文件不存在仍按原逻辑返回错误, + // 由 home 启动处 panic,避免「列表里写了路径、服务器上没有文件」时静默漏拦。 + var n int + if n, err = sys.loadBuiltin(); err != nil { + return + } + if options.Log != nil { + options.Log.Infof("wordfilter: 已加载内置敏感词 %d 条", n) + } if len(options.WorldFile) > 0 { for _, v := range options.WorldFile { ext := filepath.Ext(v) diff --git a/apps/services/lego/sys/wordfilter/sys_test.go b/apps/services/lego/sys/wordfilter/sys_test.go index 9ef698be..22902a32 100644 --- a/apps/services/lego/sys/wordfilter/sys_test.go +++ b/apps/services/lego/sys/wordfilter/sys_test.go @@ -1,9 +1,10 @@ package wordfilter_test import ( - "yunyan/lego/sys/wordfilter" "fmt" "testing" + + "yunyan/lego/sys/wordfilter" ) // 国内 @@ -15,3 +16,25 @@ func Test_sys_wordfilter(t *testing.T) { fmt.Println(ok, str) } } + +// 内置词库不依赖部署目录里的 txt。改名「售卖海洛因」必须被拦住。 +func TestBuiltinDictRejectsDrugTradeName(t *testing.T) { + sys, err := wordfilter.NewSys() + if err != nil { + t.Fatal(err) + } + ok, hit := sys.Validate("售卖海洛因") + if ok { + t.Fatal("售卖海洛因 应被内置词库拒绝") + } + if hit != "海洛因" { + t.Fatalf("命中词 = %q,期望 海洛因", hit) + } + // 空格会被当成噪声去掉,插空格绕不过去。 + if ok, _ = sys.Validate("售卖 海洛因"); ok { + t.Fatal("插空格后仍应拒绝") + } + if ok, hit = sys.Validate("小明"); !ok { + t.Fatalf("普通昵称不应命中,得到 %q", hit) + } +} diff --git a/apps/services/modules/user/api_setting.go b/apps/services/modules/user/api_setting.go index 80bd96e1..e62da281 100644 --- a/apps/services/modules/user/api_setting.go +++ b/apps/services/modules/user/api_setting.go @@ -42,7 +42,7 @@ func (this *apiComp) Setting(session comm.IUserSession, req *pb.UserSettingReq) if ok, _ = wordfilter.Validate(req.Name); !ok { errdata = &pb.ErrorData{ Code: pb.ErrorCode_NameInscriptionWords, - Message: pb.ErrorCode_NameInscriptionWords.String(), + Message: "名称包含敏感词", } return } diff --git a/apps/services/modules/user/api_sgin.go b/apps/services/modules/user/api_sgin.go index 1392e62f..9e8300b6 100644 --- a/apps/services/modules/user/api_sgin.go +++ b/apps/services/modules/user/api_sgin.go @@ -4,6 +4,7 @@ import ( "context" "yunyan/comm" "yunyan/lego/sys/mysql" + "yunyan/lego/sys/wordfilter" "yunyan/lego/utils/container/id" "yunyan/pb" apple_auth "yunyan/sys/auth/apple" @@ -233,9 +234,7 @@ func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp // if user.Mail != "" { // user.Mail = aes.AesEncryptCBC(user.Mail, this.options.CBCKey) // } - if user.Name == "" { - user.Name = fmt.Sprintf("User_%d", rand.Intn(10000)) - } + user.Name = cleanDisplayName(user.Name) // user.Name = aes.AesEncryptCBC(user.Name, this.options.CBCKey) // if user.Avatar != "" { @@ -281,9 +280,7 @@ func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp case pb.SginTyoe_Apple: user.Appleopenid = apple_uuid } - if user.Name == "" { - user.Name = fmt.Sprintf("User_%d", rand.Intn(10000)) - } + user.Name = cleanDisplayName(user.Name) } if istestaccount { user.Vipexptime = time.Now().AddDate(1, 0, 0).Unix() @@ -346,3 +343,14 @@ func (this *apiComp) Sgin(session comm.IUserSession, req *pb.UserSginReq) (resp } return } + +// cleanDisplayName 去掉空名和敏感词。登录不能因为微信昵称不干净就拒绝建号, +// 换成 User_xxxx,之后可以在资料页再改一个干净的。 +func cleanDisplayName(name string) string { + if name != "" { + if ok, _ := wordfilter.Validate(name); ok { + return name + } + } + return fmt.Sprintf("User_%d", rand.Intn(10000)) +} diff --git a/deploy/app/confs/home.yaml.example b/deploy/app/confs/home.yaml.example index d0b2b249..b6d2d400 100644 --- a/deploy/app/confs/home.yaml.example +++ b/deploy/app/confs/home.yaml.example @@ -18,7 +18,8 @@ sys: Name: "home" MaxReconnects: -1 ReconnectWait: 2 - # 敏感词过滤:默认【全部注释掉 = 不加载任何词库】。 + # 敏感词过滤:内置词库打在 home 二进制里,改昵称命中即拒,不依赖下面的文件。 + # WorldFile 是额外词库,默认全部注释。 # ⚠️ 这些 txt 不在 git 里,要用就得自己放到部署目录的 wordfilter/ 下(会随工作目录挂进容器 /app)。 # 列表里只要有一个文件在服务器上不存在,home 就会 `panic: init sys.wordfilter err: no found file:...` # → entrypoint 杀掉整个容器 → 无限重启;而表象常常是 api 报「Table 'xxx.userdevice' doesn't exist」