feat: 登录管理设备列表显示会话类型和剩余有效期
- 临时会话显示橙色'临时会话'徽章 - 非当前设备显示剩余有效时间(临时/长期均显示) - 当前设备不显示剩余时间(持续续期,显示无意义) - 新增 formatTTL 模板函数,分钟数转人类可读格式 - sessionConfig 接口增加 GetRememberTimeout 方法
This commit is contained in:
@ -129,6 +129,16 @@ func bindEnvOverride(v *viper.Viper) {
|
||||
_ = v.BindEnv("database.password", "DATABASE_PASSWORD")
|
||||
}
|
||||
|
||||
// GetIdleTimeout 返回临时会话空闲超时(分钟),实现 controller.sessionConfig 接口
|
||||
func (c *Config) GetIdleTimeout() int {
|
||||
return c.Session.IdleTimeout
|
||||
}
|
||||
|
||||
// GetRememberTimeout 返回记住我会话空闲超时(分钟)
|
||||
func (c *Config) GetRememberTimeout() int {
|
||||
return c.Session.RememberTimeout
|
||||
}
|
||||
|
||||
// DSN 返回 PostgreSQL 连接字符串
|
||||
func (d DatabaseConfig) DSN() string {
|
||||
return "host=" + d.Host +
|
||||
|
||||
@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/model"
|
||||
@ -33,17 +34,24 @@ type auditSubmittable interface {
|
||||
GetPendingTypes(userID uint) ([]string, error)
|
||||
}
|
||||
|
||||
// sessionConfig 登录管理对配置的最小依赖(ISP:2 个方法)
|
||||
type sessionConfig interface {
|
||||
GetIdleTimeout() int // 分钟
|
||||
GetRememberTimeout() int // 分钟
|
||||
}
|
||||
|
||||
// SettingsController 个人设置控制器
|
||||
type SettingsController struct {
|
||||
authService profileProvider
|
||||
avatarService avatarProvider
|
||||
auditService auditSubmittable
|
||||
sessionMgr sessionManager
|
||||
sessionCfg sessionConfig
|
||||
}
|
||||
|
||||
// NewSettingsController 构造函数
|
||||
func NewSettingsController(authService profileProvider, avatarService avatarProvider, auditService auditSubmittable, sessionMgr sessionManager) *SettingsController {
|
||||
return &SettingsController{authService: authService, avatarService: avatarService, auditService: auditService, sessionMgr: sessionMgr}
|
||||
func NewSettingsController(authService profileProvider, avatarService avatarProvider, auditService auditSubmittable, sessionMgr sessionManager, sessionCfg sessionConfig) *SettingsController {
|
||||
return &SettingsController{authService: authService, avatarService: avatarService, auditService: auditService, sessionMgr: sessionMgr, sessionCfg: sessionCfg}
|
||||
}
|
||||
|
||||
// SettingsPage 个人设置页面(需登录)
|
||||
@ -94,13 +102,24 @@ func (sc *SettingsController) SettingsPage(c *gin.Context) {
|
||||
*session.Session
|
||||
DeviceInfo session.DeviceInfo
|
||||
IsCurrent bool
|
||||
TTLMinutes int // 剩余有效分钟数(仅非当前设备显示)
|
||||
}
|
||||
var views []sessionView
|
||||
for _, s := range sessions {
|
||||
timeout := sc.sessionCfg.GetIdleTimeout() // 临时会话超时(分钟)
|
||||
if s.RememberMe {
|
||||
timeout = sc.sessionCfg.GetRememberTimeout() // 记住我超时(分钟)
|
||||
}
|
||||
elapsed := int(time.Since(s.LastAccess).Minutes())
|
||||
ttl := timeout - elapsed
|
||||
if ttl < 0 {
|
||||
ttl = 0
|
||||
}
|
||||
views = append(views, sessionView{
|
||||
Session: s,
|
||||
DeviceInfo: session.ParseUA(s.UserAgent),
|
||||
IsCurrent: s.ID == currentSID,
|
||||
TTLMinutes: ttl,
|
||||
})
|
||||
}
|
||||
data["Sessions"] = views
|
||||
|
||||
@ -22,7 +22,7 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting
|
||||
|
||||
messageController := controller.NewMessageController(notificationService)
|
||||
|
||||
settingsCtrl := controller.NewSettingsController(authService, avatarSvc, auditService, sessionMgr)
|
||||
settingsCtrl := controller.NewSettingsController(authService, avatarSvc, auditService, sessionMgr, cfg)
|
||||
siteSettingController := adminCtrl.NewSiteSettingController(
|
||||
service.NewSiteSettingService(siteSettings),
|
||||
&service.AuditDefaults{
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -17,8 +18,9 @@ type TemplateRoot struct {
|
||||
// 模板名 = prefix + 相对于 root.Dir 的相对路径
|
||||
func LoadTemplates(roots ...TemplateRoot) (*template.Template, error) {
|
||||
funcMap := template.FuncMap{
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"subtract": func(a, b int) int { return a - b },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"subtract": func(a, b int) int { return a - b },
|
||||
"formatTTL": formatTTL,
|
||||
}
|
||||
t := template.New("").Funcs(funcMap)
|
||||
for _, root := range roots {
|
||||
@ -55,3 +57,23 @@ func LoadContent(path string) (template.HTML, error) {
|
||||
}
|
||||
return template.HTML(content), nil
|
||||
}
|
||||
|
||||
// formatTTL 将剩余分钟数格式化为人类可读的时间(如 "23 小时"、"3 天"、"15 分钟")
|
||||
func formatTTL(minutes int) string {
|
||||
if minutes <= 0 {
|
||||
return "即将过期"
|
||||
}
|
||||
if minutes < 60 {
|
||||
return fmt.Sprintf("%d 分钟", minutes)
|
||||
}
|
||||
hours := minutes / 60
|
||||
if hours < 24 {
|
||||
return fmt.Sprintf("%d 小时", hours)
|
||||
}
|
||||
days := hours / 24
|
||||
remainHours := hours % 24
|
||||
if remainHours == 0 {
|
||||
return fmt.Sprintf("%d 天", days)
|
||||
}
|
||||
return fmt.Sprintf("%d 天 %d 小时", days, remainHours)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user