优化结构

This commit is contained in:
Yvan
2026-08-21 00:10:27 +08:00
parent 746e33e027
commit 361a562f75
334 changed files with 382 additions and 379 deletions
+151
View File
@@ -0,0 +1,151 @@
package main
import (
"context"
"flag"
"log/slog"
"net"
"os"
"path/filepath"
"strings"
"kra/app/system/internal/conf"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/worker"
"kra/pkg/logging"
"github.com/go-kratos/kratos/v3"
"github.com/go-kratos/kratos/v3/config"
"github.com/go-kratos/kratos/v3/config/file"
"github.com/go-kratos/kratos/v3/log"
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
_ "go.uber.org/automaxprocs"
)
// go build -ldflags "-X main.Version=x.y.z"
var (
// Name is the name of the compiled software.
Name string
// Version is the version of the compiled software.
Version string
// flagconf is the config flag.
flagconf string
id, _ = os.Hostname()
)
func init() {
flag.StringVar(&flagconf, "conf", "../configs", "config path, eg: -conf config.yaml")
}
func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger) *kratos.App {
if audit != nil && loggerControl != nil {
loggerControl.SetErrorSink(logging.ErrorSinkFunc(func(ctx context.Context, entry logging.ErrorEntry) error {
return audit.CreateErrorRequest(ctx, &dto.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID})
}))
}
return kratos.New(
kratos.ID(id),
kratos.Name(Name),
kratos.Version(Version),
kratos.Metadata(map[string]string{}),
kratos.Logger(logger),
kratos.Server(
hs,
scheduler,
),
)
}
func zapSettings(admin *conf.AdminBackend) (string, logging.Options) {
options := logging.Options{Level: "info", Format: "json", EncodeLevel: "LowercaseLevelEncoder", LogInConsole: true, ShowLine: true, RetentionDay: 7}
root := "logs"
if admin == nil || admin.Zap == nil {
return root, options
}
zapConfig := admin.Zap
options = logging.Options{Level: zapConfig.Level, Format: zapConfig.Format, EncodeLevel: zapConfig.EncodeLevel, Prefix: zapConfig.Prefix, StacktraceKey: zapConfig.StacktraceKey, LogInConsole: zapConfig.LogInConsole, ShowLine: zapConfig.ShowLine, RetentionDay: int(zapConfig.RetentionDay), FileOnlyModules: zapConfig.FileOnlyModules}
if zapConfig.Director != "" {
root = zapConfig.Director
}
return root, options
}
func httpAddress(server *conf.Server) string {
if server != nil && server.Http != nil && server.Http.Addr != "" {
return server.Http.Addr
}
return ":8000"
}
func swaggerAddress(server *conf.Server, admin *conf.AdminBackend) string {
address := httpAddress(server)
if strings.HasPrefix(address, ":") {
address = "127.0.0.1" + address
} else if host, port, err := net.SplitHostPort(address); err == nil && (host == "" || host == "0.0.0.0" || host == "::") {
address = net.JoinHostPort("127.0.0.1", port)
}
prefix := ""
if admin != nil {
prefix = strings.Trim(admin.RouterPrefix, "/")
}
if prefix != "" {
prefix = "/" + prefix
}
return "http://" + address + prefix + "/swagger/index.html"
}
func main() {
flag.Parse()
c := config.New(
config.WithSource(
file.NewSource(flagconf),
),
)
defer c.Close()
if err := c.Load(); err != nil {
panic(err)
}
var bc conf.Bootstrap
if err := c.Scan(&bc); err != nil {
panic(err)
}
logRoot, logOptions := zapSettings(bc.Admin)
loggerAttrs := []any{slog.String("service.id", id), slog.String("service.name", Name), slog.String("service.version", Version)}
if bc.Admin != nil && bc.Admin.App != nil {
loggerAttrs = append(loggerAttrs, slog.String("node", bc.Admin.App.Node), slog.String("app_id", bc.Admin.App.AppId), slog.String("env", bc.Admin.App.Env))
}
logger, loggerControl := logging.NewReloadableZapLogger(logRoot, "application.log", logOptions, loggerAttrs...)
defer loggerControl.Close()
slog.SetDefault(logger)
log.SetDefault(logger)
if bc.Admin != nil {
bc.Admin.ConfigPath = flagconf
if info, err := os.Stat(flagconf); err == nil && info.IsDir() {
bc.Admin.ConfigPath = filepath.Join(flagconf, "config.yaml")
}
}
runtime := conf.NewRuntime(bc.Data, bc.Admin)
unsubscribeLogger := runtime.Subscribe(func(_ *conf.Data, admin *conf.AdminBackend) {
root, options := zapSettings(admin)
loggerControl.Reload(root, options)
})
defer unsubscribeLogger()
app, cleanup, err := wireApp(bc.Server, runtime, logger, loggerControl, Version)
if err != nil {
panic(err)
}
defer cleanup()
logger.Info("Kra administration service initialized", "mod", "system", "version", Version, "http_address", httpAddress(bc.Server), "swagger", swaggerAddress(bc.Server, bc.Admin), "admin_frontend", "http://127.0.0.1:8080")
// start and wait for stop signal
if err := app.Run(); err != nil {
panic(err)
}
}
+27
View File
@@ -0,0 +1,27 @@
package main
import (
"testing"
"kra/app/system/internal/conf"
)
func TestSwaggerAddress(t *testing.T) {
tests := []struct {
name string
server *conf.Server
admin *conf.AdminBackend
want string
}{
{name: "default", want: "http://127.0.0.1:8000/swagger/index.html"},
{name: "wildcard with prefix", server: &conf.Server{Http: &conf.Server_HTTP{Addr: "0.0.0.0:9000"}}, admin: &conf.AdminBackend{RouterPrefix: "/admin/"}, want: "http://127.0.0.1:9000/admin/swagger/index.html"},
{name: "host", server: &conf.Server{Http: &conf.Server_HTTP{Addr: "example.test:8080"}}, want: "http://example.test:8080/swagger/index.html"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := swaggerAddress(test.server, test.admin); got != test.want {
t.Fatalf("swaggerAddress() = %q, want %q", got, test.want)
}
})
}
}
+46
View File
@@ -0,0 +1,46 @@
//go:build wireinject
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
"log/slog"
"kra/app/system/internal/app"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/data"
"kra/app/system/internal/initialize"
"kra/app/system/internal/integration"
"kra/app/system/internal/integration/cache"
"kra/app/system/internal/server"
"kra/app/system/internal/service"
"kra/app/system/internal/worker"
"kra/pkg/logging"
platformtask "kra/pkg/task"
"github.com/go-kratos/kratos/v3"
"github.com/google/wire"
)
// wireApp init kratos application.
func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogger, string) (*kratos.App, func(), error) {
panic(wire.Build(
server.ProviderSet,
worker.ProviderSet,
app.Catalog,
app.TaskRegistry,
app.Runtime,
data.ProviderSet,
integration.ProviderSet,
initialize.ProviderSet,
wire.Bind(new(initialize.Backend), new(*data.Data)),
wire.Bind(new(cache.RedisProvider), new(*data.Data)),
wire.Bind(new(biz.TaskMethodRegistry), new(*platformtask.Registry)),
biz.ProviderSet,
service.ProviderSet,
newApp,
))
}
+156
View File
@@ -0,0 +1,156 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"github.com/go-kratos/kratos/v3"
"kra/app/system/internal/app"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/data"
"kra/app/system/internal/data/payment"
"kra/app/system/internal/data/repository"
"kra/app/system/internal/initialize"
"kra/app/system/internal/integration/cache"
"kra/app/system/internal/integration/email"
"kra/app/system/internal/integration/storage"
"kra/app/system/internal/server"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/handler"
"kra/app/system/internal/worker"
"kra/pkg/logging"
"log/slog"
)
import (
_ "go.uber.org/automaxprocs"
)
// Injectors from wire.go:
// wireApp init kratos application.
func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger, reloadableLogger *logging.ReloadableLogger, string2 string) (*kratos.App, func(), error) {
reloadable, err := storage.NewFileStorage(runtime)
if err != nil {
return nil, nil, err
}
catalog := app.Catalog()
dataData, cleanup, err := data.NewData(runtime, logger, reloadable, catalog)
if err != nil {
return nil, nil, err
}
authorityAccessRepo := system.NewAuthorityAccessRepo(dataData)
apiRepo := system.NewAPIRepo(dataData)
accessControlUsecase := biz.NewAccessControlUsecase(authorityAccessRepo, apiRepo)
accessControlService := service.NewAccessControlService(accessControlUsecase)
userRepo := system.NewUserRepo(dataData)
userUsecase := biz.NewUserUsecase(userRepo)
securityRepo := system.NewSecurityRepo(dataData)
bizCache := cache.New(dataData)
runtimeSettings := system.NewRuntimeSettings(runtime)
apiTokenRepo := system.NewAPITokenRepo(dataData)
tokenUsecase := biz.NewTokenUsecase(apiTokenRepo)
securityUsecase := biz.NewSecurityUsecase(securityRepo, bizCache, runtimeSettings, tokenUsecase)
tokenIssuer := system.NewTokenIssuer(runtimeSettings)
auditRecordRepo := system.NewAuditRecorderRepo(dataData)
authenticationUsecase := biz.NewAuthenticationUsecase(userUsecase, securityUsecase, tokenIssuer, auditRecordRepo)
authService := service.NewAuthService(authenticationUsecase)
securityService := service.NewSecurityService(securityUsecase)
auditRecorderUsecase := biz.NewAuditRecorderUsecase(auditRecordRepo)
auditRecorder := service.NewAuditRecorder(auditRecorderUsecase)
authorityUsecase := biz.NewAuthorityUsecase(authorityAccessRepo)
authorityService := service.NewAuthorityService(authorityUsecase)
authority := handler.NewAuthority(authorityService)
menuRepo := system.NewMenuRepo(dataData)
menuUsecase := biz.NewMenuUsecase(menuRepo)
menuService := service.NewMenuService(menuUsecase)
menu := handler.NewMenu(menuService)
apiUsecase := biz.NewAPIUsecase(apiRepo)
apiService := service.NewAPIService(apiUsecase, runtimeSettings)
api := handler.NewAPI(apiService)
permissionRepo := system.NewPermissionRepo(dataData)
permissionUsecase := biz.NewPermissionUsecase(permissionRepo)
permissionService := service.NewPermissionService(permissionUsecase)
permission := handler.NewPermission(permissionService)
departmentRepo := system.NewDepartmentRepo(dataData)
departmentUsecase := biz.NewDepartmentUsecase(departmentRepo)
departmentService := service.NewDepartmentService(departmentUsecase)
positionRepo := system.NewPositionRepo(dataData)
positionUsecase := biz.NewPositionUsecase(positionRepo)
positionService := service.NewPositionService(positionUsecase)
organization := handler.NewOrganization(departmentService, positionService)
announcementRepo := system.NewAnnouncementRepo(dataData)
announcementUsecase := biz.NewAnnouncementUsecase(announcementRepo)
announcementService := service.NewAnnouncementService(announcementUsecase)
announcement := handler.NewAnnouncement(announcementService)
emailRepo := email.NewEmailRepo(runtime)
emailUsecase := biz.NewEmailUsecase(emailRepo)
emailService := service.NewEmailService(emailUsecase)
handlerEmail := handler.NewEmail(emailService)
paymentRepo := payment.NewPaymentRepo(dataData)
paymentOrderRepo := payment.NewPaymentOrderRepo(dataData)
paymentUsecase := biz.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger)
paymentService := service.NewPaymentService(paymentUsecase)
handlerPayment := handler.NewPayment(paymentService)
taskRepo := system.NewTaskRepo(dataData)
registry := app.TaskRegistry(catalog)
taskUsecase := biz.NewTaskUsecaseWithRegistry(taskRepo, registry)
mediaRepo := system.NewMediaRepo(dataData)
mediaUsecase := biz.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings)
taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, mediaUsecase, runtime, registry)
taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger)
taskRuntime := worker.NewTaskRuntime(taskScheduler)
taskApplicationUsecase := biz.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
taskService := service.NewTaskService(taskApplicationUsecase)
task := handler.NewTask(taskService)
mediaService := service.NewMediaService(mediaUsecase, runtimeSettings)
media := handler.NewMedia(mediaService)
auditQueryRepo := system.NewAuditRepo(dataData)
auditUsecase := biz.NewAuditUsecase(auditQueryRepo)
auditService := service.NewAuditService(auditUsecase)
logFileRepo := system.NewLogFileRepo(dataData)
logViewerUsecase := biz.NewLogViewerUsecase(logFileRepo)
logViewerService := service.NewLogViewerService(logViewerUsecase)
audit := handler.NewAudit(auditService, auditRecorder, logViewerService, logger)
exportRepo := system.NewExportRepo(dataData)
exportUsecase := biz.NewExportUsecase(exportRepo)
exportService := service.NewExportService(exportUsecase, bizCache)
export := handler.NewExport(exportService)
versionRepo := system.NewVersionRepo(dataData)
versionUsecase := biz.NewVersionUsecase(versionRepo)
versionService := service.NewVersionService(versionUsecase)
version := handler.NewVersion(versionService)
dictionaryRepo := system.NewDictionaryRepo(dataData)
dictionaryUsecase := biz.NewDictionaryUsecase(dictionaryRepo)
dictionaryService := service.NewDictionaryService(dictionaryUsecase)
dictionary := handler.NewDictionary(dictionaryService)
parameterRepo := system.NewParameterRepo(dataData)
parameterUsecase := biz.NewParameterUsecase(parameterRepo)
parameterService := service.NewParameterService(parameterUsecase)
parameter := handler.NewParameter(parameterService)
tokenService := service.NewTokenService(tokenUsecase, tokenIssuer)
apiToken := handler.NewAPIToken(tokenService)
initializationRepo := initialize.NewRepo(dataData, catalog)
systemConfigUsecase := biz.NewSystemConfigUsecase(initializationRepo, taskRuntime)
systemConfigService := service.NewSystemConfigService(systemConfigUsecase, runtimeSettings)
systemConfig := handler.NewSystemConfig(systemConfigService, securityService)
public := handler.NewPublic(authService, systemConfigService, securityService)
userService := service.NewUserService(userUsecase, securityService)
user := handler.NewUser(userService, authService)
navigation := handler.NewNavigation(userService)
session := handler.NewSession(tokenService)
set := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session)
routes := server.NewRoutes(set)
taskMethods := worker.NewTaskMethods(taskUsecase, mediaUsecase, runtime)
moduleRuntime := app.Runtime(routes, taskMethods, registry)
engine := server.NewGinEngineWithRuntime(runtime, accessControlService, authService, securityService, auditRecorder, logger, string2, moduleRuntime)
httpServer := server.NewGinServer(confServer, engine)
kratosApp := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger)
return kratosApp, func() {
cleanup()
}, nil
}
+147
View File
@@ -0,0 +1,147 @@
server:
http:
network: tcp
addr: 0.0.0.0:8000
timeout: 600s
data:
database:
driver: mysql
source: root:Xu950329.@tcp(127.0.0.1:3306)/kra?charset=utf8mb4&parseTime=True&loc=Local
host: 127.0.0.1
port: "3306"
user: root
password: Xu950329.
name: kra
config: charset=utf8mb4&parseTime=True&loc=Local
path: ""
alias_name: ""
disable: false
prefix: ""
engine: InnoDB
log_mode: info
max_idle_conns: 10
max_open_conns: 100
conn_max_lifetime: 3600
singular: false
redis:
network: tcp
name: default
addr: 127.0.0.1:6379
password: ""
db: 0
use_cluster: false
cluster_addrs: []
read_timeout: 0.200s
write_timeout: 0.200s
# Additional databases use alias_name as the lookup key. Disabled entries
# remain available as configuration examples without opening connections.
database_list: []
# Example:
# database_list:
# - driver: mysql
# source: ""
# host: 127.0.0.1
# port: "3306"
# user: root
# password: ""
# name: business
# config: timeout=5s&parseTime=True&loc=Local&charset=utf8mb4
# path: ""
# alias_name: business
# disable: true
# prefix: ""
# engine: InnoDB
# log_mode: info
# max_idle_conns: 10
# max_open_conns: 100
# conn_max_lifetime: 3600
# singular: false
redis_list: []
# Example:
# redis_list:
# - network: tcp
# name: cache
# addr: 127.0.0.1:6379
# password: ""
# db: 0
# use_cluster: false
# cluster_addrs: []
# read_timeout: 0.2s
# write_timeout: 0.2s
mongo:
coll: ""
options: ""
database: ""
username: ""
password: ""
auth_source: ""
min_pool_size: "0"
max_pool_size: "100"
socket_timeout_ms: "0"
connect_timeout_ms: "0"
is_zap: false
hosts:
- host: ""
port: ""
admin:
# The administration system router prefix maps to this transport prefix.
router_prefix: ""
jwt:
# Production deployments must override this value with a private secret.
signing_key: 86a6eb31-46b3-4da9-ae79-85aeff3e699d
expires_time: 604800s
buffer_time: 86400s
issuer: kra
captcha:
key_long: 6
img_width: 240
img_height: 80
store_expiration: 180s
local:
store_path: uploads/file
path_prefix: uploads/file
media:
# Upload chunks are stored below .chunks in the selected storage backend.
session_ttl: 24
max_file_size: "0"
chunk_dir: uploads/chunks
system:
use_redis: false
use_multipoint: false
use_strict_auth: false
disable_auto_migrate: false
use_mongo: false
addr: 8000
iplimit_count: 0
iplimit_time: 0
zap:
level: info
prefix: '[kra] '
format: json
director: logs
encode_level: LowercaseLevelEncoder
stacktrace_key: stacktrace
show_line: true
log_in_console: true
retention_day: 7
access_req_body: true
access_resp_data: true
access_req_headers: true
access_log_max_bytes: 1024
file_only_modules: []
cors:
mode: whitelist
whitelist: []
# Example:
# whitelist:
# - allow_origin: https://admin.example.com
# allow_headers: Content-Type,Authorization,X-Token,X-User-Id
# allow_methods: POST,GET,PUT,DELETE,OPTIONS
# expose_headers: Content-Length,Content-Type
# allow_credentials: true
app:
node: ""
app_id: kra
env: development
disk_list:
- mount_point: /
+34
View File
@@ -0,0 +1,34 @@
// Package app is the composition root for the running administration service.
// It knows which business modules are enabled and wires their contributions
// into the shared platform. Individual modules do not import this package.
package app
import (
systemmodule "kra/app/system/internal/module"
systemserver "kra/app/system/internal/server"
systemworker "kra/app/system/internal/worker"
"kra/pkg/module"
platformtask "kra/pkg/task"
)
// Catalog lists the business modules enabled in this binary. Adding an order
// module means adding one Definition here; system initialization and runtime
// code consume the catalog without knowing that module's implementation.
func Catalog() module.Catalog {
return module.Catalog{Definitions: []module.Definition{systemmodule.Definition()}}
}
// TaskRegistry builds the process-wide registry from dependency-free module
// contributions. Dependency-bearing methods are added by their module runtime
// constructors after the usecases have been created.
func TaskRegistry(catalog module.Catalog) *platformtask.Registry {
registry := platformtask.NewRegistry()
registry.RegisterAll(catalog.TaskMethods())
return registry
}
// Runtime composes HTTP route contributors from the enabled modules.
func Runtime(systemRoutes *systemserver.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime {
platformtask.Apply(registry, systemTasks)
return module.NewRuntime(systemRoutes)
}
+2322
View File
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
syntax = "proto3";
package kratos.api;
import "google/protobuf/duration.proto";
option go_package = "kra/app/system/internal/conf;conf";
message Bootstrap {
Server server = 1;
Data data = 2;
AdminBackend admin = 3;
}
message Server {
message HTTP {
string network = 1;
string addr = 2;
google.protobuf.Duration timeout = 3;
}
HTTP http = 1;
}
message Data {
message Database {
string driver = 1;
string source = 2;
string host = 3;
string port = 4;
string user = 5;
string password = 6;
string name = 7;
string config = 8;
string path = 9;
string alias_name = 10;
bool disable = 11;
string prefix = 12;
string engine = 13;
string log_mode = 14;
int32 max_idle_conns = 15;
int32 max_open_conns = 16;
int32 conn_max_lifetime = 17;
bool singular = 18;
}
message Redis {
string network = 1;
string addr = 2;
google.protobuf.Duration read_timeout = 3;
google.protobuf.Duration write_timeout = 4;
string name = 5;
string password = 6;
int32 db = 7;
bool use_cluster = 8;
repeated string cluster_addrs = 9;
}
message MongoHost {
string host = 1;
string port = 2;
}
message Mongo {
string coll = 1;
string options = 2;
string database = 3;
string username = 4;
string password = 5;
string auth_source = 6;
uint64 min_pool_size = 7;
uint64 max_pool_size = 8;
int64 socket_timeout_ms = 9;
int64 connect_timeout_ms = 10;
bool is_zap = 11;
repeated MongoHost hosts = 12;
}
Database database = 1;
Redis redis = 2;
repeated Database database_list = 3;
repeated Redis redis_list = 4;
Mongo mongo = 5;
}
// AdminBackend contains settings for the administration HTTP transport.
message AdminBackend {
string router_prefix = 1;
JWT jwt = 2;
Captcha captcha = 3;
Local local = 4;
Email email = 5;
Storage storage = 6;
// ConfigPath is populated by the entrypoint and is not required in YAML.
string config_path = 7;
Media media = 8;
repeated Disk disk_list = 9;
System system = 10;
Zap zap = 11;
CORS cors = 12;
App app = 13;
message JWT {
string signing_key = 1;
google.protobuf.Duration expires_time = 2;
google.protobuf.Duration buffer_time = 3;
string issuer = 4;
}
message Captcha {
int32 key_long = 1;
int32 img_width = 2;
int32 img_height = 3;
google.protobuf.Duration store_expiration = 4;
}
message Local {
string store_path = 1;
string path_prefix = 2;
}
message Email {
string to = 1;
string from = 2;
string host = 3;
string secret = 4;
string nickname = 5;
int32 port = 6;
bool is_ssl = 7;
bool is_login_auth = 8;
}
message Media {
int32 session_ttl = 1;
int64 max_file_size = 2;
string chunk_dir = 3;
}
message Disk {
string mount_point = 1;
}
message System {
bool use_redis = 1;
bool use_multipoint = 2;
bool use_strict_auth = 3;
bool disable_auto_migrate = 4;
bool use_mongo = 5;
int32 addr = 6;
int32 iplimit_count = 7;
int32 iplimit_time = 8;
}
message Zap {
string level = 1;
string prefix = 2;
string format = 3;
string director = 4;
string encode_level = 5;
string stacktrace_key = 6;
bool show_line = 7;
bool log_in_console = 8;
int32 retention_day = 9;
bool access_req_body = 10;
bool access_resp_data = 11;
bool access_req_headers = 12;
int32 access_log_max_bytes = 13;
repeated string file_only_modules = 14;
}
message CORS {
string mode = 1;
repeated CORSRule whitelist = 2;
}
message CORSRule {
string allow_origin = 1;
string allow_methods = 2;
string allow_headers = 3;
string expose_headers = 4;
bool allow_credentials = 5;
}
message App {
string node = 1;
string app_id = 2;
string env = 3;
}
message Storage {
string type = 1;
Qiniu qiniu = 2;
ObjectStore aliyun_oss = 3;
ObjectStore huawei_obs = 4;
ObjectStore tencent_cos = 5;
ObjectStore aws_s3 = 6;
ObjectStore cloudflare_r2 = 7;
ObjectStore minio = 8;
}
message Qiniu {
string zone = 1;
string bucket = 2;
string base_url = 3;
string access_key = 4;
string secret_key = 5;
bool use_https = 6;
bool use_cdn_domains = 7;
}
// ObjectStore covers S3-compatible configuration used by public cloud
// providers and self-hosted MinIO.
message ObjectStore {
string endpoint = 1;
string region = 2;
string bucket = 3;
string access_key = 4;
string secret_key = 5;
string base_url = 6;
string path_prefix = 7;
bool use_ssl = 8;
bool force_path_style = 9;
string account_id = 10;
}
}
+128
View File
@@ -0,0 +1,128 @@
package conf
import (
"sync"
"sync/atomic"
"google.golang.org/protobuf/proto"
)
// Runtime owns the active immutable configuration snapshot. Callers always
// receive clones so a config file reload cannot race with request handling.
type Runtime struct {
snapshot atomic.Pointer[runtimeSnapshot]
mu sync.RWMutex
nextID uint64
listeners map[uint64]func(*Data, *AdminBackend)
}
type runtimeSnapshot struct {
data *Data
admin *AdminBackend
}
func NewRuntime(data *Data, admin *AdminBackend) *Runtime {
runtime := &Runtime{listeners: make(map[uint64]func(*Data, *AdminBackend))}
runtime.Replace(data, admin)
return runtime
}
func cloneData(value *Data) *Data {
if value == nil {
return nil
}
return proto.Clone(value).(*Data)
}
func cloneAdmin(value *AdminBackend) *AdminBackend {
if value == nil {
return nil
}
return proto.Clone(value).(*AdminBackend)
}
func (r *Runtime) Replace(data *Data, admin *AdminBackend) {
data = cloneData(data)
admin = cloneAdmin(admin)
r.snapshot.Store(&runtimeSnapshot{data: data, admin: admin})
r.mu.RLock()
listeners := make([]func(*Data, *AdminBackend), 0, len(r.listeners))
for _, listener := range r.listeners {
listeners = append(listeners, listener)
}
r.mu.RUnlock()
for _, listener := range listeners {
listener(cloneData(data), cloneAdmin(admin))
}
}
// Subscribe registers a callback invoked after every successful runtime
// configuration replacement. It is used by long-lived clients such as the
// logger and other long-lived clients that must follow runtime config changes.
func (r *Runtime) Subscribe(listener func(*Data, *AdminBackend)) func() {
if listener == nil {
return func() {}
}
r.mu.Lock()
r.nextID++
id := r.nextID
r.listeners[id] = listener
r.mu.Unlock()
return func() {
r.mu.Lock()
delete(r.listeners, id)
r.mu.Unlock()
}
}
func (r *Runtime) Data() *Data {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil
}
return cloneData(snapshot.data)
}
func (r *Runtime) Admin() *AdminBackend {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil
}
return cloneAdmin(snapshot.admin)
}
func (r *Runtime) Values() (*Data, *AdminBackend) {
snapshot := r.snapshot.Load()
if snapshot == nil {
return nil, nil
}
return cloneData(snapshot.data), cloneAdmin(snapshot.admin)
}
func (r *Runtime) ConfigPath() string {
snapshot := r.snapshot.Load()
if snapshot == nil || snapshot.admin == nil {
return ""
}
return snapshot.admin.ConfigPath
}
func (r *Runtime) UpdateDatabase(database *Data_Database) {
for {
current := r.snapshot.Load()
var data *Data
var admin *AdminBackend
if current != nil {
data = cloneData(current.data)
admin = cloneAdmin(current.admin)
}
if data == nil {
data = &Data{}
}
data.Database = proto.Clone(database).(*Data_Database)
next := &runtimeSnapshot{data: data, admin: admin}
if r.snapshot.CompareAndSwap(current, next) {
return
}
}
}
+41
View File
@@ -0,0 +1,41 @@
package conf
import (
"sync"
"testing"
)
func TestRuntimeSnapshotsAreImmutable(t *testing.T) {
runtime := NewRuntime(&Data{Database: &Data_Database{Name: "first"}}, &AdminBackend{RouterPrefix: "/api", Jwt: &AdminBackend_JWT{SigningKey: "secret"}})
admin := runtime.Admin()
admin.RouterPrefix = "/changed"
admin.Jwt.SigningKey = "changed"
data := runtime.Data()
data.Database.Name = "changed"
if got := runtime.Admin(); got.RouterPrefix != "/api" || got.Jwt.SigningKey != "secret" {
t.Fatalf("admin snapshot mutated: %+v", got)
}
if got := runtime.Data().Database.Name; got != "first" {
t.Fatalf("data snapshot mutated: %s", got)
}
}
func TestRuntimeConcurrentReplaceAndRead(t *testing.T) {
runtime := NewRuntime(&Data{Database: &Data_Database{Name: "initial"}}, &AdminBackend{Jwt: &AdminBackend_JWT{SigningKey: "initial"}})
var wait sync.WaitGroup
for worker := 0; worker < 8; worker++ {
wait.Add(1)
go func() {
defer wait.Done()
for index := 0; index < 500; index++ {
_ = runtime.Admin().Jwt.SigningKey
_ = runtime.Data().Database.Name
}
}()
}
for index := 0; index < 500; index++ {
runtime.Replace(&Data{Database: &Data_Database{Name: "next"}}, &AdminBackend{Jwt: &AdminBackend_JWT{SigningKey: "next"}})
}
wait.Wait()
}
@@ -1,7 +1,7 @@
package data
import (
"kra/internal/conf"
"kra/app/system/internal/conf"
"google.golang.org/protobuf/proto"
)
@@ -9,8 +9,8 @@ import (
"path/filepath"
"strconv"
"kra/app/system/integration/storage"
"kra/internal/conf"
"kra/app/system/internal/conf"
"kra/app/system/internal/integration/storage"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
@@ -11,10 +11,10 @@ import (
"github.com/google/wire"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
datapayment "kra/app/system/data/payment"
datasystem "kra/app/system/data/repository"
"kra/app/system/integration/storage"
"kra/internal/conf"
"kra/app/system/internal/conf"
datapayment "kra/app/system/internal/data/payment"
datasystem "kra/app/system/internal/data/repository"
"kra/app/system/internal/integration/storage"
"kra/pkg/module"
)
@@ -7,7 +7,7 @@ import (
"reflect"
"strings"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"gorm.io/gorm"
)
@@ -19,7 +19,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"kra/internal/conf"
"kra/app/system/internal/conf"
"kra/pkg/database/gormkit"
)
@@ -8,8 +8,8 @@ import (
"testing"
"time"
"kra/internal/logging"
"kra/pkg/database/gormkit"
"kra/pkg/logging"
"gorm.io/gorm/logger"
)
@@ -5,9 +5,9 @@ import (
"errors"
"fmt"
"kra/app/system/biz"
"kra/app/system/integration/storage"
"kra/internal/conf"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/integration/storage"
"github.com/google/uuid"
"google.golang.org/protobuf/encoding/protojson"
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
"kra/internal/conf"
"kra/app/system/internal/conf"
"gopkg.in/yaml.v3"
)
@@ -8,7 +8,7 @@ import (
"strings"
"time"
"kra/internal/conf"
"kra/app/system/internal/conf"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
@@ -7,8 +7,8 @@ import (
"strings"
"testing"
"kra/app/system/integration/storage"
"kra/internal/conf"
"kra/app/system/internal/conf"
"kra/app/system/internal/integration/storage"
"google.golang.org/protobuf/encoding/protojson"
"gopkg.in/yaml.v3"
@@ -1,8 +1,8 @@
package data
import (
datapayment "kra/app/system/data/payment"
datasystem "kra/app/system/data/repository"
datapayment "kra/app/system/internal/data/payment"
datasystem "kra/app/system/internal/data/repository"
"kra/pkg/database/migration"
"kra/pkg/module"
@@ -6,7 +6,7 @@ import (
"strings"
"time"
"kra/internal/conf"
"kra/app/system/internal/conf"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -11,8 +11,8 @@ import (
"strconv"
"strings"
"kra/app/system/biz"
datapayment "kra/app/system/integration/payment"
"kra/app/system/internal/biz"
datapayment "kra/app/system/internal/integration/payment"
"gorm.io/gorm"
)
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func TestSavePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) {
@@ -1,6 +1,6 @@
package payment
import "kra/app/system/utils/paymentutil"
import "kra/app/system/internal/utils/paymentutil"
func text(values map[string]any, key string) string {
value, _ := values[key].(string)
@@ -5,7 +5,7 @@ import (
"encoding/json"
"testing"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func TestMigrateSeedsPaymentProviders(t *testing.T) {
@@ -10,7 +10,7 @@ import (
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
)
@@ -5,7 +5,7 @@ import (
"testing"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func newPaymentOrderRepoForTest(t *testing.T) *paymentOrderRepo {
@@ -5,7 +5,7 @@ import (
"encoding/json"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/gormkit"
"kra/pkg/database/pagination"
@@ -4,7 +4,7 @@ import (
"context"
"testing"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func TestAnnouncementRepositoryKeepsRawIDQuerySemantics(t *testing.T) {
@@ -6,7 +6,7 @@ import (
"strconv"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
"gorm.io/gorm"
@@ -5,7 +5,7 @@ import (
"errors"
"strconv"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"github.com/casbin/casbin/v3"
casbinmodel "github.com/casbin/casbin/v3/model"
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"kra/app/system/biz"
"kra/internal/conf"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
)
func newPolicyTestData(t *testing.T) *Data {
@@ -3,7 +3,7 @@ package system
import (
"context"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"gorm.io/gorm"
)
@@ -5,7 +5,7 @@ import (
"errors"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
"gorm.io/gorm"
@@ -1,6 +1,6 @@
package system
import "kra/app/system/biz"
import "kra/app/system/internal/biz"
type auditQueryRepo struct{ data Provider }
type auditRecorderRepo struct{ data Provider }
@@ -4,7 +4,7 @@ import (
"context"
"errors"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"gorm.io/gorm"
)
@@ -5,8 +5,8 @@ import (
"errors"
"testing"
"kra/app/system/biz"
"kra/internal/conf"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"gorm.io/gorm"
)
@@ -4,7 +4,7 @@ import (
"context"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
"gorm.io/gorm"
@@ -7,7 +7,7 @@ import (
"strings"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"gorm.io/gorm"
)
@@ -7,7 +7,7 @@ import (
"strings"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
"gorm.io/gorm"
@@ -4,7 +4,7 @@ import (
"context"
"testing"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func TestDictionaryListPreservesPreloadCollectionShapes(t *testing.T) {
@@ -4,7 +4,7 @@ import (
"context"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"kra/pkg/database/pagination"
"gorm.io/gorm"
@@ -5,7 +5,7 @@ import (
"testing"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
func errorString(value string) *string { return &value }
@@ -7,7 +7,7 @@ import (
"fmt"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"kra/app/system/biz"
"kra/app/system/internal/biz"
"strconv"
"strings"
"time"
@@ -14,7 +14,7 @@ import (
"strings"
"time"
"kra/app/system/biz"
"kra/app/system/internal/biz"
)
const (
@@ -7,8 +7,8 @@ import (
"path/filepath"
"testing"
"kra/app/system/biz"
"kra/internal/conf"
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
)
func TestLogViewerReadsNestedCategoryFiles(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More