Files
mce/internal/controller/settings_controller.go
Victor_Jay 213b2a571e feat: 登录管理设备列表显示会话类型和剩余有效期
- 临时会话显示橙色'临时会话'徽章
- 非当前设备显示剩余有效时间(临时/长期均显示)
- 当前设备不显示剩余时间(持续续期,显示无意义)
- 新增 formatTTL 模板函数,分钟数转人类可读格式
- sessionConfig 接口增加 GetRememberTimeout 方法
2026-05-31 00:36:25 +08:00

150 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"io"
"net/http"
"time"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/session"
"github.com/gin-gonic/gin"
)
// profileProvider SettingsController 对 Service 层的最小依赖ISP4 个方法)
type profileProvider interface {
GetProfile(userID uint) (*model.User, error)
UpdateProfile(userID uint, username, bio string) error
ChangePassword(userID uint, currentPassword, newPassword string) error
DeleteAccount(userID uint, password, reason string) error
}
// avatarProvider 头像上传对 Service 层的最小依赖ISP2 个方法)
type avatarProvider interface {
ProcessAvatar(userID uint, file io.Reader, contentType string, cropX, cropY, cropSize int) (string, error)
ProcessImage(userID uint, file io.Reader, contentType string, cropX, cropY, cropSize int) (string, error)
}
// auditSubmittable 个人设置对审核服务的依赖ISP4 个方法)
type auditSubmittable interface {
ShouldAudit(userID uint) (bool, error)
SubmitProfileChanges(userID uint, currentUser *model.User, newUsername, newBio string) error
Submit(userID uint, auditType, newValue string) error
GetPendingTypes(userID uint) ([]string, error)
}
// sessionConfig 登录管理对配置的最小依赖ISP2 个方法)
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, sessionCfg sessionConfig) *SettingsController {
return &SettingsController{authService: authService, avatarService: avatarService, auditService: auditService, sessionMgr: sessionMgr, sessionCfg: sessionCfg}
}
// SettingsPage 个人设置页面(需登录)
func (sc *SettingsController) SettingsPage(c *gin.Context) {
uidVal, exists := c.Get("uid")
if !exists {
c.Redirect(http.StatusFound, "/auth/login")
c.Abort()
return
}
uid := uidVal.(uint)
tab := c.Param("tab")
if tab != "profile" && tab != "account" && tab != "sessions" {
c.String(http.StatusNotFound, "页面不存在")
return
}
user, err := sc.authService.GetProfile(uid)
if err != nil || user == nil {
c.String(http.StatusNotFound, "用户不存在")
return
}
// 查询待审核类型(用于前端显示审核提示)
pendingTypes, _ := sc.auditService.GetPendingTypes(uid)
data := gin.H{
"Title": "个人设置",
"ExtraCSS": "/static/css/settings.css",
"ActiveTab": tab,
"User": user,
"RoleName": model.RoleDisplayNames[user.Role],
"StatusName": model.StatusDisplayNames[user.Status],
"PendingTypes": pendingTypes,
"AuditTypes": model.AuditTypeNames,
}
// 登录管理 tab 需要额外数据
if tab == "sessions" {
sessions, _ := sc.sessionMgr.ListByUID(uid)
if sessions == nil {
sessions = []*session.Session{}
}
currentSID, _ := c.Cookie(common.SessionCookieName)
// 解析每个 session 的设备信息
type sessionView struct {
*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
data["CurrentSID"] = currentSID
}
c.HTML(http.StatusOK, "settings/index.html", common.BuildPageData(c, data))
}
// AuditStatus 查询当前用户的待审核类型(需登录)
func (sc *SettingsController) AuditStatus(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
types, err := sc.auditService.GetPendingTypes(uid.(uint))
if err != nil {
common.Ok(c, gin.H{"pending_types": []string{}})
return
}
if types == nil {
types = []string{}
}
common.Ok(c, gin.H{"pending_types": types})
}