Browse Source

fix(services):改昵称时加载内置敏感词库,拦住毒品类名称

部署配置里的词库默认不加载,校验等于空跑。把词库打进 home,命中即拒绝保存。

Co-authored-by: Cursor <cursoragent@cursor.com>
along-test
Rodger-Wang 2 weeks ago
parent
commit
229ed2254b
  1. 25
      apps/services/lego/sys/wordfilter/embed.go
  2. 11
      apps/services/lego/sys/wordfilter/sys.go
  3. 25
      apps/services/lego/sys/wordfilter/sys_test.go
  4. 2
      apps/services/modules/user/api_setting.go
  5. 20
      apps/services/modules/user/api_sgin.go
  6. 3
      deploy/app/confs/home.yaml.example

25
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
}

11
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)

25
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)
}
}

2
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
}

20
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))
}

3
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」

Loading…
Cancel
Save