管理端补上运营真正要用的那几件事

原来 107 个后台接口配的是一套只能看的界面:风险中心 6 行代码全是只读,
动态和消息只能一条一条点,运营想发一句公告没有任何入口。

公告与通知:按全体、会员、城市、喂养者、有宠物的用户或指定名单发系统通知,
发之前能看到会发给多少人,可同时走离线推送。撤回只删还没读的那些——已经
读过的假装收回去只会让统计说谎。

资金总览:把"平台账上哪些钱不是自己的"单独摆出来(任务托管中 + 喂养者余额 +
申诉冻结),旁边才是收入、退款和待打款,最后是按产品和按日的收款。

退款统一到一个入口:会员、代喂、保证金都能分几次退到退完,每笔记一行;
退完最后一分钱才撤销买到的东西,半份会员仍然是会员。

风险中心能动手了:按等级和状态筛,行内直接封禁、冻结、加白(必须写理由)、
跳用户详情,事件可以标记跟进完成。

批量处置:动态、消息、举报支持勾选后批量下架/恢复/驳回,一次最多 100 条。
处罚仍然一条一条来——批量封禁是错封人最快的方式。

敏感词统一词库:消息、动态、送养共用一套,命中是拦截还是转人工由运营定;
转人工的命中会在风险中心留一条,管理端还能拿真实文案先试一遍。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-07 13:07:58 +08:00
co-authored by Claude Opus 5
parent 3e6a4900e2
commit 0031f79eb3
13 changed files with 1589 additions and 23 deletions
+11 -1
View File
@@ -564,6 +564,16 @@ func (a *App) createPost(w http.ResponseWriter, r *http.Request) {
if req.Visibility != 2 {
req.Visibility = 1
}
// 动态和消息共用一套词库:拦截词直接拒绝,转人工的词先发出去但排进审核队列。
moderation := 1
if word, action := a.matchKeyword(r.Context(), "post", req.Content); word != "" {
if action == "block" {
fail(w, 400, 20001, "动态包含平台不允许的内容")
return
}
moderation = 0
a.recordKeywordRisk(r.Context(), current(r).ID, "post", word)
}
tx, err := a.db.BeginTx(r.Context(), nil)
if err != nil {
fail(w, 500, 50001, "发布失败")
@@ -578,7 +588,7 @@ func (a *App) createPost(w http.ResponseWriter, r *http.Request) {
return
}
}
result, err := tx.ExecContext(r.Context(), `INSERT INTO posts (user_id,content,visibility,city_code,location_text) VALUES (?,?,?,'310100',?)`, current(r).ID, req.Content, req.Visibility, req.Location)
result, err := tx.ExecContext(r.Context(), `INSERT INTO posts (user_id,content,visibility,city_code,location_text,moderation_status) VALUES (?,?,?,'310100',?,?)`, current(r).ID, req.Content, req.Visibility, req.Location, moderation)
if err != nil {
_ = tx.Rollback()
fail(w, 500, 50001, "发布失败")