feat: apply user settings to effect
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
"table_prefix": "baihu_"
|
"table_prefix": "baihu_"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"jwt_secret": "ql_panel_secret_key",
|
|
||||||
"password_salt": "ql_panel_salt"
|
"password_salt": "ql_panel_salt"
|
||||||
},
|
},
|
||||||
"task": {
|
"task": {
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ func (a *App) initDatabase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initRouter() {
|
func (a *App) initRouter() {
|
||||||
ctrls := router.RegisterControllers()
|
ctrls, settingsService := router.RegisterControllers()
|
||||||
a.Router = router.Setup(ctrls)
|
a.Router = router.Setup(ctrls, settingsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Run() {
|
func (a *App) Run() {
|
||||||
|
|||||||
@@ -29,31 +29,38 @@ const (
|
|||||||
// CookieName Cookie 名称
|
// CookieName Cookie 名称
|
||||||
CookieName = "BHToken"
|
CookieName = "BHToken"
|
||||||
|
|
||||||
// TokenExpireDays Token 过期天数
|
|
||||||
TokenExpireDays = 7
|
|
||||||
// CookieMaxAge Cookie 有效期(秒)7天
|
|
||||||
CookieMaxAge = 86400 * TokenExpireDays
|
|
||||||
|
|
||||||
// DefaultJWTSecret 默认 JWT 密钥
|
|
||||||
DefaultJWTSecret = "baihu-default-secret-key"
|
|
||||||
|
|
||||||
// DefaultTaskTimeout 默认任务超时时间(分钟)
|
// DefaultTaskTimeout 默认任务超时时间(分钟)
|
||||||
DefaultTaskTimeout = 30
|
DefaultTaskTimeout = 30
|
||||||
|
|
||||||
|
// Settings Section 常量
|
||||||
|
SectionSite = "site"
|
||||||
|
SectionSystem = "system"
|
||||||
|
|
||||||
|
// Site Settings Key 常量
|
||||||
|
KeyTitle = "title"
|
||||||
|
KeySubtitle = "subtitle"
|
||||||
|
KeyIcon = "icon"
|
||||||
|
KeyPageSize = "page_size"
|
||||||
|
KeyCookieDays = "cookie_days"
|
||||||
|
|
||||||
|
// System Settings Key 常量
|
||||||
|
KeyJWTSecret = "jwt_secret"
|
||||||
|
KeyInitialized = "initialized"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TablePrefix 表前缀,可在运行时设置
|
// TablePrefix 表前缀,可在运行时设置
|
||||||
var TablePrefix = DefaultTablePrefix
|
var TablePrefix = DefaultTablePrefix
|
||||||
|
|
||||||
// JWTSecret JWT 密钥,可通过配置文件设置
|
// DefaultIcon 默认站点图标
|
||||||
var JWTSecret = DefaultJWTSecret
|
var DefaultIcon = `<svg t="1766107903919" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1942" width="200" height="200"><path d="M884.992 273.05984c4.10624 0 5.0688-2.36544 2.10944-5.25312 0 0-64.28672-65.55648-111.7696-75.55072-47.48288-9.984-45.47584-59.37152-80.56832-75.02848-72.0896-32.16384-158.6176-34.3552-158.6176-34.3552s-91.37152-4.92544-138.752-9.89184c-19.46624-2.03776-54.46656-9.58464-54.46656-9.58464-4.0448-0.84992-10.63936-1.19808-14.66368-0.44032 0 0-30.63808-0.07168-44.30848 43.35616-8.8576 28.11904 1.792 104.79616 1.792 104.79616 1.46432 12.27776-3.42016 30.21824-10.72128 40.20224 0 0-36.77184 46.03904-58.9312 100.34176-22.15936 54.30272 118.15936 145.05984 208.98816 205.27104C507.82208 613.89824 502.03648 743.424 502.03648 743.424s-74.19904-96.75776-194.00704-156.9792C188.2112 526.22336 150.30272 442.23488 150.30272 442.23488c-2.89792-5.45792-5.94944-4.94592-6.71744 1.21856 0 0-15.1552 91.61728 16.25088 147.8144 70.92224 126.88384 141.74208 112.88576 197.03808 183.27552s54.272 164.9152 54.272 164.9152-97.62816-141.29152-235.66336-205.55776c-91.53536-61.27616-74.7008-125.91104-85.49376-101.85728-10.79296 24.05376 26.73664 192.41984 65.40288 222.2592 80.57856 62.18752 94.16704 101.98016 94.16704 101.98016h175.53408S544.512 814.85824 572.928 725.73952c44.41088-139.30496 40.20224-191.26272 40.20224-191.26272 0.08192-8.22272 6.81984-14.19264 14.98112-13.29152 0 0 46.45888 4.64896 66.64192 9.68704 23.53152 5.86752 55.35744 26.20416 55.35744 26.20416 3.49184 2.14016 7.39328 0.68608 8.69376-3.1744l34.4576-102.77888c1.30048-3.8912-0.63488-5.51936-4.352-3.75808 0 0-45.37344 25.09824-88.17664 12.1856-20.15232-6.08256-59.60704-14.82752-74.69056-32.6656-16.95744-20.03968-15.59552-71.3728 26.66496-79.21664 48.0256-8.9088 33.13664 15.14496 65.91488 24.64768 27.42272 7.95648 22.29248-1.69984 26.69568 5.21216 1.67936 2.63168 0.38912 32.65536 0.38912 32.65536-0.21504 6.144 3.39968 7.84384 8.0384 3.79904l41.89184-36.46464c17.37728-11.24352 30.86336-0.57344 48.24064-11.81696 14.73536-9.53344 29.58336-43.66336 29.58336-43.66336 4.48512-9.24672 0.60416-20.41856-8.63232-24.92416l-42.58816-20.80768c-3.69664-1.80224-3.38944-3.26656 0.74752-3.26656h62.0032zM422.54336 123.87328s-49.88928 50.7392-74.5472 50.66752c-24.65792-0.07168-33.24928-56.12544-3.92192-56.12544 18.00192 0 76.32896 0.21504 76.32896 0.21504 4.13696 0.02048 5.0688 2.36544 2.14016 5.24288z m123.09504 249.64096s-3.31776-25.53856-33.16736-40.05888c-29.8496-14.52032-52.5312-41.13408-59.648-68.95616-12.1856-54.8864 48.29184-104.192 48.29184-104.192s-30.1056 73.6768 3.5328 106.60864c54.272 53.12512 40.99072 106.5984 40.99072 106.5984z m155.56608-164.46464c-7.94624 10.32192-9.60512 37.92896-59.2384 20.736-49.63328-17.2032-71.00416-54.5792-71.00416-54.5792-2.27328-3.40992-0.79872-6.49216 3.328-6.8096 0 0 43.55072-5.03808 80.06656 6.44096 24.91392 7.82336 54.79424 23.88992 46.848 34.21184z" fill="#272636" p-id="1943"></path><path d="M366.30528 259.26656c1.05472-1.76128 1.51552-1.57696 1.19808 0.43008 0 0-7.55712 19.0464 22.8864 87.63392 27.0848 61.02016 87.49056 68.7104 118.66112 98.23232 55.808 52.82816 53.52448 123.45344 53.52448 123.45344s-25.82528-49.85856-85.98528-78.83776-92.6208-50.52416-127.7952-85.77024c-45.02528-45.12768 17.5104-145.14176 17.5104-145.14176zM500.0704 961.44384h134.49216s95.8464-138.07616 46.68416-291.25632c-16.19968-50.46272-43.45856-80.00512-43.45856-80.00512-5.18144-6.38976-8.89856-4.88448-8.448 3.34848 0 0 9.15456 99.80928-19.44576 194.00704-28.60032 94.208-109.824 173.90592-109.824 173.90592zM681.61536 956.8768h105.24672s22.38464-73.5232 16.61952-130.21184c-9.30816-91.57632-48.31232-132.72064-48.31232-132.72064-3.80928-4.77184-6.0928-3.67616-5.2224 2.38592 0 0 16.75264 89.1904-14.4896 150.1184-30.9248 60.30336-53.84192 110.42816-53.84192 110.42816zM869.30432 811.35616c-2.88768-2.9184-4.80256-1.95584-4.38272 2.10944 0 0 6.79936 44.99456-7.76192 81.22368-10.12736 25.1904-28.91776 58.60352-28.91776 58.60352h107.17184s5.34528-50.31936-19.89632-83.97824c-11.56096-23.99232-46.21312-57.9584-46.21312-57.9584z" fill="#272636" p-id="1944"></path></svg>`
|
||||||
|
|
||||||
// DefaultSettings 默认系统设置
|
// DefaultSettings 默认系统设置
|
||||||
var DefaultSettings = map[string]map[string]string{
|
var DefaultSettings = map[string]map[string]string{
|
||||||
"site": {
|
SectionSite: {
|
||||||
"title": "白虎面板",
|
KeyTitle: "白虎面板",
|
||||||
"subtitle": "轻量级定时任务管理系统",
|
KeySubtitle: "轻量级定时任务管理系统",
|
||||||
"icon": "<svg t=\"1766107903919\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"1942\" width=\"200\" height=\"200\"><path d=\"M884.992 273.05984c4.10624 0 5.0688-2.36544 2.10944-5.25312 0 0-64.28672-65.55648-111.7696-75.55072-47.48288-9.984-45.47584-59.37152-80.56832-75.02848-72.0896-32.16384-158.6176-34.3552-158.6176-34.3552s-91.37152-4.92544-138.752-9.89184c-19.46624-2.03776-54.46656-9.58464-54.46656-9.58464-4.0448-0.84992-10.63936-1.19808-14.66368-0.44032 0 0-30.63808-0.07168-44.30848 43.35616-8.8576 28.11904 1.792 104.79616 1.792 104.79616 1.46432 12.27776-3.42016 30.21824-10.72128 40.20224 0 0-36.77184 46.03904-58.9312 100.34176-22.15936 54.30272 118.15936 145.05984 208.98816 205.27104C507.82208 613.89824 502.03648 743.424 502.03648 743.424s-74.19904-96.75776-194.00704-156.9792C188.2112 526.22336 150.30272 442.23488 150.30272 442.23488c-2.89792-5.45792-5.94944-4.94592-6.71744 1.21856 0 0-15.1552 91.61728 16.25088 147.8144 70.92224 126.88384 141.74208 112.88576 197.03808 183.27552s54.272 164.9152 54.272 164.9152-97.62816-141.29152-235.66336-205.55776c-91.53536-61.27616-74.7008-125.91104-85.49376-101.85728-10.79296 24.05376 26.73664 192.41984 65.40288 222.2592 80.57856 62.18752 94.16704 101.98016 94.16704 101.98016h175.53408S544.512 814.85824 572.928 725.73952c44.41088-139.30496 40.20224-191.26272 40.20224-191.26272 0.08192-8.22272 6.81984-14.19264 14.98112-13.29152 0 0 46.45888 4.64896 66.64192 9.68704 23.53152 5.86752 55.35744 26.20416 55.35744 26.20416 3.49184 2.14016 7.39328 0.68608 8.69376-3.1744l34.4576-102.77888c1.30048-3.8912-0.63488-5.51936-4.352-3.75808 0 0-45.37344 25.09824-88.17664 12.1856-20.15232-6.08256-59.60704-14.82752-74.69056-32.6656-16.95744-20.03968-15.59552-71.3728 26.66496-79.21664 48.0256-8.9088 33.13664 15.14496 65.91488 24.64768 27.42272 7.95648 22.29248-1.69984 26.69568 5.21216 1.67936 2.63168 0.38912 32.65536 0.38912 32.65536-0.21504 6.144 3.39968 7.84384 8.0384 3.79904l41.89184-36.46464c17.37728-11.24352 30.86336-0.57344 48.24064-11.81696 14.73536-9.53344 29.58336-43.66336 29.58336-43.66336 4.48512-9.24672 0.60416-20.41856-8.63232-24.92416l-42.58816-20.80768c-3.69664-1.80224-3.38944-3.26656 0.74752-3.26656h62.0032zM422.54336 123.87328s-49.88928 50.7392-74.5472 50.66752c-24.65792-0.07168-33.24928-56.12544-3.92192-56.12544 18.00192 0 76.32896 0.21504 76.32896 0.21504 4.13696 0.02048 5.0688 2.36544 2.14016 5.24288z m123.09504 249.64096s-3.31776-25.53856-33.16736-40.05888c-29.8496-14.52032-52.5312-41.13408-59.648-68.95616-12.1856-54.8864 48.29184-104.192 48.29184-104.192s-30.1056 73.6768 3.5328 106.60864c54.272 53.12512 40.99072 106.5984 40.99072 106.5984z m155.56608-164.46464c-7.94624 10.32192-9.60512 37.92896-59.2384 20.736-49.63328-17.2032-71.00416-54.5792-71.00416-54.5792-2.27328-3.40992-0.79872-6.49216 3.328-6.8096 0 0 43.55072-5.03808 80.06656 6.44096 24.91392 7.82336 54.79424 23.88992 46.848 34.21184z\" fill=\"#272636\" p-id=\"1943\"></path><path d=\"M366.30528 259.26656c1.05472-1.76128 1.51552-1.57696 1.19808 0.43008 0 0-7.55712 19.0464 22.8864 87.63392 27.0848 61.02016 87.49056 68.7104 118.66112 98.23232 55.808 52.82816 53.52448 123.45344 53.52448 123.45344s-25.82528-49.85856-85.98528-78.83776-92.6208-50.52416-127.7952-85.77024c-45.02528-45.12768 17.5104-145.14176 17.5104-145.14176zM500.0704 961.44384h134.49216s95.8464-138.07616 46.68416-291.25632c-16.19968-50.46272-43.45856-80.00512-43.45856-80.00512-5.18144-6.38976-8.89856-4.88448-8.448 3.34848 0 0 9.15456 99.80928-19.44576 194.00704-28.60032 94.208-109.824 173.90592-109.824 173.90592zM681.61536 956.8768h105.24672s22.38464-73.5232 16.61952-130.21184c-9.30816-91.57632-48.31232-132.72064-48.31232-132.72064-3.80928-4.77184-6.0928-3.67616-5.2224 2.38592 0 0 16.75264 89.1904-14.4896 150.1184-30.9248 60.30336-53.84192 110.42816-53.84192 110.42816zM869.30432 811.35616c-2.88768-2.9184-4.80256-1.95584-4.38272 2.10944 0 0 6.79936 44.99456-7.76192 81.22368-10.12736 25.1904-28.91776 58.60352-28.91776 58.60352h107.17184s5.34528-50.31936-19.89632-83.97824c-11.56096-23.99232-46.21312-57.9584-46.21312-57.9584z\" fill=\"#272636\" p-id=\"1944\"></path></svg>",
|
KeyIcon: DefaultIcon,
|
||||||
"page_size": "10",
|
KeyPageSize: "10",
|
||||||
"cookie_days": "7",
|
KeyCookieDays: "7",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"baihu/internal/constant"
|
||||||
"baihu/internal/middleware"
|
"baihu/internal/middleware"
|
||||||
"baihu/internal/services"
|
"baihu/internal/services"
|
||||||
"baihu/internal/utils"
|
"baihu/internal/utils"
|
||||||
@@ -10,10 +13,11 @@ import (
|
|||||||
|
|
||||||
type AuthController struct {
|
type AuthController struct {
|
||||||
userService *services.UserService
|
userService *services.UserService
|
||||||
|
settingsService *services.SettingsService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthController(userService *services.UserService) *AuthController {
|
func NewAuthController(userService *services.UserService, settingsService *services.SettingsService) *AuthController {
|
||||||
return &AuthController{userService: userService}
|
return &AuthController{userService: userService, settingsService: settingsService}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AuthController) Login(c *gin.Context) {
|
func (ac *AuthController) Login(c *gin.Context) {
|
||||||
@@ -33,15 +37,30 @@ func (ac *AuthController) Login(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取 cookie 过期天数
|
||||||
|
expireDays := 7
|
||||||
|
if days := ac.settingsService.Get(constant.SectionSite, constant.KeyCookieDays); days != "" {
|
||||||
|
if d, err := strconv.Atoi(days); err == nil && d > 0 {
|
||||||
|
expireDays = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 JWT Secret
|
||||||
|
jwtSecret := ac.settingsService.Get(constant.SectionSystem, constant.KeyJWTSecret)
|
||||||
|
if jwtSecret == "" {
|
||||||
|
utils.ServerError(c, "系统配置错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 生成 token
|
// 生成 token
|
||||||
token, err := utils.GenerateToken(user.ID, user.Username)
|
token, err := utils.GenerateToken(user.ID, user.Username, expireDays, jwtSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.ServerError(c, "登录失败")
|
utils.ServerError(c, "登录失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 Cookie
|
// 设置 Cookie
|
||||||
middleware.SetAuthCookie(c, token)
|
middleware.SetAuthCookie(c, token, expireDays)
|
||||||
|
|
||||||
utils.Success(c, gin.H{
|
utils.Success(c, gin.H{
|
||||||
"user": user.Username,
|
"user": user.Username,
|
||||||
|
|||||||
@@ -79,18 +79,18 @@ func (sc *SettingsController) CleanLogs(c *gin.Context) {
|
|||||||
|
|
||||||
// GetSiteSettings 获取站点设置
|
// GetSiteSettings 获取站点设置
|
||||||
func (sc *SettingsController) GetSiteSettings(c *gin.Context) {
|
func (sc *SettingsController) GetSiteSettings(c *gin.Context) {
|
||||||
settings := sc.settingsService.GetSection("site")
|
settings := sc.settingsService.GetSection(constant.SectionSite)
|
||||||
utils.Success(c, settings)
|
utils.Success(c, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicSiteSettings 获取公开的站点设置(无需认证)
|
// GetPublicSiteSettings 获取公开的站点设置(无需认证)
|
||||||
func (sc *SettingsController) GetPublicSiteSettings(c *gin.Context) {
|
func (sc *SettingsController) GetPublicSiteSettings(c *gin.Context) {
|
||||||
settings := sc.settingsService.GetSection("site")
|
settings := sc.settingsService.GetSection(constant.SectionSite)
|
||||||
// 只返回公开信息
|
// 只返回公开信息
|
||||||
utils.Success(c, gin.H{
|
utils.Success(c, gin.H{
|
||||||
"title": settings["title"],
|
constant.KeyTitle: settings[constant.KeyTitle],
|
||||||
"subtitle": settings["subtitle"],
|
constant.KeySubtitle: settings[constant.KeySubtitle],
|
||||||
"icon": settings["icon"],
|
constant.KeyIcon: settings[constant.KeyIcon],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,14 +110,14 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
values := map[string]string{
|
values := map[string]string{
|
||||||
"title": req.Title,
|
constant.KeyTitle: req.Title,
|
||||||
"subtitle": req.Subtitle,
|
constant.KeySubtitle: req.Subtitle,
|
||||||
"icon": req.Icon,
|
constant.KeyIcon: req.Icon,
|
||||||
"page_size": req.PageSize,
|
constant.KeyPageSize: req.PageSize,
|
||||||
"cookie_days": req.CookieDays,
|
constant.KeyCookieDays: req.CookieDays,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sc.settingsService.SetSection("site", values); err != nil {
|
if err := sc.settingsService.SetSection(constant.SectionSite, values); err != nil {
|
||||||
utils.ServerError(c, "保存失败")
|
utils.ServerError(c, "保存失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"baihu/internal/constant"
|
"baihu/internal/constant"
|
||||||
|
"baihu/internal/services"
|
||||||
"baihu/internal/utils"
|
"baihu/internal/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthRequired 认证中间件
|
// AuthRequired 认证中间件
|
||||||
func AuthRequired() gin.HandlerFunc {
|
func AuthRequired(settingsService *services.SettingsService) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
token, err := c.Cookie(constant.CookieName)
|
token, err := c.Cookie(constant.CookieName)
|
||||||
if err != nil || token == "" {
|
if err != nil || token == "" {
|
||||||
@@ -17,8 +18,16 @@ func AuthRequired() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取 JWT Secret
|
||||||
|
jwtSecret := settingsService.Get(constant.SectionSystem, constant.KeyJWTSecret)
|
||||||
|
if jwtSecret == "" {
|
||||||
|
utils.Unauthorized(c, "系统配置错误")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 验证 token
|
// 验证 token
|
||||||
userID, username, err := utils.ParseToken(token)
|
userID, username, err := utils.ParseToken(token, jwtSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Unauthorized(c, "登录已过期,请重新登录")
|
utils.Unauthorized(c, "登录已过期,请重新登录")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
@@ -32,9 +41,10 @@ func AuthRequired() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthCookie 设置认证 Cookie
|
// SetAuthCookie 设置认证 Cookie,expireDays 为过期天数
|
||||||
func SetAuthCookie(c *gin.Context, token string) {
|
func SetAuthCookie(c *gin.Context, token string, expireDays int) {
|
||||||
c.SetCookie(constant.CookieName, token, constant.CookieMaxAge, "/", "", false, true)
|
maxAge := 86400 * expireDays
|
||||||
|
c.SetCookie(constant.CookieName, token, maxAge, "/", "", false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAuthCookie 清除认证 Cookie
|
// ClearAuthCookie 清除认证 Cookie
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ import (
|
|||||||
|
|
||||||
var cronService *services.CronService
|
var cronService *services.CronService
|
||||||
|
|
||||||
func RegisterControllers() *Controllers {
|
func RegisterControllers() (*Controllers, *services.SettingsService) {
|
||||||
// Initialize services
|
// Initialize services
|
||||||
|
settingsService := services.NewSettingsService()
|
||||||
|
|
||||||
|
// 执行系统初始化(返回 userService)
|
||||||
|
initService := services.NewInitService(settingsService)
|
||||||
|
userService := initService.Initialize()
|
||||||
|
|
||||||
taskService := services.NewTaskService()
|
taskService := services.NewTaskService()
|
||||||
userService := services.NewUserService()
|
|
||||||
envService := services.NewEnvService()
|
envService := services.NewEnvService()
|
||||||
scriptService := services.NewScriptService()
|
scriptService := services.NewScriptService()
|
||||||
executorService := services.NewExecutorService(taskService)
|
executorService := services.NewExecutorService(taskService)
|
||||||
settingsService := services.NewSettingsService()
|
|
||||||
|
|
||||||
// 执行系统初始化
|
|
||||||
initService := services.NewInitService(settingsService, userService)
|
|
||||||
initService.Initialize()
|
|
||||||
|
|
||||||
// Initialize cron service
|
// Initialize cron service
|
||||||
cronService = services.NewCronService(taskService, executorService)
|
cronService = services.NewCronService(taskService, executorService)
|
||||||
@@ -28,7 +28,7 @@ func RegisterControllers() *Controllers {
|
|||||||
// Initialize and return controllers
|
// Initialize and return controllers
|
||||||
return &Controllers{
|
return &Controllers{
|
||||||
Task: controllers.NewTaskController(taskService, cronService),
|
Task: controllers.NewTaskController(taskService, cronService),
|
||||||
Auth: controllers.NewAuthController(userService),
|
Auth: controllers.NewAuthController(userService, settingsService),
|
||||||
Env: controllers.NewEnvController(envService),
|
Env: controllers.NewEnvController(envService),
|
||||||
Script: controllers.NewScriptController(scriptService),
|
Script: controllers.NewScriptController(scriptService),
|
||||||
Executor: controllers.NewExecutorController(executorService),
|
Executor: controllers.NewExecutorController(executorService),
|
||||||
@@ -37,7 +37,7 @@ func RegisterControllers() *Controllers {
|
|||||||
Log: controllers.NewLogController(),
|
Log: controllers.NewLogController(),
|
||||||
Terminal: controllers.NewTerminalController(),
|
Terminal: controllers.NewTerminalController(),
|
||||||
Settings: controllers.NewSettingsController(userService),
|
Settings: controllers.NewSettingsController(userService),
|
||||||
}
|
}, settingsService
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopCron stops the cron service gracefully
|
// StopCron stops the cron service gracefully
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"baihu/internal/controllers"
|
"baihu/internal/controllers"
|
||||||
"baihu/internal/middleware"
|
"baihu/internal/middleware"
|
||||||
|
"baihu/internal/services"
|
||||||
"baihu/internal/static"
|
"baihu/internal/static"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -40,7 +41,7 @@ func cacheControl(value string) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup(c *Controllers) *gin.Engine {
|
func Setup(c *Controllers, settingsService *services.SettingsService) *gin.Engine {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.Use(middleware.GinLogger(), middleware.GinRecovery())
|
router.Use(middleware.GinLogger(), middleware.GinRecovery())
|
||||||
@@ -94,7 +95,7 @@ func Setup(c *Controllers) *gin.Engine {
|
|||||||
|
|
||||||
// 需要认证的路由
|
// 需要认证的路由
|
||||||
authorized := api.Group("")
|
authorized := api.Group("")
|
||||||
authorized.Use(middleware.AuthRequired())
|
authorized.Use(middleware.AuthRequired(settingsService))
|
||||||
{
|
{
|
||||||
// 获取当前用户
|
// 获取当前用户
|
||||||
authorized.GET("/auth/me", c.Auth.GetCurrentUser)
|
authorized.GET("/auth/me", c.Auth.GetCurrentUser)
|
||||||
|
|||||||
@@ -58,11 +58,6 @@ func LoadConfig(path string) (*AppConfig, error) {
|
|||||||
constant.TablePrefix = Config.Database.TablePrefix
|
constant.TablePrefix = Config.Database.TablePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 JWT 密钥
|
|
||||||
if Config.Security.JWTSecret != "" {
|
|
||||||
constant.JWTSecret = Config.Security.JWTSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
return Config, nil
|
return Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,78 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"baihu/internal/logger"
|
"crypto/rand"
|
||||||
)
|
"encoding/hex"
|
||||||
|
|
||||||
const (
|
"baihu/internal/constant"
|
||||||
InitSection = "system"
|
"baihu/internal/logger"
|
||||||
InitKey = "initialized"
|
|
||||||
InitValue = "true"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type InitService struct {
|
type InitService struct {
|
||||||
settingsService *SettingsService
|
settingsService *SettingsService
|
||||||
userService *UserService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInitService(settingsService *SettingsService, userService *UserService) *InitService {
|
func NewInitService(settingsService *SettingsService) *InitService {
|
||||||
return &InitService{
|
return &InitService{
|
||||||
settingsService: settingsService,
|
settingsService: settingsService,
|
||||||
userService: userService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize 执行初始化,如果已初始化则跳过
|
// Initialize 执行系统初始化,返回 UserService
|
||||||
func (s *InitService) Initialize() {
|
func (s *InitService) Initialize() *UserService {
|
||||||
//if s.IsInitialized() {
|
|
||||||
// logger.Info("系统已初始化,跳过")
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
logger.Info("开始初始化系统...")
|
logger.Info("开始初始化系统...")
|
||||||
|
|
||||||
// 创建管理员账号
|
// 初始化默认设置
|
||||||
s.createAdminUser()
|
|
||||||
|
|
||||||
// 初始化默认设置(每次启动都检查)
|
|
||||||
if err := s.settingsService.InitSettings(); err != nil {
|
if err := s.settingsService.InitSettings(); err != nil {
|
||||||
logger.Warnf("初始化设置失败: %v", err)
|
logger.Warnf("初始化设置失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 标记为已初始化
|
// 初始化 JWT Secret(也用作密码 salt,必须在创建 UserService 之前)
|
||||||
//s.settingsService.Set(InitSection, InitKey, InitValue)
|
s.initJWTSecret()
|
||||||
//logger.Info("系统初始化完成")
|
|
||||||
|
// 创建 UserService(依赖 settingsService 获取 salt)
|
||||||
|
userService := NewUserService(s.settingsService)
|
||||||
|
// 创建管理员账号
|
||||||
|
s.initializeAdmin(userService)
|
||||||
|
|
||||||
|
return userService
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInitialized 检查是否已初始化
|
// initializeAdmin 创建管理员账号
|
||||||
func (s *InitService) IsInitialized() bool {
|
func (s *InitService) initializeAdmin(userService *UserService) {
|
||||||
return s.settingsService.Get(InitSection, InitKey) == InitValue
|
existingUser := userService.GetUserByUsername("admin")
|
||||||
}
|
|
||||||
|
|
||||||
// createAdminUser 创建管理员账号
|
|
||||||
func (s *InitService) createAdminUser() {
|
|
||||||
existingUser := s.userService.GetUserByUsername("admin")
|
|
||||||
if existingUser != nil {
|
if existingUser != nil {
|
||||||
logger.Info("管理员账号已存在,跳过创建")
|
logger.Info("管理员账号已存在,跳过创建")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.userService.CreateUser("admin", "123456", "admin@local", "admin")
|
userService.CreateUser("admin", "123456", "admin@local", "admin")
|
||||||
logger.Info("管理员账号创建成功: admin / 123456")
|
logger.Info("管理员账号创建成功: admin / 123456")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInitialized 检查是否已初始化
|
||||||
|
func (s *InitService) IsInitialized() bool {
|
||||||
|
return s.settingsService.Get(constant.SectionSystem, constant.KeyInitialized) == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// initJWTSecret 初始化 JWT Secret,如果不存在则生成随机值
|
||||||
|
func (s *InitService) initJWTSecret() {
|
||||||
|
existing := s.settingsService.Get(constant.SectionSystem, constant.KeyJWTSecret)
|
||||||
|
if existing != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 32 字节随机密钥
|
||||||
|
bytes := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
logger.Warnf("生成 JWT Secret 失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := hex.EncodeToString(bytes)
|
||||||
|
if err := s.settingsService.Set(constant.SectionSystem, constant.KeyJWTSecret, secret); err != nil {
|
||||||
|
logger.Warnf("保存 JWT Secret 失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info("JWT Secret 已生成")
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
|
"baihu/internal/constant"
|
||||||
"baihu/internal/database"
|
"baihu/internal/database"
|
||||||
"baihu/internal/models"
|
"baihu/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct{}
|
type UserService struct {
|
||||||
|
settingsService *SettingsService
|
||||||
|
}
|
||||||
|
|
||||||
func NewUserService() *UserService {
|
func NewUserService(settingsService *SettingsService) *UserService {
|
||||||
return &UserService{}
|
return &UserService{settingsService: settingsService}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us *UserService) hashPassword(password string) string {
|
func (us *UserService) hashPassword(password string) string {
|
||||||
salt := ""
|
// 使用 JWT Secret 作为密码 salt
|
||||||
if Config != nil {
|
salt := us.settingsService.Get(constant.SectionSystem, constant.KeyJWTSecret)
|
||||||
salt = Config.Security.PasswordSalt
|
|
||||||
}
|
|
||||||
hash := sha256.Sum256([]byte(password + salt))
|
hash := sha256.Sum256([]byte(password + salt))
|
||||||
return hex.EncodeToString(hash[:])
|
return hex.EncodeToString(hash[:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"baihu/internal/constant"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,24 +14,24 @@ type Claims struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateToken 生成 JWT token
|
// GenerateToken 生成 JWT token
|
||||||
func GenerateToken(userID uint, username string) (string, error) {
|
func GenerateToken(userID uint, username string, expireDays int, secret string) (string, error) {
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Username: username,
|
Username: username,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(constant.TokenExpireDays) * 24 * time.Hour)),
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expireDays) * 24 * time.Hour)),
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
return token.SignedString([]byte(constant.JWTSecret))
|
return token.SignedString([]byte(secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseToken 解析 JWT token
|
// ParseToken 解析 JWT token
|
||||||
func ParseToken(tokenString string) (uint, string, error) {
|
func ParseToken(tokenString string, secret string) (uint, string, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) {
|
||||||
return []byte(constant.JWTSecret), nil
|
return []byte(secret), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,18 +2,20 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||||
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
pageSize: number
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:page': [page: number]
|
'update:page': [page: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const totalPages = computed(() => Math.ceil(props.total / props.pageSize) || 1)
|
const { pageSize } = useSiteSettings()
|
||||||
|
|
||||||
|
const totalPages = computed(() => Math.ceil(props.total / pageSize.value) || 1)
|
||||||
|
|
||||||
function prevPage() {
|
function prevPage() {
|
||||||
if (props.page > 1) {
|
if (props.page > 1) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { api, type SiteSettings } from '@/api'
|
import { api, type SiteSettings } from '@/api'
|
||||||
|
|
||||||
const siteSettings = ref<SiteSettings>({
|
const siteSettings = ref<SiteSettings>({
|
||||||
@@ -27,6 +27,8 @@ function updateFavicon(svgContent: string) {
|
|||||||
link.href = url
|
link.href = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageSize = computed(() => parseInt(siteSettings.value.page_size) || 10)
|
||||||
|
|
||||||
export function useSiteSettings() {
|
export function useSiteSettings() {
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
if (loaded) return
|
if (loaded) return
|
||||||
@@ -48,6 +50,7 @@ export function useSiteSettings() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
siteSettings,
|
siteSettings,
|
||||||
|
pageSize,
|
||||||
loadSettings,
|
loadSettings,
|
||||||
refreshSettings,
|
refreshSettings,
|
||||||
updateFavicon
|
updateFavicon
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import Pagination from '@/components/Pagination.vue'
|
|||||||
import { Plus, Pencil, Trash2, Eye, EyeOff, Search } from 'lucide-vue-next'
|
import { Plus, Pencil, Trash2, Eye, EyeOff, Search } from 'lucide-vue-next'
|
||||||
import { api, type EnvVar } from '@/api'
|
import { api, type EnvVar } from '@/api'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
|
const { pageSize } = useSiteSettings()
|
||||||
|
|
||||||
const envVars = ref<EnvVar[]>([])
|
const envVars = ref<EnvVar[]>([])
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
@@ -21,7 +24,6 @@ const deleteEnvId = ref<number | null>(null)
|
|||||||
|
|
||||||
const filterName = ref('')
|
const filterName = ref('')
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ onMounted(loadEnvVars)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination :total="total" :page="currentPage" :page-size="pageSize" @update:page="handlePageChange" />
|
<Pagination :total="total" :page="currentPage" @update:page="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog v-model:open="showDialog">
|
<Dialog v-model:open="showDialog">
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import { RefreshCw, X, Search, Maximize2 } from 'lucide-vue-next'
|
|||||||
import { api, type TaskLog, type LogDetail } from '@/api'
|
import { api, type TaskLog, type LogDetail } from '@/api'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
import pako from 'pako'
|
import pako from 'pako'
|
||||||
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
|
const { pageSize } = useSiteSettings()
|
||||||
|
|
||||||
const logs = ref<TaskLog[]>([])
|
const logs = ref<TaskLog[]>([])
|
||||||
const selectedLog = ref<TaskLog | null>(null)
|
const selectedLog = ref<TaskLog | null>(null)
|
||||||
const logDetail = ref<LogDetail | null>(null)
|
const logDetail = ref<LogDetail | null>(null)
|
||||||
const filterKeyword = ref('')
|
const filterKeyword = ref('')
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ onMounted(loadLogs)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination :total="total" :page="currentPage" :page-size="pageSize" @update:page="handlePageChange" />
|
<Pagination :total="total" :page="currentPage" @update:page="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 日志详情侧边栏 -->
|
<!-- 日志详情侧边栏 -->
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import Pagination from '@/components/Pagination.vue'
|
|||||||
import { Plus, Play, Pencil, Trash2, Search } from 'lucide-vue-next'
|
import { Plus, Play, Pencil, Trash2, Search } from 'lucide-vue-next'
|
||||||
import { api, type Task } from '@/api'
|
import { api, type Task } from '@/api'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
|
const { pageSize } = useSiteSettings()
|
||||||
|
|
||||||
const tasks = ref<Task[]>([])
|
const tasks = ref<Task[]>([])
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
@@ -19,7 +22,6 @@ const deleteTaskId = ref<number | null>(null)
|
|||||||
|
|
||||||
const filterName = ref('')
|
const filterName = ref('')
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@ onMounted(loadTasks)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination :total="total" :page="currentPage" :page-size="pageSize" @update:page="handlePageChange" />
|
<Pagination :total="total" :page="currentPage" @update:page="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog v-model:open="showDialog">
|
<Dialog v-model:open="showDialog">
|
||||||
|
|||||||
Reference in New Issue
Block a user