- 临时会话显示橙色'临时会话'徽章 - 非当前设备显示剩余有效时间(临时/长期均显示) - 当前设备不显示剩余时间(持续续期,显示无意义) - 新增 formatTTL 模板函数,分钟数转人类可读格式 - sessionConfig 接口增加 GetRememberTimeout 方法
150 lines
4.5 KiB
Go
150 lines
4.5 KiB
Go
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 层的最小依赖(ISP:4 个方法)
|
||
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 层的最小依赖(ISP:2 个方法)
|
||
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 个人设置对审核服务的依赖(ISP:4 个方法)
|
||
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 登录管理对配置的最小依赖(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, 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})
|
||
}
|