a773a33779
- backend:POST /auth/web-ticket(需登录,签 60s 单次票据,每用户限频)+ POST /auth/web-ticket/exchange(公开,原子即焚兑换正常登录态,响应同 /auth/login 另带 shop_code);web_tickets 新表 + user_sessions.kind 列 (sso 会话:web 平台、refresh 12h 硬到期、不占设备并发配额、设备管理可见 可踢);清理任务顺带清过期票据;schema.sql/AutoMigrate/testutil 同步 - web:/sso 落地页——兑票写入与 Web 版共享的 localStorage 后跳 redirect; redirect 白名单只收本站相对路径(防 open redirect),票据即时从地址栏抹除 - client:launchAuthedWebPath(ref, path) 封装(签票→拼 /sso URL→launchUrl, 失败降级未登录直开;桌面/移动同一套);授权面板「查看套餐价格」接入 test(backend): web_ticket_test 三用例(兑换+续期/单次即焚/过期拒绝/ sso 不占配额双向验证) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
package service
|
|
|
|
import (
|
|
"log"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/wangjia/jiu/backend/internal/model"
|
|
)
|
|
|
|
// sessionCleanupInterval 清理任务执行周期。
|
|
const sessionCleanupInterval = 24 * time.Hour
|
|
|
|
// StartSessionCleanup 启动后台清理 goroutine:启动即跑一次,之后每 24h 跑一次。
|
|
// 删除已撤销/已过期的会话行与过旧的失败登录记录,防止表无限膨胀、IP/UA 长期滞留。
|
|
// retentionDays<=0 时视为关闭清理(直接返回,不启动 goroutine)。
|
|
func StartSessionCleanup(db *gorm.DB, retentionDays int) {
|
|
if retentionDays <= 0 {
|
|
log.Printf("[cleanup] session cleanup disabled (retention_days=%d)", retentionDays)
|
|
return
|
|
}
|
|
go func() {
|
|
cleanupOnce(db, retentionDays)
|
|
ticker := time.NewTicker(sessionCleanupInterval)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
cleanupOnce(db, retentionDays)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// cleanupOnce 执行一轮清理,返回各表删除行数(供测试断言)。
|
|
func cleanupOnce(db *gorm.DB, retentionDays int) (sessions, attempts int64) {
|
|
cutoff := time.Now().AddDate(0, 0, -retentionDays)
|
|
|
|
r1 := db.Where(
|
|
"(revoked_at IS NOT NULL AND revoked_at < ?) OR (refresh_exp_at IS NOT NULL AND refresh_exp_at < ?)",
|
|
cutoff, cutoff,
|
|
).Delete(&model.UserSession{})
|
|
if r1.Error != nil {
|
|
log.Printf("[cleanup] purge user_sessions failed: %v", r1.Error)
|
|
}
|
|
|
|
r2 := db.Where("created_at < ?", cutoff).Delete(&model.LoginAttempt{})
|
|
if r2.Error != nil {
|
|
log.Printf("[cleanup] purge login_attempts failed: %v", r2.Error)
|
|
}
|
|
|
|
// 一次性网页登录票据:过期即无用(TTL 60s),过期一天后物理清除
|
|
if err := db.Where("expires_at < ?", time.Now().AddDate(0, 0, -1)).
|
|
Delete(&model.WebTicket{}).Error; err != nil {
|
|
log.Printf("[cleanup] purge web_tickets failed: %v", err)
|
|
}
|
|
|
|
if r1.RowsAffected > 0 || r2.RowsAffected > 0 {
|
|
log.Printf("[cleanup] purged %d sessions, %d login_attempts (older than %dd)",
|
|
r1.RowsAffected, r2.RowsAffected, retentionDays)
|
|
}
|
|
return r1.RowsAffected, r2.RowsAffected
|
|
}
|