Use the gin framework

This commit is contained in:
2025-10-26 14:48:02 +08:00
parent 9e0eb1497b
commit d844403505
29 changed files with 1612 additions and 1858 deletions

View File

@@ -2,57 +2,52 @@ package admin
import (
"encoding/hex"
"encoding/json"
"net/http"
"networkDev/database"
"networkDev/controllers"
"networkDev/models"
"networkDev/utils"
"networkDev/utils/encrypt"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// 创建基础控制器实例
var apiBaseController = controllers.NewBaseController()
// APIFragmentHandler 接口列表页面片段处理器
func APIFragmentHandler(w http.ResponseWriter, r *http.Request) {
utils.RenderTemplate(w, "apis.html", map[string]interface{}{
func APIFragmentHandler(c *gin.Context) {
c.HTML(http.StatusOK, "apis.html", gin.H{
"Title": "接口管理",
})
}
// APIListHandler 接口列表API处理器
func APIListHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func APIListHandler(c *gin.Context) {
// 获取分页参数
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
page, _ := strconv.Atoi(c.Query("page"))
if page <= 0 {
page = 1
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
limit, _ := strconv.Atoi(c.Query("limit"))
if limit <= 0 {
limit = 10
}
// 获取应用UUID参数用于按应用筛选接口
appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid"))
appUUID := strings.TrimSpace(c.Query("app_uuid"))
// 获取接口类型参数(用于按接口类型筛选)
apiTypeStr := strings.TrimSpace(r.URL.Query().Get("api_type"))
apiTypeStr := strings.TrimSpace(c.Query("api_type"))
var apiType int
if apiTypeStr != "" {
apiType, _ = strconv.Atoi(apiTypeStr)
}
// 构建查询
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "Internal server error", http.StatusInternalServerError)
db, ok := apiBaseController.GetDB(c)
if !ok {
return
}
@@ -73,7 +68,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
var total int64
if err := query.Count(&total).Error; err != nil {
logrus.WithError(err).Error("Failed to count APIs")
http.Error(w, "Internal server error", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "获取接口总数失败", err)
return
}
@@ -82,7 +77,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
offset := (page - 1) * limit
if err := query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&apis).Error; err != nil {
logrus.WithError(err).Error("Failed to fetch APIs")
http.Error(w, "Internal server error", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "获取接口列表失败", err)
return
}
@@ -133,9 +128,9 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
// 计算分页信息
totalPages := (total + int64(limit) - 1) / int64(limit)
response := map[string]interface{}{
response := gin.H{
"success": true,
"data": map[string]interface{}{
"data": gin.H{
"apis": responseAPIs,
"total": total,
"page": page,
@@ -144,8 +139,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) {
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
c.JSON(http.StatusOK, response)
}
// getAPIStatusName 获取API状态名称
@@ -161,12 +155,7 @@ func getAPIStatusName(status int) string {
}
// APIUpdateHandler 更新接口处理器
func APIUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func APIUpdateHandler(c *gin.Context) {
var req struct {
UUID string `json:"uuid"`
Status int `json:"status"`
@@ -178,39 +167,36 @@ func APIUpdateHandler(w http.ResponseWriter, r *http.Request) {
ReturnPrivateKey string `json:"return_private_key"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !apiBaseController.BindJSON(c, &req) {
return
}
// 验证必填字段
if strings.TrimSpace(req.UUID) == "" {
http.Error(w, "接口UUID不能为空", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "接口UUID不能为空")
return
}
if req.Status != 0 && req.Status != 1 {
http.Error(w, "无效的状态值", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "无效的状态值")
return
}
if !models.IsValidAlgorithm(req.SubmitAlgorithm) || !models.IsValidAlgorithm(req.ReturnAlgorithm) {
http.Error(w, "无效的算法类型", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "无效的算法类型")
return
}
// 获取数据库连接
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "Internal server error", http.StatusInternalServerError)
db, ok := apiBaseController.GetDB(c)
if !ok {
return
}
// 查找并更新API记录
var api models.API
if err := db.Where("uuid = ?", strings.TrimSpace(req.UUID)).First(&api).Error; err != nil {
http.Error(w, "接口不存在", http.StatusNotFound)
apiBaseController.HandleValidationError(c, "接口不存在")
return
}
@@ -231,32 +217,18 @@ func APIUpdateHandler(w http.ResponseWriter, r *http.Request) {
if err := db.Save(&api).Error; err != nil {
logrus.WithError(err).Error("Failed to update API")
http.Error(w, "更新接口失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "更新接口失败", err)
return
}
response := map[string]interface{}{
"success": true,
"message": "接口更新成功",
"data": api,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
apiBaseController.HandleSuccess(c, "接口更新成功", api)
}
// APIGetAppsHandler 获取应用列表(用于接口页面的应用选择器)
func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func APIGetAppsHandler(c *gin.Context) {
// 获取数据库连接
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "Internal server error", http.StatusInternalServerError)
db, ok := apiBaseController.GetDB(c)
if !ok {
return
}
@@ -264,26 +236,15 @@ func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) {
var apps []models.App
if err := db.Select("uuid, name").Order("created_at ASC").Find(&apps).Error; err != nil {
logrus.WithError(err).Error("Failed to fetch apps")
http.Error(w, "Internal server error", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "获取应用列表失败", err)
return
}
response := map[string]interface{}{
"success": true,
"data": apps,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
apiBaseController.HandleSuccess(c, "获取应用列表成功", apps)
}
// APIGetTypesHandler 获取接口类型列表API处理器
func APIGetTypesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func APIGetTypesHandler(c *gin.Context) {
// 构建接口类型列表
type APITypeItem struct {
Value int `json:"value"`
@@ -310,35 +271,25 @@ func APIGetTypesHandler(w http.ResponseWriter, r *http.Request) {
})
}
response := map[string]interface{}{
"success": true,
"data": apiTypes,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes)
}
func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func APIGenerateKeysHandler(c *gin.Context) {
var req struct {
Side string `json:"side"` // submit | return
Algorithm int `json:"algorithm"` // 与 models.Algorithm* 对应
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !apiBaseController.BindJSON(c, &req) {
return
}
if req.Side != "submit" && req.Side != "return" {
http.Error(w, "side参数必须为submit或return", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "side参数必须为submit或return")
return
}
if !models.IsValidAlgorithm(req.Algorithm) {
http.Error(w, "无效的算法类型", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "无效的算法类型")
return
}
@@ -355,7 +306,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
key, err := encrypt.GenerateRC4Key(8) // 生成8字节密钥
if err != nil {
logrus.WithError(err).Error("Failed to generate RC4 key")
http.Error(w, "生成RC4密钥失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "生成RC4密钥失败", err)
return
}
result["public_key"] = ""
@@ -365,7 +316,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
publicKey, privateKey, err := encrypt.GenerateRSAKeyPair(2048)
if err != nil {
logrus.WithError(err).Error("Failed to generate RSA key pair")
http.Error(w, "生成RSA密钥失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "生成RSA密钥失败", err)
return
}
@@ -373,14 +324,14 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
publicKeyPEM, err := encrypt.PublicKeyToPEM(publicKey)
if err != nil {
logrus.WithError(err).Error("Failed to convert public key to PEM")
http.Error(w, "转换公钥格式失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "转换公钥格式失败", err)
return
}
privateKeyPEM, err := encrypt.PrivateKeyToPEM(privateKey)
if err != nil {
logrus.WithError(err).Error("Failed to convert private key to PEM")
http.Error(w, "转换私钥格式失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "转换私钥格式失败", err)
return
}
@@ -391,7 +342,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
publicKeyPEM, privateKeyPEM, err := encrypt.GenerateRSADynamicKeyPair(2048)
if err != nil {
logrus.WithError(err).Error("Failed to generate RSA dynamic key pair")
http.Error(w, "生成RSA动态密钥失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "生成RSA动态密钥失败", err)
return
}
@@ -402,21 +353,15 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) {
encryptKey, _, err := encrypt.GenerateEasyKey()
if err != nil {
logrus.WithError(err).Error("Failed to generate Easy encryption key")
http.Error(w, "生成易加密密钥失败", http.StatusInternalServerError)
apiBaseController.HandleInternalError(c, "生成易加密密钥失败", err)
return
}
result["public_key"] = ""
result["private_key"] = encrypt.FormatKeyAsString(encryptKey)
default:
http.Error(w, "不支持的算法类型", http.StatusBadRequest)
apiBaseController.HandleValidationError(c, "不支持的算法类型")
return
}
response := map[string]interface{}{
"success": true,
"message": "生成成功",
"data": result,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
apiBaseController.HandleSuccess(c, "生成成功", result)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,58 @@
package admin
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"networkDev/controllers"
"networkDev/database"
"networkDev/models"
"networkDev/utils"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/viper"
)
// 创建BaseController实例
var authBaseController = controllers.NewBaseController()
// LoginPageHandler 管理员登录页渲染处理器
// - 如果已登录则重定向到 /admin
// - 否则渲染 web/template/admin/login.html 模板
// - 自动清理失效的JWT Cookie避免刷新时的问题
func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
func LoginPageHandler(c *gin.Context) {
// 使用带清理功能的JWT校验避免失效Cookie在登录页面造成问题
if IsAdminAuthenticatedWithCleanup(w, r) {
http.Redirect(w, r, "/admin", http.StatusFound)
if IsAdminAuthenticatedWithCleanup(c) {
c.Redirect(http.StatusFound, "/admin")
return
}
// 获取或生成CSRF令牌
var token string
if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" {
if existingToken := utils.GetCSRFTokenFromCookie(c); existingToken != "" {
// 重用现有的Cookie令牌
token = existingToken
} else {
// 生成新的CSRF令牌并设置到Cookie
newToken, err := utils.GenerateCSRFToken()
if err != nil {
http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError)
c.HTML(http.StatusInternalServerError, "error.html", gin.H{
"Error": "生成CSRF令牌失败",
})
return
}
token = newToken
utils.SetCSRFToken(w, token)
utils.SetCSRFToken(c, token)
}
// 准备模板数据
extraData := map[string]interface{}{
extraData := gin.H{
"Title": "管理员登录",
}
data := utils.GetDefaultTemplateData()
data := authBaseController.GetDefaultTemplateData()
data["CSRFToken"] = token
// 合并额外数据
@@ -54,7 +60,7 @@ func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
data[key] = value
}
utils.RenderTemplate(w, "login.html", data)
c.HTML(http.StatusOK, "login.html", data)
}
// LoginHandler 管理员登录接口
@@ -62,46 +68,41 @@ func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
// - 验证用户存在与密码正确性
// - 仅允许 Role=0 的管理员登录
// - 成功后设置简单的会话Cookie后续可切换为JWT或更完善的Session
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
func LoginHandler(c *gin.Context) {
var body struct {
Username string `json:"username"`
Password string `json:"password"`
Captcha string `json:"captcha"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil)
if !authBaseController.BindJSON(c, &body) {
return
}
if body.Username == "" || body.Password == "" {
utils.JsonResponse(w, http.StatusBadRequest, false, "用户名和密码不能为空", nil)
return
}
if body.Captcha == "" {
utils.JsonResponse(w, http.StatusBadRequest, false, "验证码不能为空", nil)
if !authBaseController.ValidateRequired(c, map[string]interface{}{
"用户名": body.Username,
"密码": body.Password,
"验证码": body.Captcha,
}) {
return
}
// 验证验证码
if !VerifyCaptcha(r, body.Captcha) {
utils.JsonResponse(w, http.StatusBadRequest, false, "验证码错误", nil)
if !VerifyCaptcha(c, body.Captcha) {
authBaseController.HandleValidationError(c, "验证码错误")
return
}
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
// 获取数据库连接
db, ok := authBaseController.GetDB(c)
if !ok {
return
}
// 通过前缀匹配一次性获取所有管理员相关设置
var adminSettings []models.Settings
if err = db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil {
utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil)
if err := db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil {
authBaseController.HandleValidationError(c, "用户不存在或密码错误")
return
}
@@ -117,25 +118,25 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
adminPasswordSalt, hasSalt := settingsMap["admin_password_salt"]
if !hasUsername || !hasPassword || !hasSalt {
utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil)
authBaseController.HandleValidationError(c, "用户不存在或密码错误")
return
}
// 验证用户名
if body.Username != adminUsername {
utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil)
authBaseController.HandleValidationError(c, "用户不存在或密码错误")
return
}
// 验证密码为空的情况(首次登录需要初始化)
if adminPassword == "" || adminPasswordSalt == "" {
utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员账号未初始化,请联系系统管理员", nil)
authBaseController.HandleInternalError(c, "管理员账号未初始化,请联系系统管理员", nil)
return
}
// 使用盐值验证密码
if !utils.VerifyPasswordWithSalt(body.Password, adminPasswordSalt, adminPassword) {
utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil)
authBaseController.HandleValidationError(c, "用户不存在或密码错误")
return
}
@@ -149,30 +150,30 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
// 生成JWT令牌
token, err := generateJWTTokenForAdmin(adminUser)
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成令牌失败", nil)
authBaseController.HandleInternalError(c, "生成令牌失败", err)
return
}
// 设置JWT Cookie使用安全配置
cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge())
http.SetCookie(w, cookie)
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
utils.JsonResponse(w, http.StatusOK, true, "登录成功", map[string]interface{}{
authBaseController.HandleSuccess(c, "登录成功", gin.H{
"redirect": "/admin",
})
}
// LogoutHandler 管理员登出
// - 清理JWT Cookie会话
// - 清理JWT Cookie
// - 确保令牌完全失效
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
func LogoutHandler(c *gin.Context) {
// 清理JWT Cookie
clearInvalidJWTCookie(w)
clearInvalidJWTCookie(c)
// 可选将JWT令牌加入黑名单需要Redis或数据库支持
// 这里可以实现JWT黑名单机制
utils.JsonResponse(w, http.StatusOK, true, "已退出登录", map[string]interface{}{
authBaseController.HandleSuccess(c, "已退出登录", gin.H{
"redirect": "/admin/login",
})
}
@@ -180,9 +181,9 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
// clearInvalidJWTCookie 清理失效的JWT Cookie
// - 统一的Cookie清理函数确保一致性
// - 在JWT校验失败时自动调用提升安全性和用户体验
func clearInvalidJWTCookie(w http.ResponseWriter) {
func clearInvalidJWTCookie(c *gin.Context) {
cookie := utils.CreateExpiredCookie("admin_session")
http.SetCookie(w, cookie)
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
}
// getJWTSecret 动态获取当前的JWT密钥
@@ -246,18 +247,18 @@ func parseJWTToken(tokenString string) (*JWTClaims, error) {
}
// getJWTCookie 获取JWT cookie的通用函数
func getJWTCookie(r *http.Request) (*http.Cookie, error) {
return r.Cookie("admin_session")
func getJWTCookie(c *gin.Context) (string, error) {
return c.Cookie("admin_session")
}
// validateAdminPasswordHash 验证管理员密码哈希的通用函数
func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool {
func validateAdminPasswordHash(claims *JWTClaims, c *gin.Context) bool {
// 【安全修复】验证数据库中的当前密码哈希
// 这确保了密码修改后旧的JWT令牌会失效
db, err := database.GetDB()
if err != nil {
fmt.Printf("[SECURITY WARNING] Database connection failed during auth - Username=%s, IP=%s\n",
claims.Username, r.RemoteAddr)
claims.Username, c.ClientIP())
return false
}
@@ -265,7 +266,7 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool {
var adminPassword models.Settings
if err := db.Where("name = ?", "admin_password").First(&adminPassword).Error; err != nil {
fmt.Printf("[SECURITY WARNING] Admin password not found in database - Username=%s, IP=%s\n",
claims.Username, r.RemoteAddr)
claims.Username, c.ClientIP())
return false
}
@@ -275,7 +276,7 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool {
// 验证JWT中的密码哈希是否与当前数据库中的密码哈希一致
if claims.PasswordHash != currentPasswordHash {
fmt.Printf("[SECURITY WARNING] Password hash mismatch - JWT token invalidated - Username=%s, IP=%s\n",
claims.Username, r.RemoteAddr)
claims.Username, c.ClientIP())
return false
}
@@ -285,14 +286,14 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool {
// IsAdminAuthenticated 判断管理员是否已认证(导出)
// - 检查admin_session Cookie中的JWT令牌
// - 验证令牌签名、过期时间和用户角色
func IsAdminAuthenticated(r *http.Request) bool {
cookie, err := getJWTCookie(r)
if err != nil || cookie.Value == "" {
func IsAdminAuthenticated(c *gin.Context) bool {
cookie, err := getJWTCookie(c)
if err != nil || cookie == "" {
return false
}
// 解析并验证JWT令牌
claims, err := parseJWTToken(cookie.Value)
claims, err := parseJWTToken(cookie)
if err != nil {
return false
}
@@ -300,31 +301,31 @@ func IsAdminAuthenticated(r *http.Request) bool {
// 注释:由于这是管理员专用认证函数,不需要额外的角色验证
// 验证密码哈希
return validateAdminPasswordHash(claims, r)
return validateAdminPasswordHash(claims, c)
}
// IsAdminAuthenticatedWithCleanup 带自动清理功能的JWT校验函数
// - 当JWT校验失败时自动清理失效的Cookie
// - 适用于API接口等需要清理失效令牌的场景
func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) bool {
cookie, err := getJWTCookie(r)
if err != nil || cookie.Value == "" {
func IsAdminAuthenticatedWithCleanup(c *gin.Context) bool {
cookie, err := getJWTCookie(c)
if err != nil || cookie == "" {
return false
}
// 解析并验证JWT令牌
claims, err := parseJWTToken(cookie.Value)
claims, err := parseJWTToken(cookie)
if err != nil {
// JWT解析失败清理失效Cookie
clearInvalidJWTCookie(w)
clearInvalidJWTCookie(c)
return false
}
// 注释:由于这是管理员专用认证函数,不需要额外的角色验证
// 验证密码哈希
if !validateAdminPasswordHash(claims, r) {
clearInvalidJWTCookie(w)
if !validateAdminPasswordHash(claims, c) {
clearInvalidJWTCookie(c)
return false
}
@@ -335,13 +336,13 @@ func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) boo
// - 从JWT令牌中提取用户信息
// - 自动刷新接近过期的令牌剩余时间少于6小时时刷新
// - 返回用户ID、用户名和角色
func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) {
cookie, err := getJWTCookie(r)
func GetCurrentAdminUser(c *gin.Context) (*JWTClaims, error) {
cookie, err := getJWTCookie(c)
if err != nil {
return nil, fmt.Errorf("未找到会话信息")
}
claims, err := parseJWTToken(cookie.Value)
claims, err := parseJWTToken(cookie)
if err != nil {
return nil, fmt.Errorf("无效的会话信息")
}
@@ -355,40 +356,34 @@ func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) {
// - 从JWT令牌中提取用户信息
// - 自动刷新接近过期的令牌剩余时间少于6小时时刷新
// - 返回用户ID、用户名、角色和是否刷新了令牌
func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JWTClaims, bool, error) {
cookie, err := getJWTCookie(r)
func GetCurrentAdminUserWithRefresh(c *gin.Context) (*JWTClaims, bool, error) {
cookie, err := getJWTCookie(c)
if err != nil {
return nil, false, fmt.Errorf("未找到会话信息")
}
claims, err := parseJWTToken(cookie.Value)
claims, err := parseJWTToken(cookie)
if err != nil {
return nil, false, fmt.Errorf("无效的会话信息")
}
// 注释:由于这是管理员专用函数,不需要额外的角色验证
// 验证密码哈希
if !validateAdminPasswordHash(claims, r) {
if !validateAdminPasswordHash(claims, c) {
return nil, false, fmt.Errorf("会话已失效,请重新登录")
}
// 检查是否需要刷新令牌(根据配置的阈值)
// 检查是否需要刷新令牌
refreshed := false
refreshThreshold := time.Duration(viper.GetInt("security.jwt_refresh")) * time.Hour
if time.Until(claims.ExpiresAt.Time) < refreshThreshold {
// 为管理员生成新的JWT令牌
adminUser := models.User{
Username: claims.Username,
}
newToken, err := generateJWTTokenForAdmin(adminUser)
if err == nil {
// 更新Cookie使用安全配置
newCookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge())
http.SetCookie(w, newCookie)
c.SetCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge(), "/", "", false, true)
refreshed = true
// 更新claims的过期时间
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(24 * time.Hour))
claims.IssuedAt = jwt.NewNumericDate(time.Now())
}
@@ -400,24 +395,30 @@ func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JW
// AdminAuthRequired 管理员认证拦截中间件
// - 未登录:重定向到 /admin/login
// - 已登录:自动刷新接近过期的令牌,然后放行到后续处理器
func AdminAuthRequired(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
func AdminAuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// 尝试获取用户信息并自动刷新令牌
claims, refreshed, err := GetCurrentAdminUserWithRefresh(w, r)
claims, refreshed, err := GetCurrentAdminUserWithRefresh(c)
if err != nil {
// 自动清理失效的JWT Cookie提升安全性和用户体验
clearInvalidJWTCookie(w)
clearInvalidJWTCookie(c)
// 中文注释区分普通页面请求与AJAX/JSON请求
// - 对 AJAX/JSON直接返回 401 JSON便于前端处理如提示重新登录
// - 对普通页面:保持原有重定向到登录页
accept := r.Header.Get("Accept")
xrw := strings.ToLower(strings.TrimSpace(r.Header.Get("X-Requested-With")))
accept := c.GetHeader("Accept")
xrw := strings.ToLower(strings.TrimSpace(c.GetHeader("X-Requested-With")))
if strings.Contains(accept, "application/json") || xrw == "xmlhttprequest" {
utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil)
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录或会话已过期",
"data": nil,
})
c.Abort()
return
}
http.Redirect(w, r, "/admin/login", http.StatusFound)
c.Redirect(http.StatusFound, "/admin/login")
c.Abort()
return
}
@@ -427,6 +428,6 @@ func AdminAuthRequired(next http.HandlerFunc) http.HandlerFunc {
_ = claims // 避免未使用变量警告
}
next(w, r)
c.Next()
}
}

View File

@@ -3,17 +3,21 @@ package admin
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"math/big"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"networkDev/controllers"
"networkDev/utils"
"github.com/mojocn/base64Captcha"
"github.com/spf13/viper"
)
// 创建基础控制器实例
var captchaBaseController = controllers.NewBaseController()
// 全局验证码存储器
var store = base64Captcha.DefaultMemStore
@@ -28,17 +32,12 @@ func secureRandomInt(max int) (int, error) {
// CaptchaHandler 生成验证码图片
// GET /admin/captcha - 返回验证码图片
func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
func CaptchaHandler(c *gin.Context) {
// 随机生成4-6位长度
// 使用crypto/rand生成安全的随机数
randomNum, err := secureRandomInt(3)
if err != nil {
http.Error(w, "生成随机数失败", http.StatusInternalServerError)
captchaBaseController.HandleInternalError(c, "生成随机数失败", err)
return
}
captchaLength := 4 + randomNum // 4-6位随机长度
@@ -58,20 +57,20 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
captcha := base64Captcha.NewCaptcha(&driver, store)
id, b64s, _, err := captcha.Generate()
if err != nil {
http.Error(w, "生成验证码失败", http.StatusInternalServerError)
captchaBaseController.HandleInternalError(c, "生成验证码失败", err)
return
}
// 将验证码ID存储到session中这里简化处理实际项目中应该使用更安全的方式
// 设置cookie来存储验证码ID
cookie := utils.CreateSecureCookie("captcha_id", id, 300) // 5分钟过期
http.SetCookie(w, cookie)
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
// 解码base64图片数据并返回
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
c.Header("Content-Type", "image/png")
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
// 直接返回base64编码的图片数据让浏览器解析
// 但是我们需要返回实际的图片数据所以需要解码base64
@@ -81,29 +80,30 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
imgData, err := base64.StdEncoding.DecodeString(b64s)
if err != nil {
http.Error(w, "解码验证码图片失败", http.StatusInternalServerError)
captchaBaseController.HandleInternalError(c, "解码验证码图片失败", err)
return
}
w.Write(imgData)
c.Data(http.StatusOK, "image/png", imgData)
}
// VerifyCaptcha 验证验证码
// 这个函数将在登录处理中被调用
// 支持大小写不敏感匹配
func VerifyCaptcha(r *http.Request, captchaValue string) bool {
func VerifyCaptcha(c *gin.Context, captchaValue string) bool {
// 检查是否为开发模式,如果是则跳过验证码验证
if viper.GetBool("server.dev_mode") {
return true
}
// 从cookie中获取验证码ID
cookie, err := r.Cookie("captcha_id")
captchaId, err := c.Cookie("captcha_id")
if err != nil {
return false
}
captchaId := cookie.Value
if captchaId == "" {
return false
}
@@ -132,36 +132,19 @@ func VerifyCaptcha(r *http.Request, captchaValue string) bool {
// CaptchaAPIHandler 验证码API接口可选用于AJAX验证
// POST /admin/api/captcha/verify - 验证验证码
func CaptchaAPIHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
func CaptchaAPIHandler(c *gin.Context) {
var body struct {
Captcha string `json:"captcha"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 1,
"msg": "请求参数错误",
})
if !captchaBaseController.BindJSON(c, &body) {
return
}
isValid := VerifyCaptcha(r, body.Captcha)
isValid := VerifyCaptcha(c, body.Captcha)
w.Header().Set("Content-Type", "application/json")
if isValid {
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"msg": "验证码正确",
})
captchaBaseController.HandleSuccess(c, "验证码正确", nil)
} else {
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 1,
"msg": "验证码错误",
})
captchaBaseController.HandleValidationError(c, "验证码错误")
}
}

View File

@@ -2,15 +2,20 @@ package admin
import (
"net/http"
"networkDev/database"
"networkDev/constants"
"networkDev/controllers"
"networkDev/models"
"networkDev/services"
"networkDev/utils"
"networkDev/utils/timeutil"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
// 创建基础控制器实例
var handlersBaseController = controllers.NewBaseController()
// formatDBType 格式化数据库类型显示
// 将配置文件中的小写类型转换为友好的显示格式
func formatDBType(dbType string) string {
@@ -32,40 +37,40 @@ func formatDBType(dbType string) string {
// - 未登录:重定向到 /admin/login
// - 已登录:渲染后台布局页(或重定向到 /admin/layout
// - 自动清理失效的JWT Cookie
func AdminIndexHandler(w http.ResponseWriter, r *http.Request) {
if IsAdminAuthenticatedWithCleanup(w, r) {
func AdminIndexHandler(c *gin.Context) {
if IsAdminAuthenticatedWithCleanup(c) {
// 直接渲染布局页保持URL为 /admin
AdminLayoutHandler(w, r)
AdminLayoutHandler(c)
return
}
http.Redirect(w, r, "/admin/login", http.StatusFound)
c.Redirect(http.StatusFound, "/admin/login")
}
// AdminLayoutHandler 后台布局页渲染
// - 渲染 layout.html包含顶部导航、侧边栏与动态内容容器
func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
func AdminLayoutHandler(c *gin.Context) {
// 获取或生成CSRF令牌
var token string
if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" {
if existingToken := utils.GetCSRFTokenFromCookie(c); existingToken != "" {
// 重用现有的Cookie令牌
token = existingToken
} else {
// 生成新的CSRF令牌并设置到Cookie
newToken, err := utils.GenerateCSRFToken()
if err != nil {
http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError)
handlersBaseController.HandleInternalError(c, "生成CSRF令牌失败", err)
return
}
token = newToken
utils.SetCSRFToken(w, token)
utils.SetCSRFToken(c, token)
}
// 准备额外的模板数据
extraData := make(map[string]interface{})
extraData := gin.H{}
// 从数据库读取站点标题
db, dbErr := database.GetDB()
if dbErr != nil {
db, ok := handlersBaseController.GetDB(c)
if !ok {
extraData["Title"] = "凌动技术"
} else {
siteTitle, settingErr := services.FindSettingByName("site_title", db)
@@ -77,7 +82,7 @@ func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
}
// 准备模板数据
data := utils.GetDefaultTemplateData()
data := handlersBaseController.GetDefaultTemplateData()
data["CSRFToken"] = token
// 合并额外数据
@@ -85,13 +90,13 @@ func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
data[key] = value
}
utils.RenderTemplate(w, "layout.html", data)
c.HTML(http.StatusOK, "layout.html", data)
}
// DashboardFragmentHandler 仪表盘片段渲染
// - 展示系统信息:版本、开发模式、数据库类型、启动时长
func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) {
version := "1.0.0"
func DashboardFragmentHandler(c *gin.Context) {
version := constants.AppVersion
mode := viper.GetBool("server.dev_mode")
dbType := viper.GetString("database.type")
if dbType == "" {
@@ -99,25 +104,20 @@ func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) {
}
uptime := timeutil.GetServerUptimeString()
data := map[string]interface{}{
data := gin.H{
"Version": version,
"Mode": mode,
"DBType": formatDBType(dbType),
"Uptime": uptime,
}
utils.RenderTemplate(w, "dashboard.html", data)
c.HTML(http.StatusOK, "dashboard.html", data)
}
// SystemInfoHandler 系统信息API接口
// - 返回系统运行状态的JSON数据用于前端定时刷新
func SystemInfoHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
version := "1.0.0"
func SystemInfoHandler(c *gin.Context) {
version := constants.AppVersion
mode := viper.GetBool("server.dev_mode")
dbType := viper.GetString("database.type")
if dbType == "" {
@@ -125,28 +125,22 @@ func SystemInfoHandler(w http.ResponseWriter, r *http.Request) {
}
uptime := timeutil.GetServerUptimeString()
data := map[string]interface{}{
data := gin.H{
"version": version,
"mode": mode,
"db_type": formatDBType(dbType),
"uptime": uptime,
}
utils.JsonResponse(w, http.StatusOK, true, "ok", data)
handlersBaseController.HandleSuccess(c, "ok", data)
}
// DashboardStatsHandler 仪表盘统计数据API接口
// - 返回应用统计数据的JSON数据包括全部/启用/禁用/变量数量
func DashboardStatsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
func DashboardStatsHandler(c *gin.Context) {
// 获取数据库连接
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
db, ok := handlersBaseController.GetDB(c)
if !ok {
return
}
@@ -158,34 +152,34 @@ func DashboardStatsHandler(w http.ResponseWriter, r *http.Request) {
// 统计全部应用数量
if err := db.Model(&models.App{}).Count(&totalApps).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计应用数量失败", nil)
handlersBaseController.HandleInternalError(c, "统计应用数量失败", err)
return
}
// 统计启用应用数量
if err := db.Model(&models.App{}).Where("status = ?", 1).Count(&enabledApps).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计启用应用数量失败", nil)
handlersBaseController.HandleInternalError(c, "统计启用应用数量失败", err)
return
}
// 统计禁用应用数量
if err := db.Model(&models.App{}).Where("status = ?", 0).Count(&disabledApps).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计禁用应用数量失败", nil)
handlersBaseController.HandleInternalError(c, "统计禁用应用数量失败", err)
return
}
// 统计变量数量
if err := db.Model(&models.Variable{}).Count(&totalVariables).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计变量数量失败", nil)
handlersBaseController.HandleInternalError(c, "统计变量数量失败", err)
return
}
data := map[string]interface{}{
data := gin.H{
"total_apps": totalApps,
"enabled_apps": enabledApps,
"disabled_apps": disabledApps,
"total_variables": totalVariables,
}
utils.JsonResponse(w, http.StatusOK, true, "ok", data)
handlersBaseController.HandleSuccess(c, "ok", data)
}

View File

@@ -1,49 +1,43 @@
package admin
import (
"encoding/json"
"fmt"
"net/http"
"networkDev/database"
"networkDev/models"
"networkDev/utils"
// 新增:用于刷新内存缓存
"networkDev/services"
// 新增用于RedisDel上下文
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"networkDev/controllers"
"networkDev/models"
"networkDev/services"
"networkDev/utils"
)
// 创建基础控制器实例
var settingsBaseController = controllers.NewBaseController()
// SettingsFragmentHandler 设置片段渲染
// - 渲染设置表单通过前端JS调用API加载/保存)
func SettingsFragmentHandler(w http.ResponseWriter, r *http.Request) {
utils.RenderTemplate(w, "settings.html", map[string]interface{}{})
func SettingsFragmentHandler(c *gin.Context) {
c.HTML(http.StatusOK, "settings.html", gin.H{})
}
// SettingsQueryHandler 设置查询API
// - 返回所有设置项的 name:value 映射
func SettingsQueryHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
func SettingsQueryHandler(c *gin.Context) {
db, ok := settingsBaseController.GetDB(c)
if !ok {
return
}
var list []models.Settings
if err := db.Find(&list).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil)
settingsBaseController.HandleInternalError(c, "查询失败", err)
return
}
res := map[string]string{}
for _, s := range list {
res[s.Name] = s.Value
}
utils.JsonResponse(w, http.StatusOK, true, "ok", res)
settingsBaseController.HandleSuccess(c, "ok", res)
}
// SettingsUpdateHandler 更新系统设置处理器
@@ -56,16 +50,10 @@ func SettingsQueryHandler(w http.ResponseWriter, r *http.Request) {
// - 更新完成后:
// 1. 删除对应的Redis缓存键确保后续读取走数据库并重建缓存
// 2. 刷新SettingsService内存缓存
func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
func SettingsUpdateHandler(c *gin.Context) {
// 先尝试解析为直接字段格式
var directBody map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&directBody); err != nil {
utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil)
if !settingsBaseController.BindJSON(c, &directBody) {
return
}
@@ -82,7 +70,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
}
} else {
utils.JsonResponse(w, http.StatusBadRequest, false, "settings字段格式错误", nil)
settingsBaseController.HandleValidationError(c, "settings字段格式错误")
return
}
} else {
@@ -99,13 +87,12 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
if len(settingsData) == 0 {
utils.JsonResponse(w, http.StatusBadRequest, false, "无设置项", nil)
settingsBaseController.HandleValidationError(c, "无设置项")
return
}
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
db, ok := settingsBaseController.GetDB(c)
if !ok {
return
}
@@ -120,7 +107,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
s = models.Settings{Name: k, Value: v}
if err := db.Create(&s).Error; err != nil {
logrus.WithError(err).WithField("setting_name", k).Error("创建设置失败")
utils.JsonResponse(w, http.StatusInternalServerError, false, fmt.Sprintf("保存设置 %s 失败", k), nil)
settingsBaseController.HandleInternalError(c, fmt.Sprintf("保存设置 %s 失败", k), err)
return
}
@@ -128,7 +115,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
// 存在则更新
if err := db.Model(&models.Settings{}).Where("id = ?", s.ID).Update("value", v).Error; err != nil {
logrus.WithError(err).WithField("setting_name", k).Error("更新设置失败")
utils.JsonResponse(w, http.StatusInternalServerError, false, fmt.Sprintf("更新设置 %s 失败", k), nil)
settingsBaseController.HandleInternalError(c, fmt.Sprintf("更新设置 %s 失败", k), err)
return
}
@@ -143,5 +130,5 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) {
// 刷新内存中的设置缓存,保证后续读取一致
services.GetSettingsService().RefreshCache()
utils.JsonResponse(w, http.StatusOK, true, "保存成功", nil)
settingsBaseController.HandleSuccess(c, "保存成功", nil)
}

View File

@@ -1,36 +1,35 @@
package admin
import (
"encoding/json"
"net/http"
"networkDev/database"
"networkDev/controllers"
"networkDev/models"
"networkDev/utils"
"strings"
"github.com/gin-gonic/gin"
)
// 创建基础控制器实例
var baseController = controllers.NewBaseController()
// UserFragmentHandler 个人资料片段渲染
// - 渲染个人资料与修改密码表单
func UserFragmentHandler(w http.ResponseWriter, r *http.Request) {
utils.RenderTemplate(w, "user.html", map[string]interface{}{})
func UserFragmentHandler(c *gin.Context) {
c.HTML(http.StatusOK, "user.html", gin.H{})
}
// UserProfileQueryHandler 获取当前登录管理员的用户名
// - 返回 JSON: {username}
// - 直接从JWT获取用户名信息
func UserProfileQueryHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
claims, _, err := GetCurrentAdminUserWithRefresh(w, r)
func UserProfileQueryHandler(c *gin.Context) {
claims, _, err := GetCurrentAdminUserWithRefresh(c)
if err != nil {
utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil)
baseController.HandleValidationError(c, "未登录或会话已过期")
return
}
utils.JsonResponse(w, http.StatusOK, true, "ok", map[string]interface{}{
baseController.HandleSuccess(c, "ok", gin.H{
"username": claims.Username,
})
}
@@ -40,15 +39,10 @@ func UserProfileQueryHandler(w http.ResponseWriter, r *http.Request) {
// - 校验旧密码正确性、新密码与确认一致性
// - 成功后更新密码哈希
// - 自动刷新接近过期的JWT令牌
func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
claims, _, err := GetCurrentAdminUserWithRefresh(w, r)
func UserPasswordUpdateHandler(c *gin.Context) {
claims, _, err := GetCurrentAdminUserWithRefresh(c)
if err != nil {
utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil)
baseController.HandleValidationError(c, "未登录或会话已过期")
return
}
@@ -57,43 +51,45 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
NewPassword string `json:"new_password"`
ConfirmPassword string `json:"confirm_password"`
}
var decodeErr error
if decodeErr = json.NewDecoder(r.Body).Decode(&body); decodeErr != nil {
utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil)
if !baseController.BindJSON(c, &body) {
return
}
// 基础校验
if body.OldPassword == "" || body.NewPassword == "" || body.ConfirmPassword == "" {
utils.JsonResponse(w, http.StatusBadRequest, false, "旧密码/新密码/确认密码均不能为空", nil)
if !baseController.ValidateRequired(c, map[string]interface{}{
"旧密码": body.OldPassword,
"新密码": body.NewPassword,
"确认密码": body.ConfirmPassword,
}) {
return
}
if len(body.NewPassword) < 6 {
utils.JsonResponse(w, http.StatusBadRequest, false, "新密码长度不能少于6位", nil)
baseController.HandleValidationError(c, "新密码长度不能少于6位")
return
}
if body.NewPassword != body.ConfirmPassword {
utils.JsonResponse(w, http.StatusBadRequest, false, "两次输入的新密码不一致", nil)
baseController.HandleValidationError(c, "两次输入的新密码不一致")
return
}
if body.NewPassword == body.OldPassword {
utils.JsonResponse(w, http.StatusBadRequest, false, "新密码不能与旧密码相同", nil)
baseController.HandleValidationError(c, "新密码不能与旧密码相同")
return
}
// 注释由于使用了AdminAuthRequired中间件已确保是管理员用户
// 获取数据库连接
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
db, ok := baseController.GetDB(c)
if !ok {
return
}
// 通过前缀匹配一次性获取所有管理员相关设置
var adminSettings []models.Settings
if err = db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "获取管理员设置失败", nil)
baseController.HandleInternalError(c, "获取管理员设置失败", err)
return
}
@@ -107,37 +103,37 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
adminPassword, hasPassword := settingsMap["admin_password"]
adminPasswordSalt, hasSalt := settingsMap["admin_password_salt"]
if !hasPassword || !hasSalt {
utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码设置不完整", nil)
baseController.HandleInternalError(c, "管理员密码设置不完整", nil)
return
}
// 校验旧密码
if !utils.VerifyPasswordWithSalt(body.OldPassword, adminPasswordSalt, adminPassword) {
utils.JsonResponse(w, http.StatusUnauthorized, false, "旧密码不正确", nil)
baseController.HandleValidationError(c, "旧密码不正确")
return
}
// 生成新的密码盐值
newSalt, err := utils.GenerateRandomSalt()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成密码盐失败", nil)
baseController.HandleInternalError(c, "生成密码盐失败", err)
return
}
// 生成新密码哈希
newPasswordHash, err := utils.HashPasswordWithSalt(body.NewPassword, newSalt)
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成密码哈希失败", nil)
baseController.HandleInternalError(c, "生成密码哈希失败", err)
return
}
// 更新settings中的管理员密码和盐值
if err = db.Model(&models.Settings{}).Where("name = ?", "admin_password").Update("value", newPasswordHash).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "更新密码失败", nil)
baseController.HandleInternalError(c, "更新密码失败", err)
return
}
if err = db.Model(&models.Settings{}).Where("name = ?", "admin_password_salt").Update("value", newSalt).Error; err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "更新密码盐值失败", nil)
baseController.HandleInternalError(c, "更新密码盐值失败", err)
return
}
@@ -149,16 +145,15 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
newToken, err := generateJWTTokenForAdmin(adminUser)
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil)
baseController.HandleInternalError(c, "生成新令牌失败", err)
return
}
// 更新Cookie使用安全配置
cookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge())
http.SetCookie(w, cookie)
c.SetCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge(), "/", "", false, true)
// 密码修改成功已重新生成JWT令牌
utils.JsonResponse(w, http.StatusOK, true, "密码修改成功", nil)
baseController.HandleSuccess(c, "密码修改成功", nil)
}
// UserProfileUpdateHandler 修改当前登录管理员的用户名
@@ -166,15 +161,10 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
// - 校验用户名非空、长度与唯一性
// - 更新数据库后重新签发JWT并写入 Cookie保持前端展示的一致性
// - 自动刷新接近过期的JWT令牌
func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
_, _, err := GetCurrentAdminUserWithRefresh(w, r)
func UserProfileUpdateHandler(c *gin.Context) {
_, _, err := GetCurrentAdminUserWithRefresh(c)
if err != nil {
utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil)
baseController.HandleValidationError(c, "未登录或会话已过期")
return
}
@@ -182,24 +172,22 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
Username string `json:"username"`
OldPassword string `json:"old_password"`
}
if decodeErr := json.NewDecoder(r.Body).Decode(&body); decodeErr != nil {
utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil)
if !baseController.BindJSON(c, &body) {
return
}
username := strings.TrimSpace(body.Username)
if username == "" {
utils.JsonResponse(w, http.StatusBadRequest, false, "用户名不能为空", nil)
baseController.HandleValidationError(c, "用户名不能为空")
return
}
if len(username) > 64 {
utils.JsonResponse(w, http.StatusBadRequest, false, "用户名长度不能超过64字符", nil)
baseController.HandleValidationError(c, "用户名长度不能超过64字符")
return
}
db, err := database.GetDB()
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
db, ok := baseController.GetDB(c)
if !ok {
return
}
@@ -208,7 +196,7 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
// 获取所有管理员相关设置
var adminSettings []models.Settings
if dbErr := db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; dbErr != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "获取管理员设置失败", nil)
baseController.HandleInternalError(c, "获取管理员设置失败", dbErr)
return
}
@@ -220,25 +208,25 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
adminUsername, exists := settingsMap["admin_username"]
if !exists {
utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员用户名设置不存在", nil)
baseController.HandleInternalError(c, "管理员用户名设置不存在", nil)
return
}
adminPassword, exists := settingsMap["admin_password"]
if !exists {
utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码设置不存在", nil)
baseController.HandleInternalError(c, "管理员密码设置不存在", nil)
return
}
adminPasswordSalt, exists := settingsMap["admin_password_salt"]
if !exists {
utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码盐值设置不存在", nil)
baseController.HandleInternalError(c, "管理员密码盐值设置不存在", nil)
return
}
// 如果用户名未变化则直接返回成功(无需校验旧密码)
if strings.EqualFold(username, adminUsername) {
utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{
baseController.HandleSuccess(c, "保存成功", gin.H{
"username": username,
})
return
@@ -246,19 +234,19 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
// 修改用户名需要进行当前密码校验
if strings.TrimSpace(body.OldPassword) == "" {
utils.JsonResponse(w, http.StatusBadRequest, false, "修改用户名需要提供当前密码", nil)
baseController.HandleValidationError(c, "修改用户名需要提供当前密码")
return
}
// 使用盐值验证当前密码
if !utils.VerifyPasswordWithSalt(body.OldPassword, adminPasswordSalt, adminPassword) {
utils.JsonResponse(w, http.StatusUnauthorized, false, "当前密码不正确", nil)
baseController.HandleValidationError(c, "当前密码不正确")
return
}
// 更新管理员用户名设置
if dbErr := db.Model(&models.Settings{}).Where("name = ?", "admin_username").Update("value", username).Error; dbErr != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "更新管理员用户名失败", nil)
baseController.HandleInternalError(c, "更新管理员用户名失败", dbErr)
return
}
@@ -271,13 +259,12 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
token, err := generateJWTTokenForAdmin(adminUser)
if err != nil {
utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil)
baseController.HandleInternalError(c, "生成新令牌失败", err)
return
}
cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge())
http.SetCookie(w, cookie)
c.SetCookie("admin_session", token, utils.GetDefaultCookieMaxAge(), "/", "", false, true)
utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{
baseController.HandleSuccess(c, "保存成功", gin.H{
"username": username,
})
}

View File

@@ -1,58 +1,57 @@
package admin
import (
"encoding/json"
"net/http"
"networkDev/database"
"networkDev/controllers"
"networkDev/models"
"networkDev/utils"
"regexp"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// 创建基础控制器实例
var variableBaseController = controllers.NewBaseController()
// VariableFragmentHandler 变量列表页面片段处理器
func VariableFragmentHandler(w http.ResponseWriter, r *http.Request) {
utils.RenderTemplate(w, "variables", map[string]interface{}{
func VariableFragmentHandler(c *gin.Context) {
c.HTML(http.StatusOK, "variables.html", gin.H{
"Title": "变量管理",
})
}
// VariableListHandler 变量列表API处理器
func VariableListHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func VariableListHandler(c *gin.Context) {
// 获取分页参数
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
page, _ := strconv.Atoi(c.Query("page"))
if page <= 0 {
page = 1
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
limit, _ := strconv.Atoi(c.Query("limit"))
// 兼容前端使用的page_size参数
if limit <= 0 {
limit, _ = strconv.Atoi(c.Query("page_size"))
}
if limit <= 0 {
limit = 10
}
// 获取应用UUID参数用于按应用筛选变量
appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid"))
appUUID := strings.TrimSpace(c.Query("app_uuid"))
// 获取搜索关键词参数(支持编号、别名、数据的综合搜索)
search := strings.TrimSpace(r.URL.Query().Get("search"))
search := strings.TrimSpace(c.Query("search"))
// 兼容旧的别名搜索参数
if search == "" {
search = strings.TrimSpace(r.URL.Query().Get("alias"))
search = strings.TrimSpace(c.Query("alias"))
}
// 构建查询
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "Internal server error", http.StatusInternalServerError)
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
@@ -66,7 +65,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) {
// 如果指定了搜索关键词,则在编号、别名、数据、备注中进行模糊搜索
if search != "" {
query = query.Where("number LIKE ? OR alias LIKE ? OR data LIKE ? OR remark LIKE ?",
query = query.Where("number LIKE ? OR alias LIKE ? OR data LIKE ? OR remark LIKE ?",
"%"+search+"%", "%"+search+"%", "%"+search+"%", "%"+search+"%")
}
@@ -74,7 +73,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) {
var total int64
if err := query.Count(&total).Error; err != nil {
logrus.WithError(err).Error("Failed to count variables")
http.Error(w, "Internal server error", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "查询变量总数失败", err)
return
}
@@ -83,7 +82,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) {
offset := (page - 1) * limit
if err := query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&variables).Error; err != nil {
logrus.WithError(err).Error("Failed to fetch variables")
http.Error(w, "Internal server error", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "查询变量列表失败", err)
return
}
@@ -141,24 +140,18 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) {
})
}
response := map[string]interface{}{
response := gin.H{
"code": 0,
"msg": "success",
"count": total,
"data": responseData,
}
w.Header().Set("Content-Type", "application/json")
utils.WriteJSONResponse(w, http.StatusOK, response)
c.JSON(http.StatusOK, response)
}
// VariableCreateHandler 新增变量API处理器
func VariableCreateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func VariableCreateHandler(c *gin.Context) {
var req struct {
AppUUID string `json:"app_uuid"`
Alias string `json:"alias"`
@@ -166,40 +159,34 @@ func VariableCreateHandler(w http.ResponseWriter, r *http.Request) {
Remark string `json:"remark"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logrus.WithError(err).Error("Failed to decode JSON request")
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !variableBaseController.BindJSON(c, &req) {
return
}
// 验证必填字段
if strings.TrimSpace(req.AppUUID) == "" {
http.Error(w, "应用UUID不能为空", http.StatusBadRequest)
return
}
if strings.TrimSpace(req.Alias) == "" {
http.Error(w, "变量别名不能为空", http.StatusBadRequest)
if !variableBaseController.ValidateRequired(c, map[string]interface{}{
"应用UUID": req.AppUUID,
"变量别名": req.Alias,
}) {
return
}
// 验证别名格式:必须以英文字母开头,只能包含数字和英文字母
aliasPattern := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`)
if !aliasPattern.MatchString(req.Alias) {
http.Error(w, "别名必须以英文字母开头,只能包含数字和英文字母", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "别名必须以英文字母开头,只能包含数字和英文字母")
return
}
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "数据库连接失败", http.StatusInternalServerError)
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
// 验证应用是否存在
var app models.App
if err := db.Where("uuid = ?", req.AppUUID).First(&app).Error; err != nil {
http.Error(w, "应用不存在", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "应用不存在")
return
}
@@ -213,27 +200,15 @@ func VariableCreateHandler(w http.ResponseWriter, r *http.Request) {
if err := db.Create(&variable).Error; err != nil {
logrus.WithError(err).Error("Failed to create variable")
http.Error(w, "创建变量失败", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "创建变量失败", err)
return
}
response := map[string]interface{}{
"code": 0,
"msg": "创建成功",
"data": variable,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
variableBaseController.HandleSuccess(c, "创建成功", variable)
}
// VariableUpdateHandler 更新变量API处理器
func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func VariableUpdateHandler(c *gin.Context) {
var req struct {
UUID string `json:"uuid"`
AppUUID string `json:"app_uuid"`
@@ -242,50 +217,42 @@ func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) {
Remark string `json:"remark"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !variableBaseController.BindJSON(c, &req) {
return
}
// 验证必填字段
if strings.TrimSpace(req.UUID) == "" {
http.Error(w, "变量UUID不能为空", http.StatusBadRequest)
return
}
if strings.TrimSpace(req.AppUUID) == "" {
http.Error(w, "应用UUID不能为空", http.StatusBadRequest)
return
}
if strings.TrimSpace(req.Alias) == "" {
http.Error(w, "变量别名不能为空", http.StatusBadRequest)
if !variableBaseController.ValidateRequired(c, map[string]interface{}{
"变量UUID": req.UUID,
"应用UUID": req.AppUUID,
"变量别名": req.Alias,
}) {
return
}
// 验证别名格式:必须以英文字母开头,只能包含数字和英文字母
aliasPattern := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`)
if !aliasPattern.MatchString(req.Alias) {
http.Error(w, "别名必须以英文字母开头,只能包含数字和英文字母", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "别名必须以英文字母开头,只能包含数字和英文字母")
return
}
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "数据库连接失败", http.StatusInternalServerError)
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
// 验证应用是否存在
var app models.App
if err := db.Where("uuid = ?", req.AppUUID).First(&app).Error; err != nil {
http.Error(w, "应用不存在", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "应用不存在")
return
}
// 通过uuid字段查找变量
var variable models.Variable
if err := db.Where("uuid = ?", strings.TrimSpace(req.UUID)).First(&variable).Error; err != nil {
http.Error(w, "变量不存在", http.StatusNotFound)
variableBaseController.HandleValidationError(c, "变量不存在")
return
}
@@ -297,139 +264,90 @@ func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) {
if err := db.Save(&variable).Error; err != nil {
logrus.WithError(err).Error("Failed to update variable")
http.Error(w, "更新变量失败", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "更新变量失败", err)
return
}
response := map[string]interface{}{
"code": 0,
"msg": "更新成功",
"data": variable,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
variableBaseController.HandleSuccess(c, "更新成功", variable)
}
// VariableDeleteHandler 删除变量API处理器
func VariableDeleteHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func VariableDeleteHandler(c *gin.Context) {
var req struct {
ID uint `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !variableBaseController.BindJSON(c, &req) {
return
}
if req.ID == 0 {
http.Error(w, "变量ID不能为空", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "变量ID不能为空")
return
}
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "数据库连接失败", http.StatusInternalServerError)
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
// 删除变量
if err := db.Delete(&models.Variable{}, req.ID).Error; err != nil {
logrus.WithError(err).Error("Failed to delete variable")
http.Error(w, "删除变量失败", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "删除变量失败", err)
return
}
logrus.WithField("variable_id", req.ID).Info("Successfully deleted variable")
response := map[string]interface{}{
"code": 0,
"msg": "删除成功",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
variableBaseController.HandleSuccess(c, "删除成功", nil)
}
// VariablesBatchDeleteHandler 批量删除变量API处理器
func VariablesBatchDeleteHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
func VariablesBatchDeleteHandler(c *gin.Context) {
var req struct {
IDs []uint `json:"ids"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
if !variableBaseController.BindJSON(c, &req) {
return
}
if len(req.IDs) == 0 {
http.Error(w, "请选择要删除的变量", http.StatusBadRequest)
variableBaseController.HandleValidationError(c, "请选择要删除的变量")
return
}
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "数据库连接失败", http.StatusInternalServerError)
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
// 批量删除变量
if err := db.Delete(&models.Variable{}, req.IDs).Error; err != nil {
logrus.WithError(err).Error("Failed to batch delete variables")
http.Error(w, "批量删除失败", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "批量删除失败", err)
return
}
logrus.WithField("variable_ids", req.IDs).Info("Successfully batch deleted variables")
response := map[string]interface{}{
"code": 0,
"msg": "批量删除成功",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
variableBaseController.HandleSuccess(c, "批量删除成功", nil)
}
// VariableGetAppsHandler 获取应用列表(用于筛选下拉框)
func VariableGetAppsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
db, err := database.GetDB()
if err != nil {
logrus.WithError(err).Error("Failed to get database connection")
http.Error(w, "Internal server error", http.StatusInternalServerError)
func VariableGetAppsHandler(c *gin.Context) {
db, ok := variableBaseController.GetDB(c)
if !ok {
return
}
var apps []models.App
if err := db.Select("uuid, name").Find(&apps).Error; err != nil {
logrus.WithError(err).Error("Failed to fetch apps")
http.Error(w, "Internal server error", http.StatusInternalServerError)
variableBaseController.HandleInternalError(c, "获取应用列表失败", err)
return
}
response := map[string]interface{}{
"code": 0,
"msg": "success",
"data": apps,
}
w.Header().Set("Content-Type", "application/json")
utils.WriteJSONResponse(w, http.StatusOK, response)
variableBaseController.HandleSuccess(c, "success", apps)
}

159
controllers/base.go Normal file
View File

@@ -0,0 +1,159 @@
package controllers
import (
"net/http"
"strconv"
"networkDev/database"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// BaseController 基础控制器结构体
type BaseController struct{}
// NewBaseController 创建基础控制器实例
func NewBaseController() *BaseController {
return &BaseController{}
}
// GetDB 获取数据库连接,统一错误处理
func (bc *BaseController) GetDB(c *gin.Context) (*gorm.DB, bool) {
db, err := database.GetDB()
if err != nil {
bc.HandleDatabaseError(c, err)
return nil, false
}
return db, true
}
// HandleDatabaseError 统一处理数据库连接错误
func (bc *BaseController) HandleDatabaseError(c *gin.Context, err error) {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 1,
"msg": "数据库连接失败",
"data": nil,
})
}
// HandleValidationError 统一处理验证错误
func (bc *BaseController) HandleValidationError(c *gin.Context, message string) {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"msg": message,
"data": nil,
})
}
// HandleNotFoundError 统一处理资源未找到错误
func (bc *BaseController) HandleNotFoundError(c *gin.Context, resource string) {
c.JSON(http.StatusNotFound, gin.H{
"code": 1,
"msg": resource + "不存在",
"data": nil,
})
}
// HandleInternalError 统一处理内部服务器错误
func (bc *BaseController) HandleInternalError(c *gin.Context, message string, err error) {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 1,
"msg": message,
"data": nil,
})
}
// HandleSuccess 统一处理成功响应
func (bc *BaseController) HandleSuccess(c *gin.Context, message string, data interface{}) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": message,
"data": data,
})
}
// HandleCreated 统一处理创建成功响应
func (bc *BaseController) HandleCreated(c *gin.Context, message string, data interface{}) {
c.JSON(http.StatusCreated, gin.H{
"code": 0,
"msg": message,
"data": data,
})
}
// ValidateRequired 验证必填字段
func (bc *BaseController) ValidateRequired(c *gin.Context, fields map[string]interface{}) bool {
for fieldName, fieldValue := range fields {
if fieldValue == nil || fieldValue == "" {
bc.HandleValidationError(c, fieldName+"不能为空")
return false
}
}
return true
}
// GetPaginationParams 获取分页参数
func (bc *BaseController) GetPaginationParams(c *gin.Context) (int, int) {
page := 1
pageSize := 10
if p := c.Query("page"); p != "" {
if pageInt, err := strconv.Atoi(p); err == nil && pageInt > 0 {
page = pageInt
}
}
if ps := c.Query("page_size"); ps != "" {
if pageSizeInt, err := strconv.Atoi(ps); err == nil && pageSizeInt > 0 && pageSizeInt <= 100 {
pageSize = pageSizeInt
}
}
return page, pageSize
}
// CalculateOffset 计算数据库查询偏移量
func (bc *BaseController) CalculateOffset(page, pageSize int) int {
return (page - 1) * pageSize
}
// BindJSON 绑定JSON数据并处理错误
func (bc *BaseController) BindJSON(c *gin.Context, obj interface{}) bool {
if err := c.ShouldBindJSON(obj); err != nil {
bc.HandleValidationError(c, "请求参数错误: "+err.Error())
return false
}
return true
}
// BindQuery 绑定查询参数并处理错误
func (bc *BaseController) BindQuery(c *gin.Context, obj interface{}) bool {
if err := c.ShouldBindQuery(obj); err != nil {
bc.HandleValidationError(c, "查询参数错误: "+err.Error())
return false
}
return true
}
// BindURI 绑定URI参数并处理错误
func (bc *BaseController) BindURI(c *gin.Context, obj interface{}) bool {
if err := c.ShouldBindUri(obj); err != nil {
bc.HandleValidationError(c, "URI参数绑定失败: "+err.Error())
return false
}
return true
}
// GetDefaultTemplateData 获取默认模板数据
// 返回包含系统基础信息的数据映射,包括站点标题、页脚文本、备案信息等
func (bc *BaseController) GetDefaultTemplateData() gin.H {
return gin.H{
"SystemName": "凌动技术",
"FooterText": "© 2025 凌动技术 保留所有权利",
"ICPRecord": "",
"ICPRecordLink": "https://beian.miit.gov.cn",
"PSBRecord": "",
"PSBRecordLink": "https://www.beian.gov.cn",
}
}

View File

@@ -2,72 +2,48 @@ package home
import (
"net/http"
"networkDev/controllers"
"networkDev/database"
"networkDev/models"
"networkDev/services"
"networkDev/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// RootHandler 主页处理器
func RootHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
var homeBaseController = controllers.NewBaseController()
// getSettingValue 获取配置值,优先从数据库获取,不存在时使用默认值
func getSettingValue(settingName string, defaultValue string, db *gorm.DB) string {
if setting, err := services.FindSettingByName(settingName, db); err == nil {
return setting.Value
}
return defaultValue
}
// RootHandler 主页处理器
func RootHandler(c *gin.Context) {
// 获取数据库连接
db, err := database.GetDB()
if err != nil {
http.Error(w, "数据库连接失败", http.StatusInternalServerError)
c.HTML(http.StatusInternalServerError, "error.html", gin.H{
"error": "数据库连接失败",
})
return
}
// 从数据库获取站点标题和页脚文本
siteTitle, err := services.FindSettingByName("site_title", db)
if err != nil {
siteTitle = &models.Settings{Value: "凌动技术"}
}
// 获取默认模板数据
defaultData := homeBaseController.GetDefaultTemplateData()
footerText, err := services.FindSettingByName("footer_text", db)
if err != nil {
footerText = &models.Settings{Value: "© 2025 凌动技术 保留所有权利"}
}
// 从数据库获取备案信息
icpRecord, err := services.FindSettingByName("icp_record", db)
if err != nil {
icpRecord = &models.Settings{Value: ""}
}
icpRecordLink, err := services.FindSettingByName("icp_record_link", db)
if err != nil {
icpRecordLink = &models.Settings{Value: "https://beian.miit.gov.cn"}
}
// 从数据库获取公安备案信息
psbRecord, err := services.FindSettingByName("psb_record", db)
if err != nil {
psbRecord = &models.Settings{Value: ""}
}
psbRecordLink, err := services.FindSettingByName("psb_record_link", db)
if err != nil {
psbRecordLink = &models.Settings{Value: "https://www.beian.gov.cn"}
}
// 准备模板数据
data := map[string]interface{}{
"SystemName": siteTitle.Value,
"FooterText": footerText.Value,
"ICPRecord": icpRecord.Value,
"ICPRecordLink": icpRecordLink.Value,
"PSBRecord": psbRecord.Value,
"PSBRecordLink": psbRecordLink.Value,
// 准备模板数据,优先使用数据库配置,不存在时使用默认值
data := gin.H{
"SystemName": getSettingValue("site_title", defaultData["SystemName"].(string), db),
"FooterText": getSettingValue("footer_text", defaultData["FooterText"].(string), db),
"ICPRecord": getSettingValue("icp_record", defaultData["ICPRecord"].(string), db),
"ICPRecordLink": getSettingValue("icp_record_link", defaultData["ICPRecordLink"].(string), db),
"PSBRecord": getSettingValue("psb_record", defaultData["PSBRecord"].(string), db),
"PSBRecordLink": getSettingValue("psb_record_link", defaultData["PSBRecordLink"].(string), db),
"title": "主页",
}
if err := utils.RenderTemplate(w, "index.html", data); err != nil {
http.Error(w, "页面加载失败", http.StatusInternalServerError)
return
}
c.HTML(http.StatusOK, "index.html", data)
}