优化
This commit is contained in:
@@ -94,7 +94,7 @@ func (r *announcementRepo) UserOptions(ctx context.Context) ([]biz.UserOption, e
|
||||
Label string
|
||||
Value uint
|
||||
}
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Where("deleted_at IS NULL").Scan(&rows).Error
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au
|
||||
return err
|
||||
}
|
||||
}
|
||||
defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
|
||||
defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/base/login", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
|
||||
for _, item := range defaults {
|
||||
var api apiPO
|
||||
if err := tx.Where("path = ? AND method = ?", item.path, item.method).First(&api).Error; err == nil {
|
||||
@@ -186,13 +186,23 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
|
||||
})
|
||||
}
|
||||
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Updates(map[string]any{"authority_name": value.AuthorityName, "parent_id": value.ParentID, "data_scope": value.DataScope, "default_router": value.DefaultRouter}).Error
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var current authorityPO
|
||||
if err := db.Where("authority_id = ?", value.AuthorityID).First(¤t).Error; err != nil {
|
||||
return errors.New("查询角色数据失败")
|
||||
}
|
||||
updates := &authorityPO{AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
|
||||
return db.Model(¤t).Updates(updates).Error
|
||||
}
|
||||
func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||
if id == 888 {
|
||||
return errors.New("不能删除超级管理员")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var authority authorityPO
|
||||
if err := tx.Where("authority_id = ?", id).First(&authority).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("该角色不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
var users, children int64
|
||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Count(&users).Error; err != nil {
|
||||
return err
|
||||
@@ -221,15 +231,12 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&authorityPO{}, "authority_id = ?", id).Error
|
||||
})
|
||||
}
|
||||
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var allowed map[uint]bool
|
||||
|
||||
@@ -66,6 +66,16 @@ func (r *initializationRepo) ConfigurationJSON() (json.RawMessage, error) {
|
||||
}
|
||||
}
|
||||
maskDataSecrets(dataConfig)
|
||||
safeAdmin := cloneAdminConfig(adminConfig)
|
||||
if safeAdmin != nil {
|
||||
if safeAdmin.Jwt != nil {
|
||||
safeAdmin.Jwt.SigningKey = "******"
|
||||
}
|
||||
if safeAdmin.Email != nil {
|
||||
safeAdmin.Email.Secret = "******"
|
||||
}
|
||||
maskStorageSecrets(safeAdmin.Storage)
|
||||
}
|
||||
dataMap := map[string]any{}
|
||||
if dataConfig != nil {
|
||||
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(dataConfig)
|
||||
@@ -76,7 +86,9 @@ func (r *initializationRepo) ConfigurationJSON() (json.RawMessage, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return json.Marshal(map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}})
|
||||
config := gvaConfiguration(dataConfig, safeAdmin)
|
||||
config["admin"], config["email"], config["data"] = admin, email, dataMap
|
||||
return json.Marshal(map[string]any{"config": config})
|
||||
}
|
||||
|
||||
func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
|
||||
@@ -86,6 +98,9 @@ func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json
|
||||
}
|
||||
nextData := cloneDataConfig(currentData)
|
||||
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
||||
if err := applyGVAConfiguration(raw, nextData, nextAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
var value configurationEnvelope
|
||||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return err
|
||||
@@ -154,6 +169,13 @@ func cloneDataConfig(value *conf.Data) *conf.Data {
|
||||
return proto.Clone(value).(*conf.Data)
|
||||
}
|
||||
|
||||
func cloneAdminConfig(value *conf.AdminBackend) *conf.AdminBackend {
|
||||
if value == nil {
|
||||
return &conf.AdminBackend{}
|
||||
}
|
||||
return proto.Clone(value).(*conf.AdminBackend)
|
||||
}
|
||||
|
||||
func refreshDatabaseSources(value *conf.Data) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
@@ -222,6 +244,11 @@ func applyDataPatch(target, patch *conf.Data) {
|
||||
func applyAdminPatch(target, patch *conf.AdminBackend) {
|
||||
target.RouterPrefix = patch.RouterPrefix
|
||||
if patch.System != nil {
|
||||
if target.System != nil {
|
||||
patch.System.Addr = target.System.Addr
|
||||
patch.System.IplimitCount = target.System.IplimitCount
|
||||
patch.System.IplimitTime = target.System.IplimitTime
|
||||
}
|
||||
target.System = patch.System
|
||||
}
|
||||
if patch.Jwt != nil {
|
||||
@@ -234,6 +261,9 @@ func applyAdminPatch(target, patch *conf.AdminBackend) {
|
||||
target.Local = patch.Local
|
||||
}
|
||||
if patch.Media != nil {
|
||||
if patch.Media.ChunkDir == "" && target.Media != nil {
|
||||
patch.Media.ChunkDir = target.Media.ChunkDir
|
||||
}
|
||||
target.Media = patch.Media
|
||||
}
|
||||
if patch.Storage != nil {
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
@@ -84,6 +86,45 @@ func mergeYAMLNode(dst, src *yaml.Node) {
|
||||
dst.HeadComment, dst.LineComment, dst.FootComment = head, line, foot
|
||||
}
|
||||
|
||||
func yamlMappingValue(node *yaml.Node, keys ...string) *yaml.Node {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
||||
node = node.Content[0]
|
||||
}
|
||||
for _, key := range keys {
|
||||
if node.Kind != yaml.MappingNode {
|
||||
return nil
|
||||
}
|
||||
var next *yaml.Node
|
||||
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||
if node.Content[i].Value == key {
|
||||
next = node.Content[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if next == nil {
|
||||
return nil
|
||||
}
|
||||
node = next
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func setServerHTTPPort(document *yaml.Node, port int32) error {
|
||||
if port <= 0 {
|
||||
return nil
|
||||
}
|
||||
host := "0.0.0.0"
|
||||
if addr := yamlMappingValue(document, "server", "http", "addr"); addr != nil {
|
||||
if currentHost, _, err := net.SplitHostPort(addr.Value); err == nil && currentHost != "" {
|
||||
host = currentHost
|
||||
}
|
||||
}
|
||||
return setYAMLMapping(document, "server", map[string]any{"http": map[string]any{"addr": net.JoinHostPort(host, strconv.Itoa(int(port)))}})
|
||||
}
|
||||
|
||||
func (d *Data) persistConfig() error {
|
||||
dataConfig, adminConfig := d.runtime.Values()
|
||||
return d.persistConfigValues(dataConfig, adminConfig)
|
||||
@@ -122,6 +163,11 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
|
||||
if err = setYAMLMapping(&document, "admin", adminValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if adminConfig.System != nil {
|
||||
if err = setServerHTTPPort(&document, adminConfig.System.Addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return writeConfigDocument(configPath, &document)
|
||||
}
|
||||
|
||||
|
||||
@@ -108,12 +108,6 @@ func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||
if count > 0 {
|
||||
return errors.New("存在子部门,不允许删除")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该部门下存在用户,不允许删除")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +129,7 @@ func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Depa
|
||||
}
|
||||
func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||
var pos []departmentPO
|
||||
db := r.data.gormDB.WithContext(ctx).Order("sort,id")
|
||||
db := r.data.gormDB.WithContext(ctx).Order("sort")
|
||||
if name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
@@ -160,7 +154,7 @@ func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*b
|
||||
n := nodes[po.ID]
|
||||
if p := nodes[po.ParentID]; p != nil {
|
||||
p.Children = append(p.Children, n)
|
||||
} else {
|
||||
} else if po.ParentID == 0 {
|
||||
roots = append(roots, n)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -111,19 +111,18 @@ func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary
|
||||
return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error
|
||||
}
|
||||
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&dictionaryPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
var dictionary dictionaryPO
|
||||
if err := db.First(&dictionary, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("请不要搞事")
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("存在子字典不可删除")
|
||||
}
|
||||
if err := tx.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&dictionaryPO{}, id).Error
|
||||
})
|
||||
return err
|
||||
}
|
||||
if err := db.Delete(&dictionary).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error
|
||||
}
|
||||
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
||||
var po dictionaryPO
|
||||
|
||||
+2
-38
@@ -111,21 +111,7 @@ func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemp
|
||||
})
|
||||
}
|
||||
func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var tids []string
|
||||
if err := tx.Model(&exportTemplatePO{}).Where("id IN ?", ids).Pluck("template_id", &tids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tids) > 0 {
|
||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportConditionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportJoinPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Delete(&exportTemplatePO{}, ids).Error
|
||||
})
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&[]exportTemplatePO{}, "id IN ?", ids).Error
|
||||
}
|
||||
func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) {
|
||||
var po exportTemplatePO
|
||||
@@ -173,31 +159,9 @@ func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q
|
||||
if err := applyPagination(db, page, size, 0).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
templateIDs := make([]string, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
templateIDs = append(templateIDs, po.TemplateID)
|
||||
}
|
||||
var conditions []exportConditionPO
|
||||
var joins []exportJoinPO
|
||||
if len(templateIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("template_id IN ?", templateIDs).Find(&conditions).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("template_id IN ?", templateIDs).Find(&joins).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
conditionsByTemplate := make(map[string][]exportConditionPO, len(pos))
|
||||
for _, condition := range conditions {
|
||||
conditionsByTemplate[condition.TemplateID] = append(conditionsByTemplate[condition.TemplateID], condition)
|
||||
}
|
||||
joinsByTemplate := make(map[string][]exportJoinPO, len(pos))
|
||||
for _, join := range joins {
|
||||
joinsByTemplate[join.TemplateID] = append(joinsByTemplate[join.TemplateID], join)
|
||||
}
|
||||
out := make([]*biz.ExportTemplate, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, exportFromPO(po, conditionsByTemplate[po.TemplateID], joinsByTemplate[po.TemplateID]))
|
||||
out = append(out, exportFromPO(po, nil, nil))
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func gvaDatabase(value *conf.Data_Database) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
path := value.Host
|
||||
if value.Driver == "sqlite" {
|
||||
path = value.Path
|
||||
}
|
||||
return map[string]any{
|
||||
"prefix": value.Prefix, "port": value.Port, "config": value.Config,
|
||||
"db-name": value.Name, "username": value.User, "password": value.Password,
|
||||
"path": path, "engine": value.Engine, "log-mode": value.LogMode,
|
||||
"max-idle-conns": value.MaxIdleConns, "max-open-conns": value.MaxOpenConns,
|
||||
"conn-max-lifetime": value.ConnMaxLifetime, "singular": value.Singular,
|
||||
}
|
||||
}
|
||||
|
||||
func gvaRedis(value *conf.Data_Redis) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{"name": value.Name, "addr": value.Addr, "password": value.Password, "db": value.Db, "useCluster": value.UseCluster, "clusterAddrs": value.ClusterAddrs}
|
||||
}
|
||||
|
||||
func gvaObjectStore(value *conf.AdminBackend_ObjectStore) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"endpoint": value.Endpoint, "region": value.Region, "bucket": value.Bucket,
|
||||
"access-key": value.AccessKey, "secret-key": value.SecretKey, "base-url": value.BaseUrl,
|
||||
"path-prefix": value.PathPrefix, "use-ssl": value.UseSsl,
|
||||
"s3-force-path-style": value.ForcePathStyle, "account-id": value.AccountId,
|
||||
}
|
||||
}
|
||||
|
||||
func gvaObjectStores(storage *conf.AdminBackend_Storage) map[string]map[string]any {
|
||||
if storage == nil {
|
||||
storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
aliyun, huawei := storage.AliyunOss, storage.HuaweiObs
|
||||
tencent, aws := storage.TencentCos, storage.AwsS3
|
||||
cloudflare, minio := storage.CloudflareR2, storage.Minio
|
||||
if aliyun == nil {
|
||||
aliyun = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if huawei == nil {
|
||||
huawei = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if tencent == nil {
|
||||
tencent = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if aws == nil {
|
||||
aws = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if cloudflare == nil {
|
||||
cloudflare = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if minio == nil {
|
||||
minio = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
return map[string]map[string]any{
|
||||
"aliyun-oss": {"endpoint": aliyun.Endpoint, "access-key-id": aliyun.AccessKey, "access-key-secret": aliyun.SecretKey, "bucket-name": aliyun.Bucket, "bucket-url": aliyun.BaseUrl, "base-path": aliyun.PathPrefix},
|
||||
"hua-wei-obs": {"path": huawei.PathPrefix, "bucket": huawei.Bucket, "endpoint": huawei.Endpoint, "access-key": huawei.AccessKey, "secret-key": huawei.SecretKey},
|
||||
"tencent-cos": {"bucket": tencent.Bucket, "region": tencent.Region, "secret-id": tencent.AccessKey, "secret-key": tencent.SecretKey, "base-url": tencent.BaseUrl, "path-prefix": tencent.PathPrefix},
|
||||
"aws-s3": {"bucket": aws.Bucket, "region": aws.Region, "endpoint": aws.Endpoint, "secret-id": aws.AccessKey, "secret-key": aws.SecretKey, "base-url": aws.BaseUrl, "path-prefix": aws.PathPrefix, "s3-force-path-style": aws.ForcePathStyle, "disable-ssl": !aws.UseSsl},
|
||||
"cloudflare-r2": {"bucket": cloudflare.Bucket, "base-url": cloudflare.BaseUrl, "path": cloudflare.PathPrefix, "account-id": cloudflare.AccountId, "access-key-id": cloudflare.AccessKey, "secret-access-key": cloudflare.SecretKey},
|
||||
"minio": {"endpoint": minio.Endpoint, "access-key-id": minio.AccessKey, "access-key-secret": minio.SecretKey, "bucket-name": minio.Bucket, "use-ssl": minio.UseSsl, "base-path": minio.PathPrefix, "bucket-url": minio.BaseUrl},
|
||||
}
|
||||
}
|
||||
|
||||
func gvaConfiguration(data *conf.Data, admin *conf.AdminBackend) map[string]any {
|
||||
result := map[string]any{}
|
||||
if data == nil {
|
||||
data = &conf.Data{}
|
||||
}
|
||||
if admin == nil {
|
||||
admin = &conf.AdminBackend{}
|
||||
}
|
||||
jwt := admin.Jwt
|
||||
if jwt == nil {
|
||||
jwt = &conf.AdminBackend_JWT{}
|
||||
}
|
||||
result["jwt"] = map[string]any{"signing-key": jwt.SigningKey, "expires-time": durationString(jwt.ExpiresTime), "buffer-time": durationString(jwt.BufferTime), "issuer": jwt.Issuer}
|
||||
zap := admin.Zap
|
||||
if zap == nil {
|
||||
zap = &conf.AdminBackend_Zap{}
|
||||
}
|
||||
result["zap"] = map[string]any{
|
||||
"level": zap.Level, "prefix": zap.Prefix, "format": zap.Format, "director": zap.Director,
|
||||
"encode-level": zap.EncodeLevel, "stacktrace-key": zap.StacktraceKey, "show-line": zap.ShowLine,
|
||||
"log-in-console": zap.LogInConsole, "retention-day": zap.RetentionDay,
|
||||
"access-req-body": zap.AccessReqBody, "access-resp-data": zap.AccessRespData,
|
||||
"access-req-headers": zap.AccessReqHeaders, "access-log-max-bytes": zap.AccessLogMaxBytes,
|
||||
"file-only-modules": zap.FileOnlyModules,
|
||||
}
|
||||
result["redis"] = gvaRedis(data.Redis)
|
||||
redisList := make([]map[string]any, 0, len(data.RedisList))
|
||||
for _, item := range data.RedisList {
|
||||
redisList = append(redisList, gvaRedis(item))
|
||||
}
|
||||
result["redis-list"] = redisList
|
||||
mongo := data.Mongo
|
||||
if mongo == nil {
|
||||
mongo = &conf.Data_Mongo{}
|
||||
}
|
||||
hosts := make([]map[string]any, 0, len(mongo.Hosts))
|
||||
for _, host := range mongo.Hosts {
|
||||
if host != nil {
|
||||
hosts = append(hosts, map[string]any{"host": host.Host, "port": host.Port})
|
||||
}
|
||||
}
|
||||
result["mongo"] = map[string]any{"coll": mongo.Coll, "options": mongo.Options, "database": mongo.Database, "username": mongo.Username, "password": mongo.Password, "auth-source": mongo.AuthSource, "min-pool-size": mongo.MinPoolSize, "max-pool-size": mongo.MaxPoolSize, "socket-timeout-ms": mongo.SocketTimeoutMs, "connect-timeout-ms": mongo.ConnectTimeoutMs, "is-zap": mongo.IsZap, "hosts": hosts}
|
||||
email := admin.Email
|
||||
if email == nil {
|
||||
email = &conf.AdminBackend_Email{}
|
||||
}
|
||||
result["email"] = map[string]any{"to": email.To, "from": email.From, "host": email.Host, "secret": email.Secret, "nickname": email.Nickname, "port": email.Port, "is-ssl": email.IsSsl, "is-loginauth": email.IsLoginAuth}
|
||||
system := admin.System
|
||||
if system == nil {
|
||||
system = &conf.AdminBackend_System{}
|
||||
}
|
||||
storageType := "local"
|
||||
if admin.Storage != nil && admin.Storage.Type != "" {
|
||||
storageType = admin.Storage.Type
|
||||
}
|
||||
driver := ""
|
||||
if data.Database != nil {
|
||||
driver = data.Database.Driver
|
||||
}
|
||||
result["system"] = map[string]any{"db-type": driver, "oss-type": storageType, "router-prefix": admin.RouterPrefix, "addr": system.Addr, "iplimit-count": system.IplimitCount, "iplimit-time": system.IplimitTime, "use-multipoint": system.UseMultipoint, "use-redis": system.UseRedis, "use-mongo": system.UseMongo, "use-strict-auth": system.UseStrictAuth, "disable-auto-migrate": system.DisableAutoMigrate}
|
||||
for _, name := range []string{"mysql", "mssql", "pgsql", "oracle", "sqlite"} {
|
||||
result[name] = map[string]any{}
|
||||
}
|
||||
if driver != "" {
|
||||
result[driver] = gvaDatabase(data.Database)
|
||||
}
|
||||
dbList := make([]map[string]any, 0, len(data.DatabaseList))
|
||||
for _, item := range data.DatabaseList {
|
||||
row := gvaDatabase(item)
|
||||
if item != nil {
|
||||
row["type"], row["alias-name"], row["disable"] = item.Driver, item.AliasName, item.Disable
|
||||
}
|
||||
dbList = append(dbList, row)
|
||||
}
|
||||
result["db-list"] = dbList
|
||||
local := admin.Local
|
||||
if local == nil {
|
||||
local = &conf.AdminBackend_Local{}
|
||||
}
|
||||
result["local"] = map[string]any{"path": local.PathPrefix, "store-path": local.StorePath}
|
||||
media := admin.Media
|
||||
if media == nil {
|
||||
media = &conf.AdminBackend_Media{}
|
||||
}
|
||||
result["media"] = map[string]any{"session-ttl": media.SessionTtl, "max-file-size": media.MaxFileSize, "chunk-dir": media.ChunkDir}
|
||||
if admin.Storage == nil {
|
||||
admin.Storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
qiniu := admin.Storage.Qiniu
|
||||
if qiniu == nil {
|
||||
qiniu = &conf.AdminBackend_Qiniu{}
|
||||
}
|
||||
result["qiniu"] = map[string]any{"zone": qiniu.Zone, "bucket": qiniu.Bucket, "img-path": qiniu.BaseUrl, "access-key": qiniu.AccessKey, "secret-key": qiniu.SecretKey, "use-https": qiniu.UseHttps, "use-cdn-domains": qiniu.UseCdnDomains}
|
||||
for key, value := range gvaObjectStores(admin.Storage) {
|
||||
result[key] = value
|
||||
}
|
||||
disks := make([]map[string]any, 0, len(admin.DiskList))
|
||||
for _, disk := range admin.DiskList {
|
||||
if disk != nil {
|
||||
disks = append(disks, map[string]any{"mount-point": disk.MountPoint})
|
||||
}
|
||||
}
|
||||
result["disk-list"] = disks
|
||||
cors := admin.Cors
|
||||
if cors == nil {
|
||||
cors = &conf.AdminBackend_CORS{}
|
||||
}
|
||||
whitelist := make([]map[string]any, 0, len(cors.Whitelist))
|
||||
for _, rule := range cors.Whitelist {
|
||||
if rule != nil {
|
||||
whitelist = append(whitelist, map[string]any{"allow-origin": rule.AllowOrigin, "allow-methods": rule.AllowMethods, "allow-headers": rule.AllowHeaders, "expose-headers": rule.ExposeHeaders, "allow-credentials": rule.AllowCredentials})
|
||||
}
|
||||
}
|
||||
result["cors"] = map[string]any{"mode": cors.Mode, "whitelist": whitelist}
|
||||
result["app"] = map[string]any{"node": admin.GetApp().GetNode(), "app-id": admin.GetApp().GetAppId(), "env": admin.GetApp().GetEnv()}
|
||||
return result
|
||||
}
|
||||
|
||||
func objectMap(value any) map[string]any {
|
||||
result, _ := value.(map[string]any)
|
||||
return result
|
||||
}
|
||||
|
||||
func stringValue(values map[string]any, key string) string {
|
||||
value, _ := values[key].(string)
|
||||
return value
|
||||
}
|
||||
|
||||
func boolValue(values map[string]any, key string, fallback bool) bool {
|
||||
value, ok := values[key].(bool)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func int64Value(values map[string]any, key string, fallback int64) int64 {
|
||||
value, ok := values[key].(float64)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
return int64(value)
|
||||
}
|
||||
|
||||
func stringSliceValue(values map[string]any, key string, fallback []string) []string {
|
||||
items, ok := values[key].([]any)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if value, ok := item.(string); ok {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyGVARedis(target *conf.Data_Redis, values map[string]any) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Name, target.Addr = stringValue(values, "name"), stringValue(values, "addr")
|
||||
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||
target.Password = password
|
||||
}
|
||||
target.Db = int32(int64Value(values, "db", int64(target.Db)))
|
||||
target.UseCluster = boolValue(values, "useCluster", target.UseCluster)
|
||||
target.ClusterAddrs = stringSliceValue(values, "clusterAddrs", target.ClusterAddrs)
|
||||
}
|
||||
|
||||
func durationValue(raw string, fallback *durationpb.Duration) *durationpb.Duration {
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
value, err := parseGVADuration(raw)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return durationpb.New(value)
|
||||
}
|
||||
|
||||
func parseGVADuration(raw string) (time.Duration, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if value, err := time.ParseDuration(raw); err == nil {
|
||||
return value, nil
|
||||
}
|
||||
if index := strings.Index(raw, "d"); index >= 0 {
|
||||
days, _ := strconv.Atoi(raw[:index])
|
||||
value := time.Duration(days) * 24 * time.Hour
|
||||
remainder, err := time.ParseDuration(raw[index+1:])
|
||||
if err != nil {
|
||||
return value, nil
|
||||
}
|
||||
return value + remainder, nil
|
||||
}
|
||||
nanoseconds, err := strconv.ParseInt(raw, 10, 64)
|
||||
return time.Duration(nanoseconds), err
|
||||
}
|
||||
|
||||
func applyGVADatabase(target *conf.Data_Database, values map[string]any, driver string) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Driver = driver
|
||||
target.Prefix, target.Port, target.Config = stringValue(values, "prefix"), stringValue(values, "port"), stringValue(values, "config")
|
||||
target.Name, target.User = stringValue(values, "db-name"), stringValue(values, "username")
|
||||
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||
target.Password = password
|
||||
}
|
||||
if driver == "sqlite" {
|
||||
target.Path = stringValue(values, "path")
|
||||
} else {
|
||||
target.Host = stringValue(values, "path")
|
||||
}
|
||||
target.Engine, target.LogMode = stringValue(values, "engine"), stringValue(values, "log-mode")
|
||||
target.MaxIdleConns = int32(int64Value(values, "max-idle-conns", int64(target.MaxIdleConns)))
|
||||
target.MaxOpenConns = int32(int64Value(values, "max-open-conns", int64(target.MaxOpenConns)))
|
||||
target.ConnMaxLifetime = int32(int64Value(values, "conn-max-lifetime", int64(target.ConnMaxLifetime)))
|
||||
target.Singular = boolValue(values, "singular", target.Singular)
|
||||
}
|
||||
|
||||
func applyGVAObjectStore(target *conf.AdminBackend_ObjectStore, values map[string]any) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Endpoint, target.Region, target.Bucket = stringValue(values, "endpoint"), stringValue(values, "region"), stringValue(values, "bucket")
|
||||
if target.Bucket == "" {
|
||||
target.Bucket = stringValue(values, "bucket-name")
|
||||
}
|
||||
target.AccessKey = stringValue(values, "access-key")
|
||||
if target.AccessKey == "" {
|
||||
target.AccessKey = stringValue(values, "access-key-id")
|
||||
}
|
||||
if target.AccessKey == "" {
|
||||
target.AccessKey = stringValue(values, "secret-id")
|
||||
}
|
||||
secret := stringValue(values, "secret-key")
|
||||
if secret == "" {
|
||||
secret = stringValue(values, "access-key-secret")
|
||||
}
|
||||
if secret == "" {
|
||||
secret = stringValue(values, "secret-access-key")
|
||||
}
|
||||
if secret != "" && secret != "******" {
|
||||
target.SecretKey = secret
|
||||
}
|
||||
target.BaseUrl = stringValue(values, "base-url")
|
||||
if target.BaseUrl == "" {
|
||||
target.BaseUrl = stringValue(values, "bucket-url")
|
||||
}
|
||||
target.PathPrefix = stringValue(values, "path-prefix")
|
||||
if target.PathPrefix == "" {
|
||||
target.PathPrefix = stringValue(values, "base-path")
|
||||
}
|
||||
if target.PathPrefix == "" {
|
||||
target.PathPrefix = stringValue(values, "path")
|
||||
}
|
||||
target.UseSsl = boolValue(values, "use-ssl", !boolValue(values, "disable-ssl", !target.UseSsl))
|
||||
target.ForcePathStyle = boolValue(values, "s3-force-path-style", target.ForcePathStyle)
|
||||
target.AccountId = stringValue(values, "account-id")
|
||||
}
|
||||
|
||||
func applyGVAConfiguration(raw json.RawMessage, data *conf.Data, admin *conf.AdminBackend) error {
|
||||
var values map[string]any
|
||||
if err := json.Unmarshal(raw, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, isGVA := values["system"]; !isGVA {
|
||||
return nil
|
||||
}
|
||||
if data.Database == nil {
|
||||
data.Database = &conf.Data_Database{}
|
||||
}
|
||||
if admin.System == nil {
|
||||
admin.System = &conf.AdminBackend_System{}
|
||||
}
|
||||
if admin.Storage == nil {
|
||||
admin.Storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
system := objectMap(values["system"])
|
||||
driver := stringValue(system, "db-type")
|
||||
if driver == "" {
|
||||
driver = data.Database.Driver
|
||||
}
|
||||
applyGVADatabase(data.Database, objectMap(values[driver]), driver)
|
||||
admin.Storage.Type = stringValue(system, "oss-type")
|
||||
admin.RouterPrefix = stringValue(system, "router-prefix")
|
||||
admin.System.UseMultipoint = boolValue(system, "use-multipoint", admin.System.UseMultipoint)
|
||||
admin.System.UseRedis = boolValue(system, "use-redis", admin.System.UseRedis)
|
||||
admin.System.UseMongo = boolValue(system, "use-mongo", admin.System.UseMongo)
|
||||
admin.System.UseStrictAuth = boolValue(system, "use-strict-auth", admin.System.UseStrictAuth)
|
||||
admin.System.DisableAutoMigrate = boolValue(system, "disable-auto-migrate", admin.System.DisableAutoMigrate)
|
||||
admin.System.Addr = int32(int64Value(system, "addr", int64(admin.System.Addr)))
|
||||
admin.System.IplimitCount = int32(int64Value(system, "iplimit-count", int64(admin.System.IplimitCount)))
|
||||
admin.System.IplimitTime = int32(int64Value(system, "iplimit-time", int64(admin.System.IplimitTime)))
|
||||
if admin.Jwt == nil {
|
||||
admin.Jwt = &conf.AdminBackend_JWT{}
|
||||
}
|
||||
jwt := objectMap(values["jwt"])
|
||||
if secret := stringValue(jwt, "signing-key"); secret != "" && secret != "******" {
|
||||
admin.Jwt.SigningKey = secret
|
||||
}
|
||||
admin.Jwt.ExpiresTime = durationValue(stringValue(jwt, "expires-time"), admin.Jwt.ExpiresTime)
|
||||
admin.Jwt.BufferTime = durationValue(stringValue(jwt, "buffer-time"), admin.Jwt.BufferTime)
|
||||
admin.Jwt.Issuer = stringValue(jwt, "issuer")
|
||||
if admin.Zap == nil {
|
||||
admin.Zap = &conf.AdminBackend_Zap{}
|
||||
}
|
||||
zap := objectMap(values["zap"])
|
||||
admin.Zap.Level, admin.Zap.Prefix, admin.Zap.Format, admin.Zap.Director = stringValue(zap, "level"), stringValue(zap, "prefix"), stringValue(zap, "format"), stringValue(zap, "director")
|
||||
admin.Zap.EncodeLevel, admin.Zap.StacktraceKey = stringValue(zap, "encode-level"), stringValue(zap, "stacktrace-key")
|
||||
admin.Zap.ShowLine, admin.Zap.LogInConsole = boolValue(zap, "show-line", admin.Zap.ShowLine), boolValue(zap, "log-in-console", admin.Zap.LogInConsole)
|
||||
admin.Zap.RetentionDay = int32(int64Value(zap, "retention-day", int64(admin.Zap.RetentionDay)))
|
||||
admin.Zap.AccessReqBody = boolValue(zap, "access-req-body", admin.Zap.AccessReqBody)
|
||||
admin.Zap.AccessRespData = boolValue(zap, "access-resp-data", admin.Zap.AccessRespData)
|
||||
admin.Zap.AccessReqHeaders = boolValue(zap, "access-req-headers", admin.Zap.AccessReqHeaders)
|
||||
admin.Zap.AccessLogMaxBytes = int32(int64Value(zap, "access-log-max-bytes", int64(admin.Zap.AccessLogMaxBytes)))
|
||||
if list, ok := zap["file-only-modules"].([]any); ok {
|
||||
admin.Zap.FileOnlyModules = admin.Zap.FileOnlyModules[:0]
|
||||
for _, item := range list {
|
||||
if value, ok := item.(string); ok {
|
||||
admin.Zap.FileOnlyModules = append(admin.Zap.FileOnlyModules, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if data.Redis == nil {
|
||||
data.Redis = &conf.Data_Redis{}
|
||||
}
|
||||
applyGVARedis(data.Redis, objectMap(values["redis"]))
|
||||
if items, ok := values["redis-list"].([]any); ok {
|
||||
current := make(map[string]*conf.Data_Redis, len(data.RedisList))
|
||||
for _, item := range data.RedisList {
|
||||
if item != nil {
|
||||
current[item.Name] = item
|
||||
}
|
||||
}
|
||||
data.RedisList = make([]*conf.Data_Redis, 0, len(items))
|
||||
for _, item := range items {
|
||||
values := objectMap(item)
|
||||
target := current[stringValue(values, "name")]
|
||||
if target == nil {
|
||||
target = &conf.Data_Redis{}
|
||||
}
|
||||
applyGVARedis(target, values)
|
||||
data.RedisList = append(data.RedisList, target)
|
||||
}
|
||||
}
|
||||
if mongo := objectMap(values["mongo"]); mongo != nil {
|
||||
if data.Mongo == nil {
|
||||
data.Mongo = &conf.Data_Mongo{}
|
||||
}
|
||||
data.Mongo.Coll, data.Mongo.Options, data.Mongo.Database = stringValue(mongo, "coll"), stringValue(mongo, "options"), stringValue(mongo, "database")
|
||||
data.Mongo.Username, data.Mongo.AuthSource = stringValue(mongo, "username"), stringValue(mongo, "auth-source")
|
||||
if password := stringValue(mongo, "password"); password != "" && password != "******" {
|
||||
data.Mongo.Password = password
|
||||
}
|
||||
data.Mongo.MinPoolSize = uint64(int64Value(mongo, "min-pool-size", int64(data.Mongo.MinPoolSize)))
|
||||
data.Mongo.MaxPoolSize = uint64(int64Value(mongo, "max-pool-size", int64(data.Mongo.MaxPoolSize)))
|
||||
data.Mongo.SocketTimeoutMs = int64Value(mongo, "socket-timeout-ms", data.Mongo.SocketTimeoutMs)
|
||||
data.Mongo.ConnectTimeoutMs = int64Value(mongo, "connect-timeout-ms", data.Mongo.ConnectTimeoutMs)
|
||||
data.Mongo.IsZap = boolValue(mongo, "is-zap", data.Mongo.IsZap)
|
||||
if hosts, ok := mongo["hosts"].([]any); ok {
|
||||
data.Mongo.Hosts = make([]*conf.Data_MongoHost, 0, len(hosts))
|
||||
for _, item := range hosts {
|
||||
host := objectMap(item)
|
||||
data.Mongo.Hosts = append(data.Mongo.Hosts, &conf.Data_MongoHost{Host: stringValue(host, "host"), Port: stringValue(host, "port")})
|
||||
}
|
||||
}
|
||||
}
|
||||
if items, ok := values["db-list"].([]any); ok {
|
||||
current := make(map[string]*conf.Data_Database, len(data.DatabaseList))
|
||||
for _, item := range data.DatabaseList {
|
||||
if item != nil {
|
||||
current[item.AliasName] = item
|
||||
}
|
||||
}
|
||||
data.DatabaseList = make([]*conf.Data_Database, 0, len(items))
|
||||
for _, item := range items {
|
||||
row := objectMap(item)
|
||||
alias := stringValue(row, "alias-name")
|
||||
target := current[alias]
|
||||
if target == nil {
|
||||
target = &conf.Data_Database{}
|
||||
}
|
||||
applyGVADatabase(target, row, stringValue(row, "type"))
|
||||
target.AliasName, target.Disable = alias, boolValue(row, "disable", target.Disable)
|
||||
data.DatabaseList = append(data.DatabaseList, target)
|
||||
}
|
||||
}
|
||||
if admin.Email == nil {
|
||||
admin.Email = &conf.AdminBackend_Email{}
|
||||
}
|
||||
email := objectMap(values["email"])
|
||||
admin.Email.To, admin.Email.From, admin.Email.Host = stringValue(email, "to"), stringValue(email, "from"), stringValue(email, "host")
|
||||
admin.Email.Nickname = stringValue(email, "nickname")
|
||||
admin.Email.Port = int32(int64Value(email, "port", int64(admin.Email.Port)))
|
||||
admin.Email.IsSsl, admin.Email.IsLoginAuth = boolValue(email, "is-ssl", admin.Email.IsSsl), boolValue(email, "is-loginauth", admin.Email.IsLoginAuth)
|
||||
if secret := stringValue(email, "secret"); secret != "" && secret != "******" {
|
||||
admin.Email.Secret = secret
|
||||
}
|
||||
if admin.Local == nil {
|
||||
admin.Local = &conf.AdminBackend_Local{}
|
||||
}
|
||||
local := objectMap(values["local"])
|
||||
admin.Local.PathPrefix, admin.Local.StorePath = stringValue(local, "path"), stringValue(local, "store-path")
|
||||
if admin.Media == nil {
|
||||
admin.Media = &conf.AdminBackend_Media{}
|
||||
}
|
||||
media := objectMap(values["media"])
|
||||
admin.Media.SessionTtl = int32(int64Value(media, "session-ttl", int64(admin.Media.SessionTtl)))
|
||||
admin.Media.MaxFileSize = int64Value(media, "max-file-size", admin.Media.MaxFileSize)
|
||||
admin.Media.ChunkDir = stringValue(media, "chunk-dir")
|
||||
if admin.Storage.Qiniu == nil {
|
||||
admin.Storage.Qiniu = &conf.AdminBackend_Qiniu{}
|
||||
}
|
||||
qiniu := objectMap(values["qiniu"])
|
||||
admin.Storage.Qiniu.Zone, admin.Storage.Qiniu.Bucket, admin.Storage.Qiniu.BaseUrl = stringValue(qiniu, "zone"), stringValue(qiniu, "bucket"), stringValue(qiniu, "img-path")
|
||||
admin.Storage.Qiniu.AccessKey = stringValue(qiniu, "access-key")
|
||||
if secret := stringValue(qiniu, "secret-key"); secret != "" && secret != "******" {
|
||||
admin.Storage.Qiniu.SecretKey = secret
|
||||
}
|
||||
admin.Storage.Qiniu.UseHttps, admin.Storage.Qiniu.UseCdnDomains = boolValue(qiniu, "use-https", admin.Storage.Qiniu.UseHttps), boolValue(qiniu, "use-cdn-domains", admin.Storage.Qiniu.UseCdnDomains)
|
||||
stores := []struct {
|
||||
key string
|
||||
target **conf.AdminBackend_ObjectStore
|
||||
}{{"aliyun-oss", &admin.Storage.AliyunOss}, {"hua-wei-obs", &admin.Storage.HuaweiObs}, {"tencent-cos", &admin.Storage.TencentCos}, {"aws-s3", &admin.Storage.AwsS3}, {"cloudflare-r2", &admin.Storage.CloudflareR2}, {"minio", &admin.Storage.Minio}}
|
||||
for _, store := range stores {
|
||||
if *store.target == nil {
|
||||
*store.target = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
applyGVAObjectStore(*store.target, objectMap(values[store.key]))
|
||||
}
|
||||
if disks, ok := values["disk-list"].([]any); ok {
|
||||
admin.DiskList = make([]*conf.AdminBackend_Disk, 0, len(disks))
|
||||
for _, item := range disks {
|
||||
disk := objectMap(item)
|
||||
admin.DiskList = append(admin.DiskList, &conf.AdminBackend_Disk{MountPoint: stringValue(disk, "mount-point")})
|
||||
}
|
||||
}
|
||||
if cors := objectMap(values["cors"]); cors != nil {
|
||||
if admin.Cors == nil {
|
||||
admin.Cors = &conf.AdminBackend_CORS{}
|
||||
}
|
||||
admin.Cors.Mode = stringValue(cors, "mode")
|
||||
if rules, ok := cors["whitelist"].([]any); ok {
|
||||
admin.Cors.Whitelist = make([]*conf.AdminBackend_CORSRule, 0, len(rules))
|
||||
for _, item := range rules {
|
||||
rule := objectMap(item)
|
||||
admin.Cors.Whitelist = append(admin.Cors.Whitelist, &conf.AdminBackend_CORSRule{AllowOrigin: stringValue(rule, "allow-origin"), AllowMethods: stringValue(rule, "allow-methods"), AllowHeaders: stringValue(rule, "allow-headers"), ExposeHeaders: stringValue(rule, "expose-headers"), AllowCredentials: boolValue(rule, "allow-credentials", false)})
|
||||
}
|
||||
}
|
||||
}
|
||||
if app := objectMap(values["app"]); app != nil {
|
||||
if admin.App == nil {
|
||||
admin.App = &conf.AdminBackend_App{}
|
||||
}
|
||||
admin.App.Node, admin.App.AppId, admin.App.Env = stringValue(app, "node"), stringValue(app, "app-id"), stringValue(app, "env")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+344
-97
@@ -4,8 +4,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -14,86 +17,320 @@ import (
|
||||
"kra/internal/biz"
|
||||
)
|
||||
|
||||
func (r *logFileRepo) logRoot() (string, error) {
|
||||
const (
|
||||
defaultLogChunkLines = 500
|
||||
maxLogChunkBytes = 2 * 1024 * 1024
|
||||
logReadBlockSize = 64 * 1024
|
||||
)
|
||||
|
||||
func (r *logFileRepo) configuredLogRoot() (root string, exists bool, err error) {
|
||||
admin := r.data.runtime.Admin()
|
||||
if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
|
||||
return "", biz.ErrLogRootUnavailable
|
||||
return "", false, biz.ErrLogRootUnavailable
|
||||
}
|
||||
root, err := filepath.Abs(admin.Zap.Director)
|
||||
root, err = filepath.Abs(admin.Zap.Director)
|
||||
if err != nil {
|
||||
return "", errors.Join(biz.ErrLogRootUnavailable, err)
|
||||
return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
info, err := os.Stat(root)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return root, nil
|
||||
return root, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", errors.Join(biz.ErrLogRootUnavailable, err)
|
||||
return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", biz.ErrLogRootUnavailable
|
||||
return "", false, biz.ErrLogRootUnavailable
|
||||
}
|
||||
return root, nil
|
||||
return root, true, nil
|
||||
}
|
||||
func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||
if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month {
|
||||
return nil, biz.ErrInvalidLogMonth
|
||||
|
||||
func (r *logFileRepo) openConfiguredLogRoot() (root *os.Root, exists bool, err error) {
|
||||
rootPath, exists, err := r.configuredLogRoot()
|
||||
if err != nil || !exists {
|
||||
return nil, exists, err
|
||||
}
|
||||
root, err := r.logRoot()
|
||||
root, err = os.OpenRoot(rootPath)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
return root, true, nil
|
||||
}
|
||||
|
||||
func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||
if err := validateLogMonth(month); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries, err := os.ReadDir(root)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if !exists {
|
||||
return []biz.LogDate{}, nil
|
||||
}
|
||||
defer logRoot.Close()
|
||||
|
||||
entries, err := fs.ReadDir(logRoot.FS(), ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
out := []biz.LogDate{}
|
||||
result := make([]biz.LogDate, 0)
|
||||
for _, entry := range entries {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
if err = ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
|
||||
continue
|
||||
}
|
||||
if parsed, parseErr := time.Parse("2006-01-02", entry.Name()); parseErr != nil || parsed.Format("2006-01-02") != entry.Name() {
|
||||
if validateLogDate(entry.Name()) != nil {
|
||||
continue
|
||||
}
|
||||
count := 0
|
||||
_ = filepath.WalkDir(filepath.Join(root, entry.Name()), func(_ string, item fs.DirEntry, walkErr error) error {
|
||||
if walkErr == nil && !item.IsDir() && strings.EqualFold(filepath.Ext(item.Name()), ".log") {
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
count, countErr := countLogFiles(ctx, logRoot, entry.Name())
|
||||
if countErr != nil {
|
||||
return nil, countErr
|
||||
}
|
||||
if count > 0 {
|
||||
out = append(out, biz.LogDate{Date: entry.Name(), FileCount: count})
|
||||
result = append(result, biz.LogDate{Date: entry.Name(), FileCount: count})
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
|
||||
return out, nil
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].Date < result[j].Date })
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
||||
return nil, biz.ErrInvalidLogDate
|
||||
if err := validateLogDate(date); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err := r.logRoot()
|
||||
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dateRoot := filepath.Join(root, date)
|
||||
out := []biz.LogFile{}
|
||||
err = filepath.WalkDir(dateRoot, func(path string, entry fs.DirEntry, walkErr error) error {
|
||||
if !exists {
|
||||
return []biz.LogFile{}, nil
|
||||
}
|
||||
defer logRoot.Close()
|
||||
|
||||
info, err := logRoot.Lstat(date)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return []biz.LogFile{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return []biz.LogFile{}, nil
|
||||
}
|
||||
dateRoot, err := logRoot.OpenRoot(date)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
defer dateRoot.Close()
|
||||
|
||||
result := make([]biz.LogFile, 0)
|
||||
err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
if errors.Is(walkErr, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return walkErr
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 {
|
||||
if entry.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
|
||||
return nil
|
||||
}
|
||||
fileInfo, infoErr := entry.Info()
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
if fileInfo.Mode().IsRegular() {
|
||||
result = append(result, biz.LogFile{Path: path, Name: entry.Name(), Size: fileInfo.Size(), ModifiedAt: fileInfo.ModTime()})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].Path < result[j].Path })
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, cursor *int64) (*biz.LogContent, error) {
|
||||
result := &biz.LogContent{Date: date, Path: apiPath}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cursor != nil && *cursor < 0 {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
file, info, err := r.openValidatedLogFile(date, apiPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
end := info.Size()
|
||||
if cursor != nil && *cursor <= end {
|
||||
end = *cursor
|
||||
}
|
||||
start, limitedByBytes, err := findLogChunkStart(file, end)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
data, err := readLogRange(file, start, end)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
if int64(len(data)) < end-start {
|
||||
currentInfo, statErr := file.Stat()
|
||||
if statErr != nil || currentInfo.Size() < start {
|
||||
if statErr != nil {
|
||||
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr)
|
||||
}
|
||||
return nil, biz.ErrLogFileUnreadable
|
||||
}
|
||||
info = currentInfo
|
||||
}
|
||||
result.Content = string(data)
|
||||
result.LineCount = countLogicalLines(data)
|
||||
result.NextCursor = start
|
||||
result.HasMore = start > 0
|
||||
result.LimitedByBytes = limitedByBytes
|
||||
result.Size = info.Size()
|
||||
result.ModifiedAt = info.ModTime()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, info os.FileInfo, err error) {
|
||||
if err = validateLogDate(date); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
segments, err := validateLogAPIPath(apiPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil, biz.ErrLogFileNotFound
|
||||
}
|
||||
defer logRoot.Close()
|
||||
|
||||
dateInfo, err := logRoot.Lstat(date)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, nil, biz.ErrLogFileNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
if dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
|
||||
return nil, nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
dateRoot, err := logRoot.OpenRoot(date)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
defer dateRoot.Close()
|
||||
|
||||
relativePath := ""
|
||||
var validatedInfo os.FileInfo
|
||||
for index, segment := range segments {
|
||||
relativePath = filepath.Join(relativePath, segment)
|
||||
validatedInfo, err = dateRoot.Lstat(relativePath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, nil, biz.ErrLogFileNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
if validatedInfo.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
if index < len(segments)-1 && !validatedInfo.IsDir() {
|
||||
return nil, nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
}
|
||||
if validatedInfo == nil || !validatedInfo.Mode().IsRegular() {
|
||||
return nil, nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
file, err = dateRoot.Open(relativePath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, nil, biz.ErrLogFileNotFound
|
||||
}
|
||||
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
openedInfo, statErr := file.Stat()
|
||||
if statErr != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(validatedInfo, openedInfo) {
|
||||
file.Close()
|
||||
if statErr != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr)
|
||||
}
|
||||
return nil, nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
return file, openedInfo, nil
|
||||
}
|
||||
|
||||
func validateLogMonth(month string) error {
|
||||
parsed, err := time.Parse("2006-01", month)
|
||||
if err != nil || parsed.Format("2006-01") != month {
|
||||
return biz.ErrInvalidLogMonth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLogDate(date string) error {
|
||||
parsed, err := time.Parse("2006-01-02", date)
|
||||
if err != nil || parsed.Format("2006-01-02") != date {
|
||||
return biz.ErrInvalidLogDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLogAPIPath(apiPath string) ([]string, error) {
|
||||
if apiPath == "" || strings.Contains(apiPath, "\\") || strings.Contains(apiPath, ":") || pathpkg.IsAbs(apiPath) {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
segments := strings.Split(apiPath, "/")
|
||||
for _, segment := range segments {
|
||||
if segment == "" || segment == "." || segment == ".." {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
}
|
||||
if !strings.EqualFold(pathpkg.Ext(apiPath), ".log") {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
func countLogFiles(ctx context.Context, logRoot *os.Root, date string) (count int, err error) {
|
||||
dateInfo, err := logRoot.Lstat(date)
|
||||
if err != nil || dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
|
||||
return 0, err
|
||||
}
|
||||
dateRoot, err := logRoot.OpenRoot(date)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer dateRoot.Close()
|
||||
err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
if entry.Type()&os.ModeSymlink != 0 {
|
||||
if entry.IsDir() {
|
||||
@@ -108,70 +345,80 @@ func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile,
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
relative, _ := filepath.Rel(dateRoot, path)
|
||||
out = append(out, biz.LogFile{Path: filepath.ToSlash(relative), Name: entry.Name(), Size: info.Size(), ModifiedAt: info.ModTime()})
|
||||
if info.Mode().IsRegular() {
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
|
||||
return out, err
|
||||
}
|
||||
func (r *logFileRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
|
||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
||||
return nil, biz.ErrInvalidLogDate
|
||||
}
|
||||
root, err := r.logRoot()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func readLogRange(file *os.File, start, end int64) ([]byte, error) {
|
||||
if end <= start {
|
||||
return []byte{}, nil
|
||||
}
|
||||
data := make([]byte, int(end-start))
|
||||
n, err := file.ReadAt(data, start)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
dateRoot := filepath.Join(root, date)
|
||||
target := filepath.Clean(filepath.Join(dateRoot, filepath.FromSlash(path)))
|
||||
if target == dateRoot || !strings.HasPrefix(target, dateRoot+string(os.PathSeparator)) || !strings.EqualFold(filepath.Ext(target), ".log") {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
}
|
||||
info, err := os.Lstat(target)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, biz.ErrLogFileNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func findLogChunkStart(file *os.File, end int64) (start int64, limitedByBytes bool, err error) {
|
||||
if end <= 0 {
|
||||
return 0, false, nil
|
||||
}
|
||||
lastByte := []byte{0}
|
||||
if _, err = file.ReadAt(lastByte, end-1); err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
targetNewlines := defaultLogChunkLines
|
||||
if lastByte[0] == '\n' {
|
||||
targetNewlines++
|
||||
}
|
||||
position, scanned, newlines := end, int64(0), 0
|
||||
for position > 0 && scanned < maxLogChunkBytes {
|
||||
readSize := int64(logReadBlockSize)
|
||||
if readSize > position {
|
||||
readSize = position
|
||||
}
|
||||
if remaining := int64(maxLogChunkBytes) - scanned; readSize > remaining {
|
||||
readSize = remaining
|
||||
}
|
||||
blockStart := position - readSize
|
||||
block := make([]byte, int(readSize))
|
||||
n, readErr := file.ReadAt(block, blockStart)
|
||||
if readErr != nil && !errors.Is(readErr, io.EOF) {
|
||||
return 0, false, readErr
|
||||
}
|
||||
for index := n - 1; index >= 0; index-- {
|
||||
if block[index] == '\n' {
|
||||
newlines++
|
||||
if newlines == targetNewlines {
|
||||
return blockStart + int64(index) + 1, false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
scanned += int64(n)
|
||||
position = blockStart
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
||||
return nil, biz.ErrInvalidLogPath
|
||||
if position > 0 {
|
||||
return end - int64(maxLogChunkBytes), true, nil
|
||||
}
|
||||
end := info.Size()
|
||||
if cursor != nil && *cursor >= 0 && *cursor < end {
|
||||
end = *cursor
|
||||
}
|
||||
start := end - int64(2*1024*1024)
|
||||
limited := start > 0
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
file, err := os.Open(target)
|
||||
if err != nil {
|
||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
defer file.Close()
|
||||
data := make([]byte, end-start)
|
||||
n, err := file.ReadAt(data, start)
|
||||
if err != nil && n == 0 {
|
||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
||||
}
|
||||
data = data[:n]
|
||||
lines := bytes.Split(data, []byte("\n"))
|
||||
if len(lines) > 501 {
|
||||
drop := len(lines) - 501
|
||||
offset := 0
|
||||
for _, line := range lines[:drop] {
|
||||
offset += len(line) + 1
|
||||
}
|
||||
start += int64(offset)
|
||||
data = data[offset:]
|
||||
limited = true
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
func countLogicalLines(data []byte) int {
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
lineCount := bytes.Count(data, []byte("\n"))
|
||||
if len(data) > 0 && data[len(data)-1] != '\n' {
|
||||
lineCount++
|
||||
count := bytes.Count(data, []byte{'\n'})
|
||||
if data[len(data)-1] != '\n' {
|
||||
count++
|
||||
}
|
||||
return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: lineCount, NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (s *runtimeSettings) MediaSettings() biz.MediaSettings {
|
||||
if config == nil || config.Media == nil {
|
||||
return biz.MediaSettings{}
|
||||
}
|
||||
return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize}
|
||||
return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir}
|
||||
}
|
||||
|
||||
func (s *runtimeSettings) UseMultipoint() bool {
|
||||
@@ -110,5 +110,5 @@ func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) {
|
||||
return nil, biz.ErrTokenInvalid
|
||||
}
|
||||
}
|
||||
return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, Issuer: claims.Issuer, ExpiresAt: claims.ExpiresAt.Time}, nil
|
||||
return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, UserType: claims.UserType, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, Issuer: claims.Issuer, ExpiresAt: claims.ExpiresAt.Time}, nil
|
||||
}
|
||||
|
||||
@@ -95,7 +95,9 @@ func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int
|
||||
}
|
||||
items := make([]*biz.StoredFile, 0, len(result.Objects))
|
||||
for _, object := range result.Objects {
|
||||
items = append(items, s.file(object.Key, object.Size))
|
||||
item := s.file(object.Key, object.Size)
|
||||
item.LastModified = object.LastModified
|
||||
items = append(items, item)
|
||||
}
|
||||
next := ""
|
||||
if result.IsTruncated {
|
||||
|
||||
@@ -141,7 +141,11 @@ func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int)
|
||||
if object.Size != nil {
|
||||
size = *object.Size
|
||||
}
|
||||
items = append(items, s.file(*object.Key, size))
|
||||
item := s.file(*object.Key, size)
|
||||
if object.LastModified != nil {
|
||||
item.LastModified = *object.LastModified
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
next := ""
|
||||
if result.NextContinuationToken != nil {
|
||||
|
||||
@@ -94,7 +94,9 @@ func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int
|
||||
}
|
||||
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
||||
for _, object := range result.Contents {
|
||||
items = append(items, s.file(object.Key, object.Size))
|
||||
item := s.file(object.Key, object.Size)
|
||||
item.LastModified = object.LastModified
|
||||
items = append(items, item)
|
||||
}
|
||||
next := result.NextMarker
|
||||
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -200,7 +201,7 @@ func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size()})
|
||||
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size(), LastModified: info.ModTime(), ContentType: mime.TypeByExtension(filepath.Ext(entry.Name()))})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -120,7 +120,10 @@ func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit in
|
||||
}
|
||||
out := make([]*biz.StoredFile, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
out = append(out, s.file(entry.Key, entry.Fsize))
|
||||
item := s.file(entry.Key, entry.Fsize)
|
||||
item.LastModified = time.Unix(0, entry.PutTime*100)
|
||||
item.ContentType = entry.MimeType
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, marker, more, nil
|
||||
}
|
||||
|
||||
@@ -118,7 +118,10 @@ func (s *s3Storage) List(ctx context.Context, prefix, cursor string, limit int)
|
||||
if item.Err != nil {
|
||||
return nil, "", false, item.Err
|
||||
}
|
||||
out = append(out, s.file(item.Key, item.Size))
|
||||
file := s.file(item.Key, item.Size)
|
||||
file.LastModified = item.LastModified
|
||||
file.ContentType = item.ContentType
|
||||
out = append(out, file)
|
||||
if len(out) > limit {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cos "github.com/tencentyun/cos-go-sdk-v5"
|
||||
"kra/internal/biz"
|
||||
@@ -107,7 +108,14 @@ func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit
|
||||
}
|
||||
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
||||
for _, object := range result.Contents {
|
||||
items = append(items, s.file(object.Key, object.Size))
|
||||
item := s.file(object.Key, object.Size)
|
||||
for _, layout := range []string{time.RFC3339, "2006-01-02T15:04:05.000Z"} {
|
||||
if parsed, parseErr := time.Parse(layout, object.LastModified); parseErr == nil {
|
||||
item.LastModified = parsed
|
||||
break
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
next := result.NextMarker
|
||||
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
||||
|
||||
@@ -429,6 +429,9 @@ func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User
|
||||
}
|
||||
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("id = ?", id).Delete(&userPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -438,7 +441,7 @@ func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&userPO{}, id).Error
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string, clearMustChange bool) error {
|
||||
|
||||
Reference in New Issue
Block a user