Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5b57203fb | |||
| a51501909a | |||
| 672214c06e |
@@ -1,13 +1,13 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cpdaily-new/internal/config"
|
||||
"cpdaily-new/internal/database"
|
||||
"cpdaily-new/internal/model"
|
||||
"cpdaily-new/internal/service"
|
||||
cpd "cpdaily-new/internal/service/cpdaily"
|
||||
"cpdaily-new/internal/utils"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -585,6 +585,12 @@ func BindInviteCode(c *fiber.Ctx) error {
|
||||
if code == "" {
|
||||
return utils.FailParam(c, "请输入邀请码")
|
||||
}
|
||||
// 格式预校验:邀请码固定 8 位且只用 inviteCodeChars 字符集。
|
||||
// 不合规的输入直接拒绝,避免每次都在库里做一次无用查询;
|
||||
// 用统一文案,不泄露「码存在但格式对」这类信息。
|
||||
if !isValidInviteCodeShape(code) {
|
||||
return utils.Fail(c, 400, "邀请码无效")
|
||||
}
|
||||
|
||||
var user model.SysUser
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
|
||||
@@ -205,3 +205,27 @@ func ResetInviteCode(c *fiber.Ctx) error {
|
||||
slog.Info("用户重置邀请码", "userId", userID, "oldCode", oldCode, "newCode", code)
|
||||
return utils.Success(c, fiber.Map{"inviteCode": code})
|
||||
}
|
||||
|
||||
// inviteCodeLegacyLength 旧版邀请码长度。
|
||||
//
|
||||
// 2026-10 之前生成为 6 位,生产库中仍有大量存量用户沿用旧码
|
||||
// (实测 551 个 6 位 / 1 个 8 位)。校验必须同时接受两种长度,
|
||||
// 否则存量用户的码会被判为无效,导致他们无法被邀请。
|
||||
const inviteCodeLegacyLength = 6
|
||||
|
||||
// isValidInviteCodeShape 校验邀请码的形态(长度 + 字符集),不查库。
|
||||
//
|
||||
// 长度接受 6(存量)与 8(当前)两种,字符集为 inviteCodeChars
|
||||
// (已去掉易混淆的 0/O/1/L)。不合规的输入无需查库即可拒绝,
|
||||
// 也避免把用户的任意长字符串当码去查。
|
||||
func isValidInviteCodeShape(code string) bool {
|
||||
if len(code) != inviteCodeLength && len(code) != inviteCodeLegacyLength {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(code); i++ {
|
||||
if !strings.ContainsRune(inviteCodeChars, rune(code[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -163,3 +163,42 @@ func TestErrInviteCodeChangedIsDistinct(t *testing.T) {
|
||||
t.Fatal("errors.Is 自反性失败")
|
||||
}
|
||||
}
|
||||
|
||||
// isValidInviteCodeShape:只校验形态(长度 + 字符集),不查库
|
||||
func TestIsValidInviteCodeShape(t *testing.T) {
|
||||
valid, err := newInviteCode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
code string
|
||||
want bool
|
||||
}{
|
||||
{"正常 8 位", valid, true},
|
||||
{"空串", "", false},
|
||||
{"过短", "ABC", false},
|
||||
{"过长", "ABCDEFGHJ", false},
|
||||
{"含易混淆字符 0", "ABCD0EFG", false},
|
||||
{"含易混淆字符 O", "ABCDOEFG", false},
|
||||
{"含易混淆字符 1", "ABCD1EFG", false},
|
||||
{"含易混淆字符 I", "ABCDIEFG", false},
|
||||
{"含易混淆字符 L", "ABCDLEFG", false},
|
||||
{"含小写", "abcdefgh", false},
|
||||
{"含符号", "ABCD-EFG", false},
|
||||
{"含中文", "邀请码邀请码邀请", false},
|
||||
// 存量兼容:生产库仍有 551 个 6 位旧码,必须仍能正常绑定
|
||||
{"存量 6 位码", "ABCDEF", true},
|
||||
{"存量 6 位含易混淆字符", "ABCD0F", false},
|
||||
{"5 位", "ABCDE", false},
|
||||
{"7 位", "ABCDEFG", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if got := isValidInviteCodeShape(c.code); got != c.want {
|
||||
t.Fatalf("isValidInviteCodeShape(%q)=%v, want %v", c.code, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,14 @@ watch(
|
||||
)
|
||||
|
||||
function onVisibility() {
|
||||
if (document.visibilityState === 'visible') schedule()
|
||||
else clearTimer()
|
||||
if (document.visibilityState === 'visible') {
|
||||
// 回到前台时重新测量:后台期间可能发生过尺寸变化(横竖屏切换、
|
||||
// 系统字体缩放等),若沿用旧的 overflow,长文本的滚动距离会算错,
|
||||
// 出现「还没滚完就翻页」或「滚过头留白」。
|
||||
void measure().then(schedule)
|
||||
} else {
|
||||
clearTimer()
|
||||
}
|
||||
}
|
||||
|
||||
function openAll() {
|
||||
|
||||
@@ -394,11 +394,26 @@ async function onResetInviteTap() {
|
||||
try {
|
||||
const res = await authApi.resetInviteCode()
|
||||
const code = res.data.data?.inviteCode || ''
|
||||
if (code) inviteCode.value = code
|
||||
if (!code) {
|
||||
// 接口成功但未返回新码:不能静默当作成功,否则页面仍显示旧码
|
||||
message.error('重置失败:未获取到新的邀请码,请重试')
|
||||
return
|
||||
}
|
||||
inviteCode.value = code
|
||||
message.success('邀请码已重置,请使用新的邀请码分享')
|
||||
} catch (e: any) {
|
||||
const status = e?.response?.status
|
||||
const msg = e?.response?.data?.message || e?.message || '重置失败,请稍后重试'
|
||||
message.error(msg)
|
||||
// 409:并发重置冲突,说明服务端已是新码。重新拉取,避免页面继续显示旧码
|
||||
if (status === 409) {
|
||||
try {
|
||||
const info = await authApi.getMyInviteInfo()
|
||||
applyInviteInfo(info.data.data)
|
||||
} catch {
|
||||
// 拉取失败不覆盖原提示,用户可手动刷新
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
resettingInvite.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user