mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Fix a large number of bugs
This commit is contained in:
10
cmd/root.go
10
cmd/root.go
@@ -75,8 +75,14 @@ func setupLogrusForNonHTTP() {
|
|||||||
// 初始化HTTP日志处理器
|
// 初始化HTTP日志处理器
|
||||||
logger.InitLogger()
|
logger.InitLogger()
|
||||||
|
|
||||||
// 记录配置加载完成
|
// 记录配置加载完成,使用相对路径或文件名保持一致性
|
||||||
logrus.WithField("file", viper.ConfigFileUsed()).Info("配置文件加载完成")
|
configFile := viper.ConfigFileUsed()
|
||||||
|
if configFile != "" {
|
||||||
|
fileName := filepath.Base(configFile)
|
||||||
|
logrus.WithField("file", fileName).Info("配置文件加载完成")
|
||||||
|
} else {
|
||||||
|
logrus.Info("配置加载完成(内存默认配置)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig 读取配置文件和环境变量
|
// initConfig 读取配置文件和环境变量
|
||||||
|
|||||||
@@ -170,20 +170,18 @@ func Init(cfgFilePath string) {
|
|||||||
},
|
},
|
||||||
).Fatal("配置文件解析错误")
|
).Fatal("配置文件解析错误")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 只显示配置文件名,不显示完整路径
|
|
||||||
configFile := viper.ConfigFileUsed()
|
|
||||||
if configFile != "" {
|
|
||||||
// 提取文件名
|
|
||||||
fileName := filepath.Base(configFile)
|
|
||||||
log.WithFields(
|
|
||||||
log.Fields{
|
|
||||||
"file": fileName,
|
|
||||||
},
|
|
||||||
).Info("使用配置文件")
|
|
||||||
} else {
|
} else {
|
||||||
log.Info("使用默认配置")
|
// 只显示配置文件名,不显示完整路径
|
||||||
|
configFile := viper.ConfigFileUsed()
|
||||||
|
if configFile != "" {
|
||||||
|
// 统一使用 filepath.Clean 和 filepath.Base 处理路径展示
|
||||||
|
cleanPath := filepath.Clean(configFile)
|
||||||
|
log.WithFields(
|
||||||
|
log.Fields{
|
||||||
|
"file": cleanPath,
|
||||||
|
},
|
||||||
|
).Info("使用配置文件")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证配置
|
// 验证配置
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"NetworkAuth/controllers"
|
"NetworkAuth/controllers"
|
||||||
"NetworkAuth/models"
|
"NetworkAuth/models"
|
||||||
|
"NetworkAuth/services"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -385,6 +386,20 @@ func AppCreateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"创建应用",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"创建了应用: "+app.Name,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithField("app_uuid", app.UUID).Debug("Successfully created app with default APIs")
|
logrus.WithField("app_uuid", app.UUID).Debug("Successfully created app with default APIs")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -555,6 +570,20 @@ func AppDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"删除应用",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"删除了应用: "+app.Name,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"app_id": app.ID,
|
"app_id": app.ID,
|
||||||
"app_uuid": app.UUID,
|
"app_uuid": app.UUID,
|
||||||
|
|||||||
@@ -211,9 +211,20 @@ func DashboardLoginLogsHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
|
// 获取当前管理员信息(可能是 username 或 admin_username,具体取决于认证中间件设置的 key)
|
||||||
|
username := c.GetString("admin_username")
|
||||||
|
if username == "" {
|
||||||
|
// 尝试获取其他可能的键名
|
||||||
|
username = c.GetString("username")
|
||||||
|
}
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
// 当前模型的 LoginLog 本身就是专用于 admin 的登录日志模型(没有 type 字段),所以直接查询全部即可
|
query := db.Model(&models.LoginLog{}).Where("type = ?", "admin")
|
||||||
query := db.Model(&models.LoginLog{})
|
|
||||||
|
// 如果有用户名,则仅过滤该用户的日志
|
||||||
|
if username != "" {
|
||||||
|
query = query.Where("username = ?", username)
|
||||||
|
}
|
||||||
|
|
||||||
if err := query.Count(&total).Error; err != nil {
|
if err := query.Count(&total).Error; err != nil {
|
||||||
handlersBaseController.HandleInternalError(c, "获取登录日志总数失败", err)
|
handlersBaseController.HandleInternalError(c, "获取登录日志总数失败", err)
|
||||||
@@ -232,3 +243,4 @@ func DashboardLoginLogsHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
handlersBaseController.HandleSuccess(c, "获取登录日志成功", data)
|
handlersBaseController.HandleSuccess(c, "获取登录日志成功", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"NetworkAuth/controllers"
|
"NetworkAuth/controllers"
|
||||||
"NetworkAuth/models"
|
"NetworkAuth/models"
|
||||||
|
"NetworkAuth/services"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -203,6 +204,20 @@ func FunctionCreateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"创建函数",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"创建了函数: "+function.Alias,
|
||||||
|
)
|
||||||
|
|
||||||
functionBaseController.HandleSuccess(c, "创建成功", function)
|
functionBaseController.HandleSuccess(c, "创建成功", function)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +307,10 @@ func FunctionDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找函数以记录日志
|
||||||
|
var function models.Function
|
||||||
|
db.First(&function, req.ID)
|
||||||
|
|
||||||
// 删除函数
|
// 删除函数
|
||||||
if err := db.Delete(&models.Function{}, req.ID).Error; err != nil {
|
if err := db.Delete(&models.Function{}, req.ID).Error; err != nil {
|
||||||
logrus.WithError(err).Error("Failed to delete function")
|
logrus.WithError(err).Error("Failed to delete function")
|
||||||
@@ -299,6 +318,25 @@ func FunctionDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
details := "删除了函数ID: " + strconv.Itoa(int(req.ID))
|
||||||
|
if function.ID != 0 {
|
||||||
|
details = "删除了函数: " + function.Alias
|
||||||
|
}
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"删除函数",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithField("function_id", req.ID).Debug("Successfully deleted function")
|
logrus.WithField("function_id", req.ID).Debug("Successfully deleted function")
|
||||||
|
|
||||||
functionBaseController.HandleSuccess(c, "删除成功", nil)
|
functionBaseController.HandleSuccess(c, "删除成功", nil)
|
||||||
@@ -331,6 +369,26 @@ func FunctionsBatchDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
var idStrs []string
|
||||||
|
for _, id := range req.IDs {
|
||||||
|
idStrs = append(idStrs, strconv.Itoa(int(id)))
|
||||||
|
}
|
||||||
|
details := "批量删除了函数ID: " + strings.Join(idStrs, ",")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"删除函数",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithField("function_ids", req.IDs).Debug("Successfully batch deleted functions")
|
logrus.WithField("function_ids", req.IDs).Debug("Successfully batch deleted functions")
|
||||||
|
|
||||||
functionBaseController.HandleSuccess(c, "批量删除成功", nil)
|
functionBaseController.HandleSuccess(c, "批量删除成功", nil)
|
||||||
|
|||||||
@@ -162,9 +162,6 @@ func LoginLogsClearHandler(c *gin.Context) {
|
|||||||
OperationType: "清空登录日志",
|
OperationType: "清空登录日志",
|
||||||
Operator: operator,
|
Operator: operator,
|
||||||
OperatorUUID: "", // NetworkAuth 中暂时无法获取 UUID
|
OperatorUUID: "", // NetworkAuth 中暂时无法获取 UUID
|
||||||
AppName: "-",
|
|
||||||
ProductName: "-",
|
|
||||||
TransactionID: "-",
|
|
||||||
Details: "管理员清空了所有登录日志",
|
Details: "管理员清空了所有登录日志",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ func LogsListHandler(c *gin.Context) {
|
|||||||
endTimeStr := strings.TrimSpace(c.Query("end_time"))
|
endTimeStr := strings.TrimSpace(c.Query("end_time"))
|
||||||
operationType := strings.TrimSpace(c.Query("operation_type"))
|
operationType := strings.TrimSpace(c.Query("operation_type"))
|
||||||
operator := strings.TrimSpace(c.Query("operator"))
|
operator := strings.TrimSpace(c.Query("operator"))
|
||||||
transactionID := strings.TrimSpace(c.Query("transaction_id"))
|
|
||||||
|
|
||||||
// 构建查询
|
// 构建查询
|
||||||
db, ok := logBaseController.GetDB(c)
|
db, ok := logBaseController.GetDB(c)
|
||||||
@@ -72,10 +71,6 @@ func LogsListHandler(c *gin.Context) {
|
|||||||
// 支持按 UUID 或 用户名 筛选
|
// 支持按 UUID 或 用户名 筛选
|
||||||
query = query.Where("operator_uuid = ? OR operator = ?", operator, operator)
|
query = query.Where("operator_uuid = ? OR operator = ?", operator, operator)
|
||||||
}
|
}
|
||||||
if transactionID != "" {
|
|
||||||
// 优化:使用精确匹配提升查询性能
|
|
||||||
query = query.Where("transaction_id = ?", transactionID)
|
|
||||||
}
|
|
||||||
if startTimeStr != "" {
|
if startTimeStr != "" {
|
||||||
if t, err := time.ParseInLocation("2006-01-02", startTimeStr, time.Local); err == nil {
|
if t, err := time.ParseInLocation("2006-01-02", startTimeStr, time.Local); err == nil {
|
||||||
query = query.Where("created_at >= ?", t)
|
query = query.Where("created_at >= ?", t)
|
||||||
@@ -140,9 +135,6 @@ func LogsClearHandler(c *gin.Context) {
|
|||||||
OperationType: "清空日志",
|
OperationType: "清空日志",
|
||||||
Operator: operator,
|
Operator: operator,
|
||||||
OperatorUUID: "",
|
OperatorUUID: "",
|
||||||
AppName: "-",
|
|
||||||
ProductName: "-",
|
|
||||||
TransactionID: "-",
|
|
||||||
Details: "管理员清空了所有操作日志",
|
Details: "管理员清空了所有操作日志",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,20 @@ func ProfilePasswordUpdateHandler(c *gin.Context) {
|
|||||||
cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite)
|
cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite)
|
||||||
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"修改密码",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"管理员修改了登录密码",
|
||||||
|
)
|
||||||
|
|
||||||
authBaseController.HandleSuccess(c, "密码修改成功", nil)
|
authBaseController.HandleSuccess(c, "密码修改成功", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +231,24 @@ func ProfileUpdateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新缓存
|
||||||
|
settingsService.RefreshCache()
|
||||||
|
_ = utils.RedisDel(c.Request.Context(), "setting:admin_username")
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"修改账号",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"管理员修改了用户名为: "+username,
|
||||||
|
)
|
||||||
|
|
||||||
// 重新签发JWT并写入Cookie
|
// 重新签发JWT并写入Cookie
|
||||||
token, err := generateJWTTokenForAdmin(username, currentHash)
|
token, err := generateJWTTokenForAdmin(username, currentHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -228,10 +260,6 @@ func ProfileUpdateHandler(c *gin.Context) {
|
|||||||
cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite)
|
cookie := utils.CreateSecureCookie("admin_session", token, maxAge, domain, secure, sameSite)
|
||||||
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
||||||
|
|
||||||
// 刷新缓存
|
|
||||||
settingsService.RefreshCache()
|
|
||||||
_ = utils.RedisDel(c.Request.Context(), "setting:admin_username")
|
|
||||||
|
|
||||||
authBaseController.HandleSuccess(c, "用户名修改成功", map[string]interface{}{
|
authBaseController.HandleSuccess(c, "用户名修改成功", map[string]interface{}{
|
||||||
"username": username,
|
"username": username,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"NetworkAuth/controllers"
|
"NetworkAuth/controllers"
|
||||||
"NetworkAuth/models"
|
"NetworkAuth/models"
|
||||||
|
"NetworkAuth/services"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -223,6 +224,20 @@ func VariableCreateHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"创建变量",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
"创建了变量: "+variable.Alias,
|
||||||
|
)
|
||||||
|
|
||||||
variableBaseController.HandleSuccess(c, "创建成功", variable)
|
variableBaseController.HandleSuccess(c, "创建成功", variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,6 +327,10 @@ func VariableDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找变量以记录日志
|
||||||
|
var variable models.Variable
|
||||||
|
db.First(&variable, req.ID)
|
||||||
|
|
||||||
// 删除变量
|
// 删除变量
|
||||||
if err := db.Delete(&models.Variable{}, req.ID).Error; err != nil {
|
if err := db.Delete(&models.Variable{}, req.ID).Error; err != nil {
|
||||||
logrus.WithError(err).Error("Failed to delete variable")
|
logrus.WithError(err).Error("Failed to delete variable")
|
||||||
@@ -319,6 +338,25 @@ func VariableDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
details := "删除了变量ID: " + strconv.Itoa(int(req.ID))
|
||||||
|
if variable.ID != 0 {
|
||||||
|
details = "删除了变量: " + variable.Alias
|
||||||
|
}
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"删除变量",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithField("variable_id", req.ID).Debug("Successfully deleted variable")
|
logrus.WithField("variable_id", req.ID).Debug("Successfully deleted variable")
|
||||||
|
|
||||||
variableBaseController.HandleSuccess(c, "删除成功", nil)
|
variableBaseController.HandleSuccess(c, "删除成功", nil)
|
||||||
@@ -351,6 +389,26 @@ func VariablesBatchDeleteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
operator := c.GetString("admin_username")
|
||||||
|
if operator == "" {
|
||||||
|
operator = "unknown"
|
||||||
|
}
|
||||||
|
operatorUUID := c.GetString("admin_uuid")
|
||||||
|
|
||||||
|
var idStrs []string
|
||||||
|
for _, id := range req.IDs {
|
||||||
|
idStrs = append(idStrs, strconv.Itoa(int(id)))
|
||||||
|
}
|
||||||
|
details := "批量删除了变量ID: " + strings.Join(idStrs, ",")
|
||||||
|
|
||||||
|
services.RecordOperationLog(
|
||||||
|
"删除变量",
|
||||||
|
operator,
|
||||||
|
operatorUUID,
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
|
||||||
logrus.WithField("variable_ids", req.IDs).Debug("Successfully batch deleted variables")
|
logrus.WithField("variable_ids", req.IDs).Debug("Successfully batch deleted variables")
|
||||||
|
|
||||||
variableBaseController.HandleSuccess(c, "批量删除成功", nil)
|
variableBaseController.HandleSuccess(c, "批量删除成功", nil)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"NetworkAuth/utils"
|
"NetworkAuth/utils"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -23,6 +24,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
// dbInstance 全局 *gorm.DB 实例,使用单例确保全局复用
|
// dbInstance 全局 *gorm.DB 实例,使用单例确保全局复用
|
||||||
dbInstance *gorm.DB
|
dbInstance *gorm.DB
|
||||||
|
// healthCheckCancel 数据库健康检查取消函数
|
||||||
|
healthCheckCancel context.CancelFunc
|
||||||
// once 确保初始化只执行一次
|
// once 确保初始化只执行一次
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
@@ -56,6 +59,10 @@ func GetDB() (*gorm.DB, error) {
|
|||||||
func ReInit() (*gorm.DB, error) {
|
func ReInit() (*gorm.DB, error) {
|
||||||
// 如果已有连接,尝试关闭它
|
// 如果已有连接,尝试关闭它
|
||||||
if dbInstance != nil {
|
if dbInstance != nil {
|
||||||
|
if healthCheckCancel != nil {
|
||||||
|
healthCheckCancel()
|
||||||
|
healthCheckCancel = nil
|
||||||
|
}
|
||||||
if sqlDB, err := dbInstance.DB(); err == nil {
|
if sqlDB, err := dbInstance.DB(); err == nil {
|
||||||
sqlDB.Close()
|
sqlDB.Close()
|
||||||
}
|
}
|
||||||
@@ -124,7 +131,7 @@ func performInit() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 启动健康检查
|
// 启动健康检查
|
||||||
utils.StartHealthCheck(dbInstance, dbConfig)
|
healthCheckCancel = utils.StartHealthCheck(dbInstance, dbConfig)
|
||||||
}
|
}
|
||||||
return initErr
|
return initErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,16 @@ import (
|
|||||||
|
|
||||||
// OperationLog 操作日志模型
|
// OperationLog 操作日志模型
|
||||||
type OperationLog struct {
|
type OperationLog struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
CreatedAt time.Time `gorm:"index;comment:创建时间" json:"created_at"`
|
|
||||||
|
|
||||||
// 操作信息
|
// 操作信息
|
||||||
OperationType string `gorm:"type:varchar(50);index;comment:操作方式" json:"operation_type"` // 如:入库成功、凭证分配等
|
OperationType string `gorm:"type:varchar(50);index;comment:操作方式" json:"operation_type"`
|
||||||
|
|
||||||
// 操作人信息
|
// 操作人信息
|
||||||
Operator string `gorm:"type:varchar(100);index;comment:操作账号" json:"operator"`
|
Operator string `gorm:"type:varchar(100);index;comment:操作账号" json:"operator"`
|
||||||
OperatorUUID string `gorm:"type:varchar(36);index;comment:操作账号UUID" json:"operator_uuid"`
|
OperatorUUID string `gorm:"type:varchar(36);index;comment:操作账号UUID" json:"operator_uuid"`
|
||||||
|
|
||||||
// 关联对象信息 (快照,防止关联对象被删除后无法查询)
|
Details string `gorm:"type:text;comment:操作详情" json:"details"`
|
||||||
TransactionID string `gorm:"type:varchar(100);index;comment:交易ID" json:"transaction_id"`
|
|
||||||
AppName string `gorm:"type:varchar(100);comment:应用名称" json:"app_name"`
|
CreatedAt time.Time `gorm:"index;comment:创建时间" json:"created_at"`
|
||||||
ProductName string `gorm:"type:varchar(100);comment:商品名称" json:"product_name"`
|
|
||||||
Details string `gorm:"type:text;comment:操作详情" json:"details"`
|
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RecordOperationLog 记录操作日志
|
// RecordOperationLog 记录操作日志
|
||||||
func RecordOperationLog(operationType, operator, operatorUUID, transactionID, appName, productName, details string) {
|
func RecordOperationLog(operationType, operator, operatorUUID, details string) {
|
||||||
db, err := database.GetDB()
|
db, err := database.GetDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Error("获取数据库连接失败,无法记录操作日志")
|
logrus.WithError(err).Error("获取数据库连接失败,无法记录操作日志")
|
||||||
@@ -20,9 +20,6 @@ func RecordOperationLog(operationType, operator, operatorUUID, transactionID, ap
|
|||||||
OperationType: operationType,
|
OperationType: operationType,
|
||||||
Operator: operator,
|
Operator: operator,
|
||||||
OperatorUUID: operatorUUID,
|
OperatorUUID: operatorUUID,
|
||||||
TransactionID: transactionID,
|
|
||||||
AppName: appName,
|
|
||||||
ProductName: productName,
|
|
||||||
Details: details,
|
Details: details,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,22 +301,29 @@ func LogConnectionStats(db *gorm.DB) {
|
|||||||
// StartHealthCheck 启动数据库健康检查
|
// StartHealthCheck 启动数据库健康检查
|
||||||
// 启动一个后台goroutine定期检查数据库连接健康状态
|
// 启动一个后台goroutine定期检查数据库连接健康状态
|
||||||
// 只在健康检查失败时输出错误日志,正常情况下不输出日志
|
// 只在健康检查失败时输出错误日志,正常情况下不输出日志
|
||||||
func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) {
|
// 返回一个 CancelFunc 用于停止健康检查
|
||||||
|
func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) context.CancelFunc {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(config.HealthCheckInterval)
|
ticker := time.NewTicker(config.HealthCheckInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for {
|
||||||
if err := PingDatabase(db, config.PingTimeout); err != nil {
|
select {
|
||||||
// 只在健康检查失败时输出错误日志
|
case <-ctx.Done():
|
||||||
LogError("数据库健康检查失败", err, map[string]interface{}{
|
return
|
||||||
"ping_timeout": config.PingTimeout,
|
case <-ticker.C:
|
||||||
})
|
if err := PingDatabase(db, config.PingTimeout); err != nil {
|
||||||
}
|
// 只在健康检查失败时输出错误日志
|
||||||
|
LogError("数据库健康检查失败", err, map[string]interface{}{
|
||||||
|
"ping_timeout": config.PingTimeout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 记录连接池统计信息(仅在调试模式下)
|
// 记录连接池统计信息(仅在调试模式下)
|
||||||
if logrus.GetLevel() == logrus.DebugLevel {
|
if logrus.GetLevel() == logrus.DebugLevel {
|
||||||
LogConnectionStats(db)
|
LogConnectionStats(db)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -325,6 +332,8 @@ func StartHealthCheck(db *gorm.DB, config *DatabaseConfig) {
|
|||||||
// "check_interval": config.HealthCheckInterval,
|
// "check_interval": config.HealthCheckInterval,
|
||||||
// "ping_timeout": config.PingTimeout,
|
// "ping_timeout": config.PingTimeout,
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
return cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDatabaseConfig 验证数据库配置参数
|
// ValidateDatabaseConfig 验证数据库配置参数
|
||||||
|
|||||||
@@ -26,12 +26,6 @@
|
|||||||
<input type="text" name="operator" placeholder="请输入操作账号" autocomplete="off" class="layui-input">
|
<input type="text" name="operator" placeholder="请输入操作账号" autocomplete="off" class="layui-input">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
|
||||||
<label class="layui-form-label">交易ID</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<input type="text" name="transaction_id" placeholder="请输入交易ID" autocomplete="off" class="layui-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<button type="button" class="layui-btn" id="btnSearchOperationLogs">搜索</button>
|
<button type="button" class="layui-btn" id="btnSearchOperationLogs">搜索</button>
|
||||||
<button type="button" class="layui-btn layui-btn-primary" id="btnResetOperationLogs">重置</button>
|
<button type="button" class="layui-btn layui-btn-primary" id="btnResetOperationLogs">重置</button>
|
||||||
@@ -91,9 +85,6 @@ layui.use(['table', 'form', 'laydate', 'util', 'jquery'], function(){
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
limits: [10, 20, 50, 100, 200, 500, 1000],
|
limits: [10, 20, 50, 100, 200, 500, 1000],
|
||||||
cols: [[
|
cols: [[
|
||||||
{field: 'app_name', title: '应用名称', minWidth: 150},
|
|
||||||
{field: 'product_name', title: '商品名称', minWidth: 150},
|
|
||||||
{field: 'transaction_id', title: '交易ID', width: 280},
|
|
||||||
{field: 'operator', title: '操作账号', minWidth: 150},
|
{field: 'operator', title: '操作账号', minWidth: 150},
|
||||||
{field: 'operation_type', title: '操作方式', minWidth: 150},
|
{field: 'operation_type', title: '操作方式', minWidth: 150},
|
||||||
{field: 'details', title: '日志内容', minWidth: 200},
|
{field: 'details', title: '日志内容', minWidth: 200},
|
||||||
@@ -125,7 +116,6 @@ layui.use(['table', 'form', 'laydate', 'util', 'jquery'], function(){
|
|||||||
where: {
|
where: {
|
||||||
operation_type: formData.operation_type,
|
operation_type: formData.operation_type,
|
||||||
operator: formData.operator,
|
operator: formData.operator,
|
||||||
transaction_id: formData.transaction_id,
|
|
||||||
start_time: $('#operation_start_time').val(),
|
start_time: $('#operation_start_time').val(),
|
||||||
end_time: $('#operation_end_time').val()
|
end_time: $('#operation_end_time').val()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,237 +1,340 @@
|
|||||||
{{ define "profile.html" }}
|
{{ define "profile.html" }}
|
||||||
<div class="layui-card">
|
<section>
|
||||||
<div class="layui-card-header">个人资料</div>
|
<h2>账户管理</h2>
|
||||||
<div class="layui-card-body">
|
<div class="layui-tab layui-tab-brief" lay-filter="userTabs" style="margin-top: 16px;">
|
||||||
<form class="layui-form" id="accountForm" lay-filter="accountForm" onsubmit="return false">
|
<ul class="layui-tab-title">
|
||||||
<!-- 按照要求纵向排序:用户名、旧密码、新密码、确认新密码 -->
|
<li class="layui-this">修改密码</li>
|
||||||
<div class="layui-form-item">
|
<li>修改用户名</li>
|
||||||
<label class="layui-form-label">用户名</label>
|
</ul>
|
||||||
<div class="layui-input-block">
|
<div class="layui-tab-content">
|
||||||
<input type="text" name="username" placeholder="请输入用户名(不修改可留空或保持不变)" autocomplete="off" class="layui-input" />
|
<!-- 修改密码模块 -->
|
||||||
|
<div class="layui-tab-item layui-show">
|
||||||
|
<div class="layui-panel" style="margin-top: 16px;">
|
||||||
|
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">修改密码</h3>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<form class="layui-form" id="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-old-password">当前密码</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div class="layui-input-wrap">
|
||||||
|
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-new-password">新的密码</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div class="layui-input-wrap">
|
||||||
|
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">确认密码</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div class="layui-input-wrap">
|
||||||
|
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn" lay-submit lay-filter="submitPassword">
|
||||||
|
<i class="layui-icon layui-icon-ok"></i> 修改密码
|
||||||
|
</button>
|
||||||
|
<button type="button" id="resetPasswordBtn" class="layui-btn layui-btn-primary">
|
||||||
|
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="layui-form-item">
|
<!-- 修改用户名模块 -->
|
||||||
<label class="layui-form-label">旧密码</label>
|
<div class="layui-tab-item">
|
||||||
<div class="layui-input-block">
|
<div class="layui-panel" style="margin-top: 16px;">
|
||||||
<!-- 不修改密码时可留空 -->
|
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">修改用户名</h3>
|
||||||
<input type="password" name="old_password" placeholder="不修改可留空" autocomplete="off" class="layui-input">
|
<div style="padding: 20px;">
|
||||||
|
<form class="layui-form" id="usernameForm" lay-filter="usernameForm" onsubmit="return false">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">当前用户名</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="current_username" disabled readonly class="layui-input readonly-field" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-username">新用户名</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input"
|
||||||
|
lay-verify="required" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-old-password">当前密码</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<div class="layui-input-wrap">
|
||||||
|
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off"
|
||||||
|
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn" lay-submit lay-filter="submitUsername">
|
||||||
|
<i class="layui-icon layui-icon-ok"></i> 修改用户名
|
||||||
|
</button>
|
||||||
|
<button type="button" id="resetUsernameBtn" class="layui-btn layui-btn-primary">
|
||||||
|
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">新密码</label>
|
|
||||||
<div class="layui-input-block">
|
|
||||||
<!-- 不修改密码时可留空 -->
|
|
||||||
<input type="password" name="new_password" placeholder="不修改可留空(至少6位)" autocomplete="off" class="layui-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">确认密码</label>
|
|
||||||
<div class="layui-input-block">
|
|
||||||
<!-- 不修改密码时可留空 -->
|
|
||||||
<input type="password" name="confirm_password" placeholder="不修改可留空" autocomplete="off" class="layui-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<div class="layui-input-block">
|
|
||||||
<button class="layui-btn" lay-submit lay-filter="submitAccount">保存更改</button>
|
|
||||||
<!-- 将原先 type="reset" 改为自定义按钮,避免浏览器重置成初始空值 -->
|
|
||||||
<button type="button" id="btnReset" class="layui-btn layui-btn-primary">重置</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||||
(() => {
|
(() => {
|
||||||
// 等待layui加载完成
|
// 工具方法:将数值角色转为中文标签
|
||||||
function waitForLayui(callback) {
|
const roleToText = (role) => {
|
||||||
if (typeof layui !== 'undefined') {
|
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||||
callback();
|
return r === 0 ? '管理员' : '子账号'
|
||||||
} else {
|
|
||||||
setTimeout(() => waitForLayui(callback), 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForLayui(() => {
|
|
||||||
layui.use(['form', 'layer'], () => {
|
|
||||||
const form = layui.form
|
|
||||||
const layer = layui.layer
|
|
||||||
|
|
||||||
// 记录初始用户名,用于判断是否需要更新
|
|
||||||
let initialUsername = ''
|
|
||||||
// 缓存最近一次加载到表单中的资料,用于“重置”恢复
|
|
||||||
let lastProfile = null
|
|
||||||
|
|
||||||
// 加载个人资料:填充用户名
|
|
||||||
// 返回:无;副作用:设置 initialUsername、lastProfile 与表单值
|
|
||||||
const loadProfile = async () => {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/admin/api/profile/info', {
|
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
const ok = (data.success === true) || (data.code === 0)
|
|
||||||
if (!ok) throw new Error(data.message || data.msg || '加载失败')
|
|
||||||
const payload = data.data || {}
|
|
||||||
initialUsername = payload.username || ''
|
|
||||||
|
|
||||||
const display = { ...payload }
|
|
||||||
lastProfile = display
|
|
||||||
form.val('accountForm', display)
|
|
||||||
} catch (e) {
|
|
||||||
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验密码表单:当任一密码字段填写时,要求三个字段均填写且有效
|
// 格式化时间
|
||||||
// 返回:{ ok: boolean, msg?: string }
|
const formatTime = (timeStr) => {
|
||||||
const validatePassword = (fields) => {
|
if (!timeStr) return ''
|
||||||
const oldPwd = (fields.old_password || '').trim()
|
const date = new Date(timeStr)
|
||||||
const newPwd = (fields.new_password || '').trim()
|
return date.toLocaleString('zh-CN')
|
||||||
const confirmPwd = (fields.confirm_password || '').trim()
|
|
||||||
const anyFilled = !!(oldPwd || newPwd || confirmPwd)
|
|
||||||
if (!anyFilled) return { ok: true }
|
|
||||||
if (!oldPwd || !newPwd || !confirmPwd) return { ok: false, msg: '请完整填写旧密码/新密码/确认新密码' }
|
|
||||||
if (newPwd.length < 6) return { ok: false, msg: '新密码长度不能少于6位' }
|
|
||||||
if (newPwd !== confirmPwd) return { ok: false, msg: '两次输入的新密码不一致' }
|
|
||||||
if (oldPwd === newPwd) return { ok: false, msg: '新密码不能与旧密码相同' }
|
|
||||||
return { ok: true }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新用户名:传输 username 与 old_password(当仅修改用户名时必须提供当前密码;同时修改密码时沿用同一 old_password)
|
// 如果未加载 layui,则按需加载
|
||||||
// 返回:Promise<void>
|
const ensureLayui = () => new Promise((resolve) => {
|
||||||
const updateUsername = async (username, oldPassword) => {
|
if (window.layui) return resolve(window.layui)
|
||||||
const payload = { username }
|
const css = document.createElement('link')
|
||||||
if (oldPassword) payload.old_password = oldPassword
|
css.rel = 'stylesheet'
|
||||||
const res = await fetch('/admin/api/profile/update', {
|
css.href = 'https://unpkg.com/layui@2.10.1/dist/css/layui.css'
|
||||||
method: 'POST',
|
document.head.appendChild(css)
|
||||||
headers: {
|
const script = document.createElement('script')
|
||||||
'Content-Type': 'application/json',
|
script.src = 'https://unpkg.com/layui@2.10.1/dist/layui.js'
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
script.onload = () => resolve(window.layui)
|
||||||
},
|
document.head.appendChild(script)
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
const ok = (data.success === true) || (data.code === 0)
|
|
||||||
if (!ok) throw new Error(data.message || data.msg || '保存资料失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新密码:仅传输旧/新/确认三个字段
|
|
||||||
// 返回:Promise<any> 后端响应数据,用于可能的重定向处理
|
|
||||||
const updatePassword = async (fields) => {
|
|
||||||
const payload = {
|
|
||||||
old_password: fields.old_password,
|
|
||||||
new_password: fields.new_password,
|
|
||||||
confirm_password: fields.confirm_password
|
|
||||||
}
|
|
||||||
const res = await fetch('/admin/api/profile/password', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
const ok = (data.success === true) || (data.code === 0)
|
|
||||||
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交综合更新:
|
|
||||||
// 规则:
|
|
||||||
// - 用户名:仅当与 initialUsername 不同且非空时更新
|
|
||||||
// - 密码:当任一密码字段填写时,要求完整校验并更新;若均未填则不更新
|
|
||||||
// - 若两者均无改动,则提示“未修改任何内容”
|
|
||||||
form.on('submit(submitAccount)', async (obj) => {
|
|
||||||
const fields = obj.field
|
|
||||||
const desiredUsername = (fields.username || '').trim()
|
|
||||||
const needUpdateUsername = desiredUsername && desiredUsername !== initialUsername
|
|
||||||
|
|
||||||
// 判定密码相关输入:
|
|
||||||
// - wantChangePassword:输入了新密码或确认密码,视为尝试修改密码(将要求三个字段都填写)
|
|
||||||
// - onlyOldProvided:仅输入了旧密码,用于支持“仅修改用户名需要当前密码”的场景
|
|
||||||
const hasOld = !!(fields.old_password && fields.old_password.trim())
|
|
||||||
const hasNewOrConfirm = !!((fields.new_password && fields.new_password.trim()) || (fields.confirm_password && fields.confirm_password.trim()))
|
|
||||||
const wantChangePassword = hasNewOrConfirm
|
|
||||||
const onlyOldProvided = hasOld && !hasNewOrConfirm
|
|
||||||
|
|
||||||
if (!needUpdateUsername && !wantChangePassword) {
|
|
||||||
layer.msg('未修改任何内容', { icon: 0 })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改密码场景:需进行严格校验(旧/新/确认均必填)
|
|
||||||
if (wantChangePassword) {
|
|
||||||
const pwdCheck = validatePassword(fields)
|
|
||||||
if (!pwdCheck.ok) {
|
|
||||||
layer.msg(pwdCheck.msg, { icon: 2 })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仅修改用户名:要求输入当前密码
|
|
||||||
if (needUpdateUsername && !wantChangePassword && !hasOld) {
|
|
||||||
layer.msg('修改用户名需要输入当前密码', { icon: 2 })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 始终先更新用户名,再更新密码(避免改密后跳转导致无法继续)
|
|
||||||
if (needUpdateUsername) {
|
|
||||||
await updateUsername(desiredUsername, hasOld ? fields.old_password : '')
|
|
||||||
initialUsername = desiredUsername
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantChangePassword) {
|
|
||||||
await updatePassword(fields)
|
|
||||||
layer.msg('密码修改成功', { icon: 1 })
|
|
||||||
// 清空密码框
|
|
||||||
form.val('accountForm', {
|
|
||||||
old_password: '',
|
|
||||||
new_password: '',
|
|
||||||
confirm_password: ''
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 未修改密码,仅修改资料
|
|
||||||
await loadProfile()
|
|
||||||
layer.msg('保存成功', { icon: 1 })
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
layer.msg(e.message || '保存失败', { icon: 2 })
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 绑定“重置”按钮:将表单恢复为最近一次加载到表单中的资料
|
// 在确保 Layui 可用后再执行页面逻辑
|
||||||
// 逻辑:
|
ensureLayui().then(() => {
|
||||||
// - 如有 lastProfile,直接回填;
|
layui.use(['form', 'layer', 'element'], () => {
|
||||||
// - 回填时同时清空三个密码字段;
|
const form = layui.form
|
||||||
// - 如暂无缓存(极小概率),则重新请求资料
|
const layer = layui.layer
|
||||||
const bindReset = () => {
|
const element = layui.element
|
||||||
const btn = document.getElementById('btnReset')
|
|
||||||
if (!btn) return
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
if (lastProfile) {
|
|
||||||
form.val('accountForm', { ...lastProfile, old_password: '', new_password: '', confirm_password: '' })
|
|
||||||
layer.msg('已恢复为当前资料', { icon: 1 })
|
|
||||||
} else {
|
|
||||||
loadProfile()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化加载
|
// 全局变量
|
||||||
bindReset()
|
let currentUsername = null
|
||||||
loadProfile()
|
|
||||||
})
|
// 获取当前用户名
|
||||||
})
|
const getCurrentUsername = async () => {
|
||||||
})()
|
try {
|
||||||
</script>
|
const res = await fetch('/admin/api/profile/info')
|
||||||
|
let data
|
||||||
|
try {
|
||||||
|
const text = await res.text()
|
||||||
|
data = JSON.parse(text)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('服务器响应格式错误')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = (data.success === true) || (data.code === 0)
|
||||||
|
if (!ok) throw new Error(data.message || data.msg || '获取用户信息失败')
|
||||||
|
|
||||||
|
currentUsername = data.data.username
|
||||||
|
// 填充用户名修改表单的当前用户名
|
||||||
|
form.val('usernameForm', { current_username: currentUsername })
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
layer.msg(e.message || '获取用户信息失败', { icon: 2 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改密码模块
|
||||||
|
const PasswordModule = {
|
||||||
|
validate: (fields) => {
|
||||||
|
const { old_password, new_password, confirm_password } = fields
|
||||||
|
|
||||||
|
if (!old_password || !new_password || !confirm_password) {
|
||||||
|
return { ok: false, msg: '请填写完整的密码信息' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_password.length < 6) {
|
||||||
|
return { ok: false, msg: '新密码长度不能少于6位' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_password !== confirm_password) {
|
||||||
|
return { ok: false, msg: '两次输入的新密码不一致' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_password === new_password) {
|
||||||
|
return { ok: false, msg: '新密码不能与当前密码相同' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: async (fields) => {
|
||||||
|
const validation = PasswordModule.validate(fields)
|
||||||
|
if (!validation.ok) {
|
||||||
|
layer.msg(validation.msg, { icon: 2 })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/admin/api/profile/password', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
old_password: fields.old_password,
|
||||||
|
new_password: fields.new_password,
|
||||||
|
confirm_password: fields.confirm_password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let data
|
||||||
|
try {
|
||||||
|
const text = await res.text()
|
||||||
|
data = JSON.parse(text)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('服务器响应格式错误')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = (data.success === true) || (data.code === 0)
|
||||||
|
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
||||||
|
|
||||||
|
// 检查是否需要跳转
|
||||||
|
if (data.data?.redirect) {
|
||||||
|
layer.msg('密码修改成功,即将跳转到登录页', { icon: 1, time: 1500 }, () => {
|
||||||
|
window.location.href = data.data.redirect
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 密码修改成功,不跳转,重置表单
|
||||||
|
layer.msg('密码修改成功', { icon: 1 })
|
||||||
|
document.getElementById('passwordForm').reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
layer.msg(e.message || '修改密码失败', { icon: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: () => {
|
||||||
|
document.getElementById('passwordForm').reset()
|
||||||
|
layer.msg('表单已重置', { icon: 1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改用户名模块
|
||||||
|
const UsernameModule = {
|
||||||
|
validate: (fields) => {
|
||||||
|
const { new_username, password } = fields
|
||||||
|
|
||||||
|
if (!new_username || !password) {
|
||||||
|
return { ok: false, msg: '请填写新用户名和当前密码' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_username === currentUsername) {
|
||||||
|
return { ok: false, msg: '新用户名不能与当前用户名相同' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_username.length < 3) {
|
||||||
|
return { ok: false, msg: '用户名长度不能少于3位' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: async (fields) => {
|
||||||
|
const validation = UsernameModule.validate(fields)
|
||||||
|
if (!validation.ok) {
|
||||||
|
layer.msg(validation.msg, { icon: 2 })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/admin/api/profile/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: fields.new_username,
|
||||||
|
old_password: fields.password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let data
|
||||||
|
try {
|
||||||
|
const text = await res.text()
|
||||||
|
data = JSON.parse(text)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('服务器响应格式错误')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = (data.success === true) || (data.code === 0)
|
||||||
|
if (!ok) throw new Error(data.message || data.msg || '修改用户名失败')
|
||||||
|
|
||||||
|
layer.msg('用户名修改成功', { icon: 1 })
|
||||||
|
|
||||||
|
// 重新获取当前用户名
|
||||||
|
await getCurrentUsername()
|
||||||
|
|
||||||
|
// 清空表单(不显示重置提示)
|
||||||
|
form.val('usernameForm', {
|
||||||
|
new_username: '',
|
||||||
|
password: '',
|
||||||
|
current_username: currentUsername || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: () => {
|
||||||
|
form.val('usernameForm', {
|
||||||
|
new_username: '',
|
||||||
|
password: '',
|
||||||
|
current_username: currentUsername || ''
|
||||||
|
})
|
||||||
|
layer.msg('表单已重置', { icon: 1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定表单提交事件
|
||||||
|
form.on('submit(submitPassword)', (obj) => {
|
||||||
|
return PasswordModule.submit(obj.field)
|
||||||
|
})
|
||||||
|
|
||||||
|
form.on('submit(submitUsername)', (obj) => {
|
||||||
|
return UsernameModule.submit(obj.field)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 绑定重置按钮
|
||||||
|
document.getElementById('resetPasswordBtn')?.addEventListener('click', PasswordModule.reset)
|
||||||
|
document.getElementById('resetUsernameBtn')?.addEventListener('click', UsernameModule.reset)
|
||||||
|
|
||||||
|
// 初始化加载
|
||||||
|
getCurrentUsername()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
</section>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
Reference in New Issue
Block a user