feat: 站点媒体库与移动端底部导航,服务测试补强
- 新增媒体库:media_library/media_thumbs 服务(WebP 缩略图)、管理端 media 页面 - 移动端底部导航 MobileTabBar 替换 MobilePostBar - 旧数据导入增强与测试、路由与登录会话/板块侧边栏测试补强 - site-doc 组件精简(移除 Breadcrumb),文档新增迁移公告说明
This commit is contained in:
@@ -163,8 +163,10 @@ func (s *AuthService) LookupFamilyByRefreshPlain(plain string, userID uint) uint
|
||||
return sessionFamilyID(rt)
|
||||
}
|
||||
|
||||
// TouchDeviceFromRefresh 用当前请求的 IP/UA 校准本会话,并回填尚未写入的 family_id。
|
||||
func (s *AuthService) TouchDeviceFromRefresh(plain, ip, ua string, userID uint) uint {
|
||||
// TouchDeviceFromRefresh 刷新本会话活跃时间,并回填尚未写入的 family_id。
|
||||
// 会话的 IP/UA 以登录时记录为准,不随访问端环境漂移,
|
||||
// 否则设备指纹会跟着请求变,设备列表在 UA 模拟/代理下会来回跳。
|
||||
func (s *AuthService) TouchDeviceFromRefresh(plain string, userID uint) uint {
|
||||
if plain == "" {
|
||||
return 0
|
||||
}
|
||||
@@ -177,12 +179,6 @@ func (s *AuthService) TouchDeviceFromRefresh(plain, ip, ua string, userID uint)
|
||||
}
|
||||
now := time.Now()
|
||||
upd := map[string]any{"last_used_at": now, "updated_at": now}
|
||||
if v := truncateStr(ip, 45); v != "" {
|
||||
upd["ip"] = v
|
||||
}
|
||||
if v := truncateStr(ua, 500); v != "" {
|
||||
upd["user_agent"] = v
|
||||
}
|
||||
fid := sessionFamilyID(rt)
|
||||
if rt.FamilyID == 0 {
|
||||
upd["family_id"] = fid
|
||||
@@ -191,26 +187,29 @@ func (s *AuthService) TouchDeviceFromRefresh(plain, ip, ua string, userID uint)
|
||||
return fid
|
||||
}
|
||||
|
||||
// revokeAllSessionsExcept 真·单会话:登录时吊销该用户除新会话外的全部
|
||||
// 有效会话(任何新登录都踢掉所有旧设备);返回吊销行数供上层广播告知。
|
||||
func (s *AuthService) revokeAllSessionsExcept(userID, keepFamily uint) int {
|
||||
// maxActiveSessions 每个用户允许并存的活跃会话(登录设备)上限,
|
||||
// 与设备列表展示上限 loginDeviceLimit 一致;超出时淘汰最久未使用的会话。
|
||||
const maxActiveSessions = 20
|
||||
|
||||
// enforceSessionCap 会话上限守护:多会话并存下新登录不踢旧设备,仅当该用户
|
||||
// 活跃会话超过 maxActiveSessions 时,按 last_used_at 淘汰最旧的会话腾位;
|
||||
// 返回淘汰行数供上层广播告知(新会话 last_used_at 最新,不会被淘汰)。
|
||||
func (s *AuthService) enforceSessionCap(userID uint) int {
|
||||
now := time.Now()
|
||||
var actives []model.RefreshToken
|
||||
if err := s.db.Select("id", "family_id").
|
||||
if err := s.db.Select("id").
|
||||
Where("user_id = ? AND revoked = ? AND expires_at > ?", userID, false, now).
|
||||
Order("last_used_at DESC, created_at DESC, id DESC").
|
||||
Find(&actives).Error; err != nil {
|
||||
return 0
|
||||
}
|
||||
ids := make([]uint, 0)
|
||||
for _, t := range actives {
|
||||
if sessionFamilyID(t) == keepFamily {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, t.ID)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
if len(actives) <= maxActiveSessions {
|
||||
return 0
|
||||
}
|
||||
ids := make([]uint, 0, len(actives)-maxActiveSessions)
|
||||
for _, t := range actives[maxActiveSessions:] {
|
||||
ids = append(ids, t.ID)
|
||||
}
|
||||
res := s.db.Model(&model.RefreshToken{}).Where("id IN ?", ids).
|
||||
Updates(map[string]any{"revoked": true, "token_cipher": "", "updated_at": now})
|
||||
if res.Error != nil {
|
||||
|
||||
Reference in New Issue
Block a user