密码强度下限、离线推送下发

一、密码规则:至少 8 位且不能全是数字。短信验证可以在后台关掉,关掉之后
密码就是账号质量的唯一门槛,而此前 validUserPassword 只检查非空——"1"
也是合法密码。规则只在「设置密码」时校验(注册、找回、改密、后台建号与
后台重置),登录不再校验,已有账号照常使用。上下限都按字符数计算,否则
43 个汉字的密码会因为字节数超限被拒。各处错误提示改为直接说明规则,
而不是笼统的一句「请填写有效的密码」。

二、离线推送:此前客户端一直在上报 push token,服务端从未下发过任何东西,
App 退到后台或被杀掉时新消息完全没有提醒(MESSAGE_PUSH 只是 WebSocket
帧名)。补上服务端下发:
- 只发给「此刻不在线 + 未对该会话免打扰 + 未关闭消息通知」的接收者,
  在线的人已经从实时通道拿到了。
- 鉴权 token 按 provider 的过期时间缓存,个推的 auth 接口限流很紧。
- 失效的 cid(10001/10002)就地停用,不再每条消息重试一次。
- 整个过程在独立 goroutine 与独立 context 上进行,推送服务再慢也不会
  拖慢或拖垮一条已经发出的消息。
- 是否显示正文由 push.show_preview 控制,关闭后锁屏上不出现消息内容。
凭据在管理端「离线推送」页填写(迁移 035 先建出配置行——集成配置保存
走的是 UPDATE,行不存在会静默保存不上)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-04 19:09:33 +08:00
co-authored by Claude Opus 5
parent f313979e88
commit ce429afcf2
14 changed files with 686 additions and 16 deletions
+13 -4
View File
@@ -94,8 +94,13 @@ func (a *App) register(w http.ResponseWriter, r *http.Request) {
fail(w, http.StatusBadRequest, 20001, err.Error())
return
}
if !validPhone(req.Phone) || !validUserPassword(req.Password) || strings.TrimSpace(req.Nickname) == "" || len([]rune(strings.TrimSpace(req.Nickname))) > 50 {
fail(w, http.StatusBadRequest, 20001, "请填写有效的手机号昵称和密码")
if !validPhone(req.Phone) || strings.TrimSpace(req.Nickname) == "" || len([]rune(strings.TrimSpace(req.Nickname))) > 50 {
fail(w, http.StatusBadRequest, 20001, "请填写有效的手机号昵称")
return
}
// Said separately: "请填写有效的密码" leaves the reader guessing what is wrong.
if !validUserPassword(req.Password) {
fail(w, http.StatusBadRequest, 20001, passwordRule)
return
}
if !a.rateLimit(w, r, "register_ip", clientIP(r), 20, 10*time.Minute) || !a.rateLimit(w, r, "register_phone", strings.TrimSpace(req.Phone), 10, 10*time.Minute) {
@@ -210,8 +215,12 @@ func (a *App) loginSMS(w http.ResponseWriter, r *http.Request) {
func (a *App) resetPassword(w http.ResponseWriter, r *http.Request) {
var req authRequest
if err := decode(r, &req); err != nil || !validPhone(req.Phone) || len(req.Code) != 6 || !validUserPassword(req.Password) {
fail(w, 400, 20001, "请填写有效的手机号验证码和新密码")
if err := decode(r, &req); err != nil || !validPhone(req.Phone) || len(req.Code) != 6 {
fail(w, 400, 20001, "请填写有效的手机号验证码")
return
}
if !validUserPassword(req.Password) {
fail(w, 400, 20001, passwordRule)
return
}
if !a.rateLimit(w, r, "password_reset_ip", clientIP(r), 20, 10*time.Minute) || !a.rateLimit(w, r, "password_reset_phone", strings.TrimSpace(req.Phone), 10, 10*time.Minute) {