数据源和 oss
This commit is contained in:
@@ -62,7 +62,7 @@ func redactJSON(raw []byte) string {
|
||||
}
|
||||
func operationAudit(svc *service.AuditService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.Method == "GET" || c.Request.Method == "HEAD" || c.Request.Method == "OPTIONS" {
|
||||
if isBootstrapPath(c.Request.URL.Path) || c.Request.Method == "GET" || c.Request.Method == "HEAD" || c.Request.Method == "OPTIONS" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
+35
-5
@@ -34,7 +34,8 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
engine.Use(ginRequestMeta(), emailErrorAlert(emails, logger), gin.Recovery(), securityRateLimit(svc, settings), operationAudit(audit))
|
||||
if config != nil && config.Local != nil && config.Local.StorePath != "" {
|
||||
localStorage := config == nil || config.Storage == nil || config.Storage.Type == "" || config.Storage.Type == "local"
|
||||
if localStorage && config != nil && config.Local != nil && config.Local.StorePath != "" {
|
||||
pathPrefix := "/" + strings.Trim(config.Local.PathPrefix, "/")
|
||||
if pathPrefix != "/" {
|
||||
engine.StaticFS(pathPrefix, filesOnly{FileSystem: http.Dir(config.Local.StorePath)})
|
||||
@@ -51,11 +52,11 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, svc *service.System
|
||||
})
|
||||
registerPublicRoutes(public, engine, config, svc, settings, access, audit)
|
||||
private := engine.Group(prefix)
|
||||
private.Use(jwtAuth(config, settings), accessControl(access, audit))
|
||||
private.Use(jwtAuth(config, settings), mustChangePasswordGuard(), accessControl(access, audit))
|
||||
registerPrivateRoutes(private, svc)
|
||||
registerAccessRoutes(private, engine, access)
|
||||
registerSettingsRoutes(private, public, settings)
|
||||
registerSystemConfigRoutes(private, config, settings)
|
||||
registerSystemConfigRoutes(private, config, svc, settings)
|
||||
registerVersionRoutes(private, versions)
|
||||
registerExportRoutes(private, public, svc, exports)
|
||||
registerAuditRoutes(private, public, audit)
|
||||
@@ -117,6 +118,22 @@ func currentClaims(c *gin.Context) *adminauth.Claims {
|
||||
return result
|
||||
}
|
||||
|
||||
func mustChangePasswordGuard() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims := currentClaims(c)
|
||||
if claims == nil || !claims.MustChangePwd {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path := strings.TrimSuffix(c.Request.URL.Path, "/")
|
||||
if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.AbortWithStatusJSON(http.StatusConflict, response{Code: codePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"})
|
||||
}
|
||||
}
|
||||
|
||||
func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *conf.AdminBackend, svc *service.SystemService, settings *service.SettingsService, access *service.AccessService, audit *service.AuditService) {
|
||||
expiration := 3 * time.Minute
|
||||
keyLong, width, height := 6, 240, 80
|
||||
@@ -221,8 +238,13 @@ func registerPublicRoutes(group *gin.RouterGroup, engine *gin.Engine, config *co
|
||||
writeResult(c, codeSuccess, gin.H{"needInit": !initialized}, message)
|
||||
})
|
||||
initGroup.POST("/initdb", func(c *gin.Context) {
|
||||
if err := svc.Initialize(c.Request.Context()); err != nil {
|
||||
fail(c, "自动创建数据库失败")
|
||||
var input service.DatabaseInit
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
fail(c, "数据库初始化参数无效")
|
||||
return
|
||||
}
|
||||
if err := svc.Initialize(c.Request.Context(), &input); err != nil {
|
||||
fail(c, "自动创建数据库失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
routes := engine.Routes()
|
||||
@@ -261,6 +283,10 @@ func incrementCached(ctx context.Context, svc *service.SystemService, key string
|
||||
}
|
||||
func securityRateLimit(svc *service.SystemService, settings *service.SettingsService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if isBootstrapPath(c.Request.URL.Path) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
cfg, err := settings.CurrentSecurity(c.Request.Context())
|
||||
if err != nil || cfg == nil || !cfg.LimitEnable {
|
||||
c.Next()
|
||||
@@ -281,6 +307,10 @@ func securityRateLimit(svc *service.SystemService, settings *service.SettingsSer
|
||||
}
|
||||
}
|
||||
|
||||
func isBootstrapPath(path string) bool {
|
||||
return strings.HasSuffix(path, "/health") || strings.Contains(path, "/init/")
|
||||
}
|
||||
|
||||
type captchaStore struct {
|
||||
service *service.SystemService
|
||||
expiration time.Duration
|
||||
|
||||
@@ -3,15 +3,19 @@ package server
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, settings *service.SettingsService) {
|
||||
func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBackend, systemService *service.SystemService, settings *service.SettingsService) {
|
||||
security := group.Group("/securityConfig")
|
||||
security.GET("/getSecurityConfig", func(c *gin.Context) {
|
||||
value, err := settings.Security(c.Request.Context())
|
||||
@@ -53,6 +57,11 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken
|
||||
if config.Email != nil {
|
||||
email = gin.H{"to": config.Email.To, "from": config.Email.From, "host": config.Email.Host, "secret": "******", "nickname": config.Email.Nickname, "port": config.Email.Port, "is-ssl": config.Email.IsSsl, "is-loginauth": config.Email.IsLoginAuth}
|
||||
}
|
||||
if config.Storage != nil {
|
||||
storage := proto.Clone(config.Storage).(*conf.AdminBackend_Storage)
|
||||
maskStorageSecrets(storage)
|
||||
admin["storage"] = storage
|
||||
}
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"config": gin.H{"admin": admin, "email": email}}, "获取成功")
|
||||
})
|
||||
@@ -69,6 +78,7 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken
|
||||
StorePath string `json:"storePath"`
|
||||
PathPrefix string `json:"pathPrefix"`
|
||||
} `json:"local"`
|
||||
Storage *conf.AdminBackend_Storage `json:"storage"`
|
||||
} `json:"admin"`
|
||||
Email *struct {
|
||||
To string `json:"to"`
|
||||
@@ -104,6 +114,10 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken
|
||||
config.Local.PathPrefix = req.Config.Admin.Local.PathPrefix
|
||||
}
|
||||
}
|
||||
if req.Config.Admin.Storage != nil {
|
||||
preserveStorageSecrets(req.Config.Admin.Storage, config.Storage)
|
||||
config.Storage = req.Config.Admin.Storage
|
||||
}
|
||||
if config.Email != nil && req.Config.Email != nil {
|
||||
config.Email.To = req.Config.Email.To
|
||||
config.Email.From = req.Config.Email.From
|
||||
@@ -117,12 +131,18 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken
|
||||
}
|
||||
}
|
||||
}
|
||||
ok(c)
|
||||
if err := systemService.PersistConfig(c.Request.Context()); err != nil {
|
||||
fail(c, "配置保存失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
writeResult(c, codeSuccess, gin.H{"restartRequired": true}, "配置已保存,重启服务后完整生效")
|
||||
})
|
||||
system.POST("/reloadSystem", func(c *gin.Context) {
|
||||
writeResult(c, codeSuccess, gin.H{"reload": false, "restartRequired": true}, "配置已在当前进程部分生效;路由与存储配置需重启服务")
|
||||
})
|
||||
system.POST("/reloadSystem", func(c *gin.Context) { writeResult(c, codeSuccess, gin.H{"reload": true}, "配置已热更新") })
|
||||
system.POST("/getServerInfo", func(c *gin.Context) {
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
cpuPercent, _ := cpu.Percent(200*time.Millisecond, false)
|
||||
memory, _ := mem.VirtualMemory()
|
||||
disk := []gin.H{}
|
||||
var stat syscall.Statfs_t
|
||||
if syscall.Statfs(".", &stat) == nil {
|
||||
@@ -135,7 +155,42 @@ func registerSystemConfigRoutes(group *gin.RouterGroup, config *conf.AdminBacken
|
||||
}
|
||||
disk = append(disk, gin.H{"mountPoint": ".", "usedMb": used / 1024 / 1024, "usedGb": used / 1024 / 1024 / 1024, "totalMb": total / 1024 / 1024, "totalGb": total / 1024 / 1024 / 1024, "usedPercent": percent})
|
||||
}
|
||||
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": []float64{}, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": mem.Alloc / 1024 / 1024, "totalMb": mem.Sys / 1024 / 1024, "usedPercent": 0}, "disk": disk}
|
||||
usedMB, totalMB, memoryPercent := uint64(0), uint64(0), float64(0)
|
||||
if memory != nil {
|
||||
usedMB, totalMB, memoryPercent = memory.Used/1024/1024, memory.Total/1024/1024, memory.UsedPercent
|
||||
}
|
||||
server := gin.H{"os": gin.H{"goos": runtime.GOOS, "numCpu": runtime.NumCPU(), "compiler": runtime.Compiler, "goVersion": runtime.Version(), "numGoroutine": runtime.NumGoroutine()}, "cpu": gin.H{"cpus": cpuPercent, "cores": runtime.NumCPU()}, "ram": gin.H{"usedMb": usedMB, "totalMb": totalMB, "usedPercent": memoryPercent}, "disk": disk}
|
||||
writeResult(c, codeSuccess, gin.H{"server": server}, "获取成功")
|
||||
})
|
||||
}
|
||||
|
||||
func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore {
|
||||
if storage == nil {
|
||||
return nil
|
||||
}
|
||||
return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio}
|
||||
}
|
||||
func maskStorageSecrets(storage *conf.AdminBackend_Storage) {
|
||||
if storage.Qiniu != nil {
|
||||
storage.Qiniu.SecretKey = "******"
|
||||
}
|
||||
for _, item := range objectStores(storage) {
|
||||
if item != nil {
|
||||
item.SecretKey = "******"
|
||||
}
|
||||
}
|
||||
}
|
||||
func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) {
|
||||
if next == nil || current == nil {
|
||||
return
|
||||
}
|
||||
if next.Qiniu != nil && current.Qiniu != nil && (next.Qiniu.SecretKey == "" || next.Qiniu.SecretKey == "******") {
|
||||
next.Qiniu.SecretKey = current.Qiniu.SecretKey
|
||||
}
|
||||
nextItems, currentItems := objectStores(next), objectStores(current)
|
||||
for i := range nextItems {
|
||||
if nextItems[i] != nil && currentItems[i] != nil && (nextItems[i].SecretKey == "" || nextItems[i].SecretKey == "******") {
|
||||
nextItems[i].SecretKey = currentItems[i].SecretKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user