优化结构
This commit is contained in:
parent
0b6b8ddf1a
commit
acaee94858
|
|
@ -9,7 +9,8 @@ agents must follow when changing the template.
|
|||
api/<domain>/<version>/ Proto sources and generated stubs. Public contract.
|
||||
cmd/<app>/ Entrypoint, Wire injector, main.go.
|
||||
configs/ Runtime config (config.yaml). No secrets.
|
||||
internal/conf/ Config proto; generated by `make config`.
|
||||
internal/config/ Viper config models, loading, snapshots, and reloads.
|
||||
internal/global/ Process-wide shared resource registry.
|
||||
internal/server/ HTTP/gRPC server wiring.
|
||||
internal/service/ Transport adapters; one file per resource.
|
||||
internal/biz/ Domain models, usecases, repo interfaces, errors.
|
||||
|
|
@ -129,7 +130,7 @@ tests exercise repo implementations at the storage boundary.
|
|||
|
||||
## Generation & generated files
|
||||
|
||||
Regenerate via `make api`, `make config`, or `make all`; never hand-edit
|
||||
Regenerate via `make api` or `make all`; never hand-edit
|
||||
`*.pb.go`, `*_grpc.pb.go`, `*_http.pb.go`, or `wire_gen.go`.
|
||||
|
||||
## Naming & error reasons
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ agents must follow when changing the template.
|
|||
api/<domain>/<version>/ Proto sources and generated stubs. Public contract.
|
||||
cmd/<app>/ Entrypoint, Wire injector, main.go.
|
||||
configs/ Runtime config (config.yaml). No secrets.
|
||||
internal/conf/ Config proto; generated by `make config`.
|
||||
internal/config/ Viper config models, loading, snapshots, and reloads.
|
||||
internal/global/ Process-wide shared resource registry.
|
||||
internal/server/ HTTP/gRPC server wiring.
|
||||
internal/service/ Transport adapters; one file per resource.
|
||||
internal/biz/ Domain models, usecases, repo interfaces, errors.
|
||||
|
|
@ -120,7 +121,7 @@ tests exercise repo implementations at the storage boundary.
|
|||
|
||||
## Generation & generated files
|
||||
|
||||
Regenerate via `make api`, `make config`, or `make all`; never hand-edit
|
||||
Regenerate via `make api` or `make all`; never hand-edit
|
||||
`*.pb.go`, `*_grpc.pb.go`, `*_http.pb.go`, or `wire_gen.go`.
|
||||
|
||||
## Naming & error reasons
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -8,11 +8,6 @@ init:
|
|||
go install github.com/google/wire/cmd/wire@latest
|
||||
go install github.com/bufbuild/buf/cmd/buf@latest
|
||||
|
||||
.PHONY: config
|
||||
# generate internal proto
|
||||
config:
|
||||
buf generate --template buf.gen.config.yaml
|
||||
|
||||
.PHONY: api
|
||||
# generate api proto
|
||||
api:
|
||||
|
|
@ -33,7 +28,6 @@ generate:
|
|||
# generate all
|
||||
all:
|
||||
make api
|
||||
make config
|
||||
make generate
|
||||
|
||||
# show help
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ Kra 是基于 Kratos 生命周期与 Wire 依赖注入、使用 Gin 提供管理
|
|||
cmd/ 服务入口与 Wire
|
||||
configs/ 运行配置
|
||||
internal/biz/ 领域对象、用例和仓储接口(按 system/payment/integration/task 拆分)
|
||||
internal/config/ Viper 配置模型、加载和热更新
|
||||
internal/data/ 数据库、缓存、对象存储及仓储实现
|
||||
internal/global/ 进程级共享资源入口
|
||||
internal/initialize/ 首次安装、配置保存和重载编排
|
||||
internal/server/ Gin 服务、路由、中间件和 Handler
|
||||
internal/service/ 按业务模块组织的 HTTP 输入输出与领域对象转换
|
||||
internal/worker/ 定时任务调度与后台工作进程
|
||||
|
|
@ -39,12 +42,6 @@ pnpm dev
|
|||
|
||||
## 生成代码
|
||||
|
||||
修改 `internal/conf/conf.proto` 后执行:
|
||||
|
||||
```shell
|
||||
make config
|
||||
```
|
||||
|
||||
修改 Wire Provider 或构造函数后执行:
|
||||
|
||||
```shell
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
version: v2
|
||||
inputs:
|
||||
- directory: internal
|
||||
plugins:
|
||||
- local: ["go", "run", "google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11"]
|
||||
out: internal
|
||||
opt: paths=source_relative
|
||||
|
||||
1
buf.yaml
1
buf.yaml
|
|
@ -1,6 +1,5 @@
|
|||
version: v2
|
||||
modules:
|
||||
- path: api
|
||||
- path: internal
|
||||
deps:
|
||||
- buf.build/googleapis/googleapis
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
payment2 "kra/internal/biz/payment"
|
||||
system2 "kra/internal/biz/system"
|
||||
task2 "kra/internal/biz/task"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/config"
|
||||
"kra/internal/data"
|
||||
"kra/internal/data/integration"
|
||||
"kra/internal/data/payment"
|
||||
|
|
@ -46,13 +46,13 @@ import (
|
|||
// 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)
|
||||
func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logger, reloadableLogger *logging.ReloadableLogger, string2 string) (*kratos.App, func(), error) {
|
||||
reloadable, err := storage.NewFileStorage(store)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
catalog := modules.Catalog()
|
||||
dataData, cleanup, err := data.NewData(runtime, logger, reloadable, catalog)
|
||||
dataData, cleanup, err := data.NewData(store, logger, reloadable, catalog)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
|||
userUsecase := system2.NewUserUsecase(userRepo)
|
||||
securityRepo := system.NewSecurityRepo(dataData)
|
||||
systemCache := cache.New(dataData)
|
||||
runtimeSettings := system.NewRuntimeSettings(runtime)
|
||||
runtimeSettings := system.NewRuntimeSettings(store)
|
||||
apiTokenRepo := system.NewAPITokenRepo(dataData)
|
||||
tokenUsecase := system2.NewTokenUsecase(apiTokenRepo)
|
||||
securityUsecase := system2.NewSecurityUsecase(securityRepo, systemCache, runtimeSettings, tokenUsecase)
|
||||
|
|
@ -100,7 +100,7 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
|||
announcementUsecase := system2.NewAnnouncementUsecase(announcementRepo)
|
||||
v10 := system3.NewAnnouncementService(announcementUsecase)
|
||||
announcement := handler.NewAnnouncement(v10)
|
||||
emailRepo := email.NewEmailRepo(runtime)
|
||||
emailRepo := email.NewEmailRepo(store)
|
||||
emailUsecase := system2.NewEmailUsecase(emailRepo)
|
||||
v11 := system3.NewEmailService(emailUsecase)
|
||||
handlerEmail := handler.NewEmail(v11)
|
||||
|
|
@ -159,8 +159,8 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
|||
navigation := handler.NewNavigation(v23)
|
||||
session := handler.NewSession(v21)
|
||||
integrationConfigRepo := integration.NewIntegrationConfigRepo(dataData)
|
||||
store := data.NewIntegrationRuntime(dataData)
|
||||
connectivityTester := integration2.NewConnectivityTester(store)
|
||||
runtimeconfigStore := data.NewIntegrationRuntime(dataData)
|
||||
connectivityTester := integration2.NewConnectivityTester(runtimeconfigStore)
|
||||
integrationConfigUsecase := integration3.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester)
|
||||
v24 := integration4.NewIntegrationConfigService(integrationConfigUsecase)
|
||||
integrationConfig := handler.NewIntegrationConfig(v24)
|
||||
|
|
@ -168,17 +168,17 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
|||
routes := router.NewRoutes(v25)
|
||||
maintenanceRepo := system.NewMaintenanceRepo(dataData)
|
||||
maintenanceUsecase := system2.NewMaintenanceUsecase(maintenanceRepo)
|
||||
taskMethods := worker.NewTaskMethods(taskUsecase, maintenanceUsecase, mediaUsecase, runtime)
|
||||
appRuntimeContributions := runtimeContributions(routes, taskMethods)
|
||||
moduleRuntime := app.Runtime(appRuntimeContributions, registry)
|
||||
websocketServer, cleanup2, err := websocket.New(store)
|
||||
taskMethods := worker.NewTaskMethods(taskUsecase, maintenanceUsecase, mediaUsecase, store)
|
||||
composition := runtimeContributions(routes, taskMethods)
|
||||
runtime := app.Build(composition, registry)
|
||||
websocketServer, cleanup2, err := websocket.New(runtimeconfigStore)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, err
|
||||
}
|
||||
engine := server.NewGinEngineWithRuntime(runtime, v, authService, v2, v3, logger, string2, moduleRuntime, websocketServer)
|
||||
httpServer := server.NewGinServer(confServer, engine)
|
||||
mqReloadable, cleanup3, err := mq.New(store, logger)
|
||||
engine := server.NewGinEngineWithRuntime(store, v, authService, v2, v3, logger, string2, runtime, websocketServer)
|
||||
httpServer := server.NewGinServer(configServer, engine)
|
||||
mqReloadable, cleanup3, err := mq.New(runtimeconfigStore, logger)
|
||||
if err != nil {
|
||||
cleanup2()
|
||||
cleanup()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# `internal` 目录结构优化结论
|
||||
|
||||
参考 Go Kratos 的分层方式,顶层保留 `app`、`modules`、`biz`、`conf`、`data`、
|
||||
`initialize`、`integration`、`security`、`server`、`service`、`worker` 十个
|
||||
参考 Go Kratos 的分层方式,顶层保留 `app`、`modules`、`biz`、`config`、`global`、
|
||||
`data`、`initialize`、`integration`、`security`、`server`、`service`、`worker` 等
|
||||
稳定职责。目录不是越少越好:同一技术角色文件较多时,应在所属层下分组,避免
|
||||
一个目录堆积几十个文件。
|
||||
|
||||
|
|
@ -9,11 +9,12 @@
|
|||
|
||||
```text
|
||||
internal/
|
||||
app/ # 运行时组合根
|
||||
app/ # 应用组合根
|
||||
modules/ # 静态 catalog 和模块定义
|
||||
modules/payment/ # payment 模块定义
|
||||
biz/ # DO、usecase、repo interface
|
||||
conf/ # 配置 proto/runtime
|
||||
config/ # Viper 配置、快照和热更新
|
||||
global/ # 进程级共享资源
|
||||
data/ # PO、repo、数据库和迁移
|
||||
initialize/ # 首次安装和配置编排
|
||||
integration/ # 外部 I/O provider
|
||||
|
|
@ -40,15 +41,15 @@ internal/
|
|||
独立边界时不继续拆分。
|
||||
- 删除只转发 `pkg/protoutil` 的 `utils/configutil`。
|
||||
|
||||
## `internal/app` 为什么只保留运行时组合
|
||||
## `internal/app` 为什么只保留应用组合
|
||||
|
||||
`modules/catalog.go` 是静态模块注册点,负责按依赖顺序汇总各模块
|
||||
`Definition()`;`app/runtime.go` 只负责任务注册和依赖注入后的运行时路由组合。
|
||||
`Definition()`;`app/runtime.go` 只负责任务注册和依赖注入后的路由组合。
|
||||
这样模块定义不再和应用组合逻辑混在一起,也不能误并入 `biz`、`service` 或
|
||||
`data`。
|
||||
|
||||
Catalog 只能自动汇总静态模块贡献;新增模块若提供运行时路由或依赖型任务,仍需
|
||||
在 cmd/Wire 中显式注册,直到统一的 runtime contribution 协议落地。
|
||||
在 cmd/Wire 中显式注册。
|
||||
|
||||
## 其他目录审查
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (s MediaSettings) EffectiveMaxFileSize() int64 {
|
|||
}
|
||||
|
||||
// RuntimeSettings exposes only the active values needed by the application.
|
||||
// The data implementation resolves every call from conf.Runtime so hot reloads
|
||||
// The data implementation resolves every call from config.Store so hot reloads
|
||||
// take effect without rebuilding services.
|
||||
type RuntimeSettings interface {
|
||||
RouterPrefix() string
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,218 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package kratos.api;
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
|
||||
option go_package = "kra/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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
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()
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package data
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"kra/internal/config"
|
||||
)
|
||||
|
||||
// watchConfig refreshes the in-memory config snapshot on file changes, while
|
||||
|
|
@ -79,10 +80,13 @@ func (d *Data) watchConfig() func() {
|
|||
logger.Error("reload changed config", "mod", "system", "error", loadErr)
|
||||
continue
|
||||
}
|
||||
if next.Data == nil || next.Admin == nil {
|
||||
logger.Error("reload changed config: data and admin configuration are required", "mod", "system")
|
||||
if next.Data == nil {
|
||||
logger.Error("reload changed config: data configuration is required", "mod", "system")
|
||||
continue
|
||||
}
|
||||
if next.Admin == nil {
|
||||
next.Admin = &config.Admin{}
|
||||
}
|
||||
if current := d.runtime.Admin(); current != nil {
|
||||
next.Admin.Storage = current.Storage
|
||||
next.Admin.Email = current.Email
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ import (
|
|||
func TestConfigWatcherStopWaitsForDebouncedReload(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
configPath := filepath.Join(root, "config.yaml")
|
||||
config := []byte("data:\n database:\n driver: sqlite\n path: " + filepath.ToSlash(root) + "\nadmin: {}\n")
|
||||
if err := os.WriteFile(configPath, config, 0o600); err != nil {
|
||||
rawConfig := []byte("data:\n database:\n driver: sqlite\n path: " + filepath.ToSlash(root) + "\nadmin: {}\n")
|
||||
if err := os.WriteFile(configPath, rawConfig, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := config.NewStore(&config.Config{Data: &config.Data{}, Admin: &config.Admin{ConfigPath: configPath}})
|
||||
started := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
var startOnce sync.Once
|
||||
stopListener := runtime.Subscribe(func(*config.Data, *config.Admin) {
|
||||
stopListener := runtime.Subscribe(func(*config.Config) {
|
||||
startOnce.Do(func() { close(started) })
|
||||
<-release
|
||||
})
|
||||
|
|
@ -30,7 +30,7 @@ func TestConfigWatcherStopWaitsForDebouncedReload(t *testing.T) {
|
|||
stop := data.watchConfig()
|
||||
|
||||
// A write event schedules the 100ms debounce reload.
|
||||
if err := os.WriteFile(configPath, config, 0o600); err != nil {
|
||||
if err := os.WriteFile(configPath, rawConfig, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ extension:
|
|||
if err := os.WriteFile(configPath, []byte(original), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := config.NewStore(&config.Data{}, &config.Admin{ConfigPath: configPath})
|
||||
runtime := config.NewStore(&config.Config{Data: &config.Data{}, Admin: &config.Admin{ConfigPath: configPath}})
|
||||
data := &Data{runtime: runtime}
|
||||
if err := data.persistDatabaseConfig(&config.Database{Driver: "mysql", Host: "db", Port: "3306", User: "root", Password: "secret", Name: "kra", Config: "parseTime=True"}, "new-key"); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
dataintegration "kra/internal/data/integration"
|
||||
"kra/internal/integration/storage"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -59,9 +58,9 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
|
|||
Qiniu: &config.Qiniu{
|
||||
Zone: "ZoneHuadong", Bucket: "qiniu-bucket", AccessKey: "qiniu-key", SecretKey: "qiniu-secret",
|
||||
},
|
||||
AliyunOss: &config.ObjectStore{
|
||||
AliyunOSS: &config.ObjectStore{
|
||||
Endpoint: "oss-cn-hangzhou.aliyuncs.com", Region: "cn-hangzhou", Bucket: "assets",
|
||||
AccessKey: "aliyun-key", SecretKey: "aliyun-secret", BaseUrl: "https://cdn.example.com", PathPrefix: "uploads",
|
||||
AccessKey: "aliyun-key", SecretKey: "aliyun-secret", BaseURL: "https://cdn.example.com", PathPrefix: "uploads",
|
||||
},
|
||||
Minio: &config.ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true},
|
||||
}
|
||||
|
|
@ -70,7 +69,7 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
|
|||
}
|
||||
email := &config.Email{
|
||||
To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
|
||||
Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSsl: true,
|
||||
Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSSL: true,
|
||||
}
|
||||
if err := saveEmailIntegrationConfig(db, email); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -135,7 +134,7 @@ func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.Type != "qiniu" || loaded.Qiniu.GetBucket() != "legacy" {
|
||||
if loaded.Type != "qiniu" || loaded.Qiniu.Bucket != "legacy" {
|
||||
t.Fatalf("migrated storage = %#v", loaded)
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +146,7 @@ func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.Type != "qiniu" || loaded.Qiniu.GetSecretKey() != "legacy-secret" {
|
||||
if loaded.Type != "qiniu" || loaded.Qiniu.SecretKey != "legacy-secret" {
|
||||
t.Fatalf("database configuration was replaced by legacy config: %#v", loaded)
|
||||
}
|
||||
}
|
||||
|
|
@ -155,7 +154,7 @@ func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
|||
func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) {
|
||||
storage := &config.Storage{
|
||||
Qiniu: &config.Qiniu{},
|
||||
AliyunOss: &config.ObjectStore{SecretKey: "configured-secret"},
|
||||
AliyunOSS: &config.ObjectStore{SecretKey: "configured-secret"},
|
||||
Minio: &config.ObjectStore{},
|
||||
}
|
||||
maskStorageSecrets(storage)
|
||||
|
|
@ -169,7 +168,7 @@ func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) {
|
|||
|
||||
func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
||||
db := openIntegrationConfigTestDB(t)
|
||||
legacy := &config.Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSsl: true}
|
||||
legacy := &config.Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSSL: true}
|
||||
loaded, err := resolveEmailIntegrationConfig(db, legacy)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -259,17 +258,9 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
|
|||
nextAdmin.Local = &config.Local{StorePath: newRoot, PathPrefix: "new-files"}
|
||||
nextAdmin.Email = &config.Email{
|
||||
To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
|
||||
Secret: "runtime-secret", Port: 465, IsSsl: true,
|
||||
Secret: "runtime-secret", Port: 465, IsSSL: true,
|
||||
}
|
||||
dataRaw, err := protojson.Marshal(&config.Data{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
adminRaw, err := protojson.Marshal(nextAdmin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = d.PersistRuntimeConfig(context.Background(), dataRaw, adminRaw); err != nil {
|
||||
if err = d.PersistRuntimeConfig(context.Background(), &config.Config{Data: &config.Data{}, Admin: nextAdmin}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ package initialize
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"kra/internal/config"
|
||||
)
|
||||
|
|
@ -34,17 +35,20 @@ func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) e
|
|||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mergeJSON(value.Data, &next.Data); err != nil {
|
||||
if next.Data == nil {
|
||||
next.Data = &config.Data{}
|
||||
}
|
||||
if next.Admin == nil {
|
||||
next.Admin = &config.Admin{}
|
||||
}
|
||||
if err := mergeDataJSON(value.Data, &next.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mergeJSON(value.Admin, &next.Admin); err != nil {
|
||||
if err := mergeAdminJSON(value.Admin, &next.Admin); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(value.Email) > 0 && string(value.Email) != "null" {
|
||||
if next.Admin == nil {
|
||||
next.Admin = &config.Admin{}
|
||||
}
|
||||
if err := mergeJSON(value.Email, &next.Admin.Email); err != nil {
|
||||
if err := mergeEmailJSON(value.Email, &next.Admin.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -79,13 +83,144 @@ func mergeJSON(raw json.RawMessage, target any) error {
|
|||
return json.Unmarshal(raw, target)
|
||||
}
|
||||
|
||||
func mergeDataJSON(raw json.RawMessage, target **config.Data) error {
|
||||
return mergeJSONMap(raw, target, func(values map[string]any) error {
|
||||
if redis := jsonObject(values["redis"]); redis != nil {
|
||||
if err := normalizeDuration(redis, "read_timeout", "readTimeout"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeDuration(redis, "write_timeout", "writeTimeout"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if items, ok := values["redis_list"].([]any); ok {
|
||||
for _, item := range items {
|
||||
redis := jsonObject(item)
|
||||
if err := normalizeDuration(redis, "read_timeout", "readTimeout"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeDuration(redis, "write_timeout", "writeTimeout"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func mergeAdminJSON(raw json.RawMessage, target **config.Admin) error {
|
||||
return mergeJSONMap(raw, target, func(values map[string]any) error {
|
||||
moveJSONKey(values, "routerPrefix", "router_prefix")
|
||||
if jwt := jsonObject(values["jwt"]); jwt != nil {
|
||||
moveJSONKey(jwt, "signingKey", "signing_key")
|
||||
moveJSONKey(jwt, "expiresTime", "expires_time")
|
||||
moveJSONKey(jwt, "bufferTime", "buffer_time")
|
||||
if err := normalizeDuration(jwt, "expires_time", "expiresTime"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeDuration(jwt, "buffer_time", "bufferTime"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if captcha := jsonObject(values["captcha"]); captcha != nil {
|
||||
moveJSONKey(captcha, "keyLong", "key_long")
|
||||
moveJSONKey(captcha, "imgWidth", "img_width")
|
||||
moveJSONKey(captcha, "imgHeight", "img_height")
|
||||
moveJSONKey(captcha, "storeExpiration", "store_expiration")
|
||||
return normalizeDuration(captcha, "store_expiration", "storeExpiration")
|
||||
}
|
||||
if local := jsonObject(values["local"]); local != nil {
|
||||
moveJSONKey(local, "storePath", "store_path")
|
||||
moveJSONKey(local, "pathPrefix", "path_prefix")
|
||||
}
|
||||
if media := jsonObject(values["media"]); media != nil {
|
||||
moveJSONKey(media, "sessionTtl", "session_ttl")
|
||||
moveJSONKey(media, "maxFileSize", "max_file_size")
|
||||
moveJSONKey(media, "chunkDir", "chunk_dir")
|
||||
}
|
||||
if system := jsonObject(values["system"]); system != nil {
|
||||
moveJSONKey(system, "useRedis", "use_redis")
|
||||
moveJSONKey(system, "useMultipoint", "use_multipoint")
|
||||
moveJSONKey(system, "useStrictAuth", "use_strict_auth")
|
||||
moveJSONKey(system, "disableAutoMigrate", "disable_auto_migrate")
|
||||
moveJSONKey(system, "useMongo", "use_mongo")
|
||||
moveJSONKey(system, "iplimitCount", "iplimit_count")
|
||||
moveJSONKey(system, "iplimitTime", "iplimit_time")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func mergeEmailJSON(raw json.RawMessage, target **config.Email) error {
|
||||
return mergeJSONMap(raw, target, func(values map[string]any) error {
|
||||
moveJSONKey(values, "is-ssl", "is_ssl")
|
||||
moveJSONKey(values, "is-loginauth", "is_login_auth")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func mergeJSONMap(raw json.RawMessage, target any, transform func(map[string]any) error) error {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return nil
|
||||
}
|
||||
var values map[string]any
|
||||
if err := json.Unmarshal(raw, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
if transform != nil {
|
||||
if err := transform(values); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
normalized, err := json.Marshal(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mergeJSON(normalized, target)
|
||||
}
|
||||
|
||||
func jsonObject(value any) map[string]any {
|
||||
result, _ := value.(map[string]any)
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeDuration(values map[string]any, keys ...string) error {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
raw, ok := values[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
text, ok := raw.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
value, err := time.ParseDuration(text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid duration %q: %w", text, err)
|
||||
}
|
||||
values[key] = int64(value)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveJSONKey(values map[string]any, oldKey, newKey string) {
|
||||
if value, ok := values[oldKey]; ok {
|
||||
values[newKey] = value
|
||||
delete(values, oldKey)
|
||||
}
|
||||
}
|
||||
|
||||
func managementConfig(value *config.Config) map[string]any {
|
||||
result := map[string]any{"data": map[string]any{}, "admin": map[string]any{}, "email": map[string]any{}}
|
||||
if value == nil {
|
||||
return result
|
||||
}
|
||||
if value.Data != nil {
|
||||
result["data"] = value.Data
|
||||
result["data"] = managementData(value.Data)
|
||||
}
|
||||
if value.Admin != nil {
|
||||
admin := value.Admin
|
||||
|
|
@ -113,6 +248,36 @@ func managementConfig(value *config.Config) map[string]any {
|
|||
return result
|
||||
}
|
||||
|
||||
func managementData(value *config.Data) any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
var result map[string]any
|
||||
if json.Unmarshal(raw, &result) != nil {
|
||||
return value
|
||||
}
|
||||
if redis := jsonObject(result["redis"]); redis != nil {
|
||||
redis["read_timeout"] = value.Redis.ReadTimeout.String()
|
||||
redis["write_timeout"] = value.Redis.WriteTimeout.String()
|
||||
}
|
||||
if items, ok := result["redis_list"].([]any); ok {
|
||||
for index, item := range items {
|
||||
if index >= len(value.RedisList) || value.RedisList[index] == nil {
|
||||
continue
|
||||
}
|
||||
if redis := jsonObject(item); redis != nil {
|
||||
redis["read_timeout"] = value.RedisList[index].ReadTimeout.String()
|
||||
redis["write_timeout"] = value.RedisList[index].WriteTimeout.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func managementJWT(value *config.JWT) any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
|
|
@ -336,14 +501,11 @@ func refreshDatabaseSource(database *config.Database) error {
|
|||
if database == nil {
|
||||
return nil
|
||||
}
|
||||
if database.Driver == "sqlite" && database.Path == "" {
|
||||
return errors.New("sqlite database path is required")
|
||||
}
|
||||
if database.Driver == "sqlite" {
|
||||
database.Source = database.Path
|
||||
} else {
|
||||
hasStructuredConfig := database.Host != "" || database.Port != "" || database.User != "" || database.Password != "" || database.Name != "" || database.Config != "" || database.Path != ""
|
||||
if hasStructuredConfig {
|
||||
// Driver-specific DSN construction remains in data. Clearing Source
|
||||
// makes the data backend rebuild it from structured values.
|
||||
// makes the data backend rebuild it from structured values, while a
|
||||
// standalone DSN is preserved when no structured fields are supplied.
|
||||
database.Source = ""
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ func TestConfigurationJSONMasksSecretsWithoutMutatingConfig(t *testing.T) {
|
|||
|
||||
func TestSaveConfigurationJSONMergesPartialValuesAndPreservesSecrets(t *testing.T) {
|
||||
backend := &configurationBackend{value: &config.Config{
|
||||
Data: &config.Data{Database: &config.Database{Driver: "mysql", Host: "old", Password: "database-secret", Source: "old-dsn"}, Redis: &config.Redis{Name: "main", Password: "redis-secret"}},
|
||||
Admin: &config.Admin{ConfigPath: "config.yaml", JWT: &config.JWT{SigningKey: "jwt-secret", Issuer: "old"}, Email: &config.Email{Host: "old.smtp", Secret: "email-secret"}},
|
||||
Data: &config.Data{Database: &config.Database{Driver: "mysql", Host: "old", Password: "database-secret", Source: "old-dsn"}, Redis: &config.Redis{Name: "main", Password: "redis-secret", ReadTimeout: time.Second}},
|
||||
Admin: &config.Admin{ConfigPath: "config.yaml", JWT: &config.JWT{SigningKey: "jwt-secret", Issuer: "old", ExpiresTime: time.Hour}, Captcha: &config.Captcha{StoreExpiration: time.Minute}, Email: &config.Email{Host: "old.smtp", Secret: "email-secret", IsSSL: true}},
|
||||
}}
|
||||
raw := json.RawMessage(`{"data":{"database":{"host":"new","password":"******"}},"admin":{"jwt":{"issuer":"new","signing_key":"******"}},"email":{"host":"new.smtp","secret":"******"}}`)
|
||||
raw := json.RawMessage(`{"data":{"database":{"host":"new","password":"******"},"redis":{"read_timeout":"250ms"}},"admin":{"jwt":{"issuer":"new","signingKey":"******","expiresTime":"48h"},"captcha":{"storeExpiration":"5m"}},"email":{"host":"new.smtp","secret":"******","is-ssl":false}}`)
|
||||
if err := (&Repo{backend: backend}).SaveConfigurationJSON(context.Background(), raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -68,7 +68,23 @@ func TestSaveConfigurationJSONMergesPartialValuesAndPreservesSecrets(t *testing.
|
|||
if got.Data.Database.Password != "database-secret" || got.Admin.JWT.SigningKey != "jwt-secret" || got.Admin.Email.Secret != "email-secret" {
|
||||
t.Fatal("masked secrets were not preserved")
|
||||
}
|
||||
if got.Data.Redis.ReadTimeout != 250*time.Millisecond || got.Admin.JWT.ExpiresTime != 48*time.Hour || got.Admin.Captcha.StoreExpiration != 5*time.Minute || got.Admin.Email.IsSSL {
|
||||
t.Fatalf("management values were not decoded: redis=%s jwt=%s captcha=%s ssl=%t", got.Data.Redis.ReadTimeout, got.Admin.JWT.ExpiresTime, got.Admin.Captcha.StoreExpiration, got.Admin.Email.IsSSL)
|
||||
}
|
||||
if got.Admin.ConfigPath != "config.yaml" {
|
||||
t.Fatalf("config path = %q", got.Admin.ConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveConfigurationJSONPreservesStandaloneDSN(t *testing.T) {
|
||||
backend := &configurationBackend{value: &config.Config{
|
||||
Data: &config.Data{Database: &config.Database{Driver: "mysql", Source: "user:secret@tcp(database.example:3306)/kra"}},
|
||||
Admin: &config.Admin{},
|
||||
}}
|
||||
if err := (&Repo{backend: backend}).SaveConfigurationJSON(context.Background(), json.RawMessage(`{"admin":{"routerPrefix":"/api"}}`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := backend.persisted.Data.Database.Source; got != "user:secret@tcp(database.example:3306)/kra" {
|
||||
t.Fatalf("standalone DSN = %q", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
224
usage.txt
224
usage.txt
|
|
@ -1,224 +0,0 @@
|
|||
cmd\main.go:13: "kra/internal/conf"
|
||||
cmd\main.go:147: runtime := conf.NewRuntime(bc.Data, bc.Admin)
|
||||
cmd\main.go:148: unsubscribeLogger := runtime.Subscribe(func(_ *conf.Data, admin *conf.AdminBackend) {
|
||||
cmd\main_test.go:6: "kra/internal/conf"
|
||||
cmd\wire_gen.go:16: "kra/internal/conf"
|
||||
internal\app\runtime.go:30: return module.NewRuntime(contributions.Routes...)
|
||||
cmd\wire.go:14: "kra/internal/conf"
|
||||
pkg\module\module_test.go:54: runtime := NewRuntime(nil, stub)
|
||||
pkg\mq\mqtt.go:102: return waitToken(ctx, client.Subscribe(topic, qos, func(_ paho.Client, msg paho.Message) {
|
||||
pkg\module\module.go:84:func NewRuntime(routes ...RouteRegistrar) *Runtime {
|
||||
internal\config\runtime_test.go:63: runtime := NewRuntime(&Config{Admin: &Admin{JWT: &JWT{SigningKey: "before"}}})
|
||||
internal\config\runtime_test.go:65: stop := runtime.Subscribe(func(value *Config) {
|
||||
internal\config\runtime_test.go:69: runtime.Replace(&Config{Admin: &Admin{JWT: &JWT{SigningKey: "after"}}})
|
||||
internal\config\runtime_test.go:72: value := runtime.Snapshot()
|
||||
internal\config\runtime_test.go:82: runtime := NewRuntime(&Config{Data: &Data{Database: &Database{Name: "initial"}}})
|
||||
internal\config\runtime_test.go:89: _ = runtime.Snapshot()
|
||||
internal\config\runtime_test.go:94: runtime.Replace(&Config{Data: &Data{Database: &Database{Name: "next"}}})
|
||||
internal\config\runtime_test.go:111: value := runtime.Snapshot()
|
||||
internal\config\runtime_test.go:117: t.Fatalf("watcher did not reload configuration: %#v", runtime.Snapshot())
|
||||
internal\config\runtime.go:56:func NewRuntime(config *Config) *Runtime {
|
||||
internal\config\runtime.go:61: runtime.Replace(config)
|
||||
internal\config\runtime.go:71: runtime := NewRuntime(config)
|
||||
internal\config\runtime.go:87: config := r.Snapshot()
|
||||
internal\config\runtime.go:95: config := r.Snapshot()
|
||||
internal\config\runtime.go:103: config := r.Snapshot()
|
||||
internal\config\runtime.go:111: config := r.Snapshot()
|
||||
internal\config\runtime.go:122: config := r.Snapshot()
|
||||
internal\config\runtime.go:155: if path := r.ConfigPath(); path != "" {
|
||||
internal\config\runtime.go:200: path := r.ConfigPath()
|
||||
internal\config\runtime.go:268: r.Replace(config)
|
||||
internal\global\resources_test.go:61: resources := registry.Snapshot()
|
||||
internal\global\resources_test.go:79: registry.Replace(Resources{
|
||||
internal\global\resources_test.go:84: resources := registry.Snapshot()
|
||||
internal\global\resources_test.go:124: registry.Replace(Resources{Redis: redisClient, Storage: storage, MQ: mq, WebSocket: webSocket, Scheduler: scheduler})
|
||||
internal\global\resources_test.go:125: resources := registry.Snapshot()
|
||||
internal\global\resources_test.go:144: _ = registry.Snapshot()
|
||||
internal\global\resources.go:80:func (r *ResourceRegistry) Logger() *slog.Logger { return r.Snapshot().Logger }
|
||||
internal\global\resources.go:86:func (r *ResourceRegistry) DB() *gorm.DB { return r.Snapshot().DB }
|
||||
internal\global\resources.go:98: return r.Snapshot().NamedDBs[name]
|
||||
internal\global\resources.go:106:func (r *ResourceRegistry) Redis() redis.UniversalClient { return r.Snapshot().Redis }
|
||||
internal\global\resources.go:118: return r.Snapshot().NamedRedis[name]
|
||||
internal\global\resources.go:126:func (r *ResourceRegistry) Mongo() *mongo.Client { return r.Snapshot().Mongo }
|
||||
internal\global\resources.go:132:func (r *ResourceRegistry) Storage() FileStorage { return r.Snapshot().Storage }
|
||||
internal\global\resources.go:138:func (r *ResourceRegistry) MQ() platformmq.Registry { return r.Snapshot().MQ }
|
||||
internal\global\resources.go:144:func (r *ResourceRegistry) WebSocket() platformws.Hub { return r.Snapshot().WebSocket }
|
||||
internal\global\resources.go:150:func (r *ResourceRegistry) Scheduler() Scheduler { return r.Snapshot().Scheduler }
|
||||
internal\global\resources.go:241:func ResourceSnapshot() Resources { return defaultResources.Snapshot() }
|
||||
internal\global\resources.go:243:func ReplaceResources(resources Resources) { defaultResources.Replace(resources) }
|
||||
internal\conf\runtime_test.go:9: runtime := NewRuntime(&Data{Database: &Data_Database{Name: "first"}}, &AdminBackend{RouterPrefix: "/api", Jwt: &AdminBackend_JWT{SigningKey: "secret"}})
|
||||
internal\conf\runtime_test.go:25: runtime := NewRuntime(&Data{Database: &Data_Database{Name: "initial"}}, &AdminBackend{Jwt: &AdminBackend_JWT{SigningKey: "initial"}})
|
||||
internal\conf\runtime_test.go:38: runtime.Replace(&Data{Database: &Data_Database{Name: "next"}}, &AdminBackend{Jwt: &AdminBackend_JWT{SigningKey: "next"}})
|
||||
internal\conf\runtime.go:24:func NewRuntime(data *Data, admin *AdminBackend) *Runtime {
|
||||
internal\conf\runtime.go:26: runtime.Replace(data, admin)
|
||||
internal\conf\conf.pb.go:2216: " \x01(\tR\taccountIdB\x18Z\x16kra/internal/conf;confb\x06proto3"
|
||||
internal\biz\task\task.go:236: return uc.runtime.Subscribe(userID)
|
||||
internal\data\database.go:22: "kra/internal/config"
|
||||
internal\data\data.go:14: "kra/internal/config"
|
||||
internal\data\data.go:52: runtime *config.Runtime
|
||||
internal\data\data.go:79:func (d *Data) Runtime() *config.Runtime {
|
||||
internal\data\data.go:164:func NewData(runtime *config.Runtime, appLogger *slog.Logger, storageManager *storage.Reloadable, catalog module.Catalog) (*Data, func(), error) {
|
||||
internal\data\data.go:256: runtime.Replace(c, admin)
|
||||
internal\data\data.go:265: storageManager.Replace(activeStorage)
|
||||
internal\data\data.go:327: d.runtime.UpdateDatabase(config)
|
||||
internal\data\initialization_backend_test.go:10: "kra/internal/config"
|
||||
internal\data\initialization_backend_test.go:81: runtime := config.NewRuntime(&config.Data{}, &config.Admin{ConfigPath: configPath})
|
||||
internal\data\config_watch_test.go:10: "kra/internal/config"
|
||||
internal\data\config_watch_test.go:20: runtime := config.NewRuntime(&config.Data{}, &config.Admin{ConfigPath: configPath})
|
||||
internal\data\config_watch_test.go:24: stopListener := runtime.Subscribe(func(*config.Data, *config.Admin) {
|
||||
internal\data\initialization_backend.go:9: "kra/internal/config"
|
||||
internal\data\initialization_backend.go:18:func (d *Data) RuntimeValues() (*config.Data, *config.Admin) { return d.runtime.Values() }
|
||||
internal\data\initialization_backend.go:61: currentData, currentAdmin := d.runtime.Values()
|
||||
internal\data\initialization_backend.go:88: d.runtime.Replace(currentData, next)
|
||||
internal\data\initialization_backend.go:90: d.storage.Replace(candidateStorage)
|
||||
internal\data\initialization_backend.go:95: currentData, currentAdmin := d.runtime.Values()
|
||||
internal\data\initialization_backend.go:125: d.runtime.Replace(nextData, nextAdmin)
|
||||
internal\data\initialization_backend.go:127: d.storage.Replace(candidateStorage)
|
||||
internal\data\initialization_backend.go:218: currentData, currentAdmin := d.runtime.Values()
|
||||
internal\data\initialization_backend.go:228: d.runtime.Replace(currentData, currentAdmin)
|
||||
internal\data\initialization_backend.go:230: d.integrations.Replace(integrationConfigs)
|
||||
internal\data\integration_config.go:10: "kra/internal/config"
|
||||
internal\data\integration_config_test.go:11: "kra/internal/config"
|
||||
internal\data\integration_config_test.go:252: runtime: config.NewRuntime(&config.Data{}, currentAdmin),
|
||||
internal\data\config_helpers.go:4: "kra/internal/config"
|
||||
internal\data\config_store.go:12: "kra/internal/config"
|
||||
internal\data\config_store.go:155: dataConfig, adminConfig := d.runtime.Values()
|
||||
internal\data\config_store.go:214: configPath := d.runtime.ConfigPath()
|
||||
internal\data\config_store.go:262: configPath := d.runtime.ConfigPath()
|
||||
internal\data\config_store.go:313: configPath := d.runtime.ConfigPath()
|
||||
internal\data\config_store.go:431: d.runtime.Replace(next.Data, next.Admin)
|
||||
internal\data\config_store.go:433: d.integrations.Replace(integrationConfigs)
|
||||
internal\data\config_store.go:436: d.storage.Replace(candidateStorage)
|
||||
internal\data\config_watch.go:16: configPath := d.runtime.ConfigPath()
|
||||
internal\data\config_watch.go:91: d.runtime.Replace(next.Data, next.Admin)
|
||||
internal\server\gin.go:9: "kra/internal/config"
|
||||
internal\server\gin.go:23:func NewGinEngine(runtime *config.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
|
||||
internal\server\gin.go:24: return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(router.NewRoutes(handlers)), nil)
|
||||
internal\server\gin.go:27:func NewGinEngineWithRuntime(runtime *config.Runtime, access *service.AccessControlService, auth middleware.TokenAuthenticator, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime, ws *websocket.Server) *gin.Engine {
|
||||
internal\server\gin.go:29: runtime = config.NewRuntime(nil)
|
||||
internal\server\gin.go:39: snapshot := runtime.Snapshot()
|
||||
internal\server\gin_test.go:14: "kra/internal/config"
|
||||
internal\server\gin_test.go:35: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "test")
|
||||
internal\server\gin_test.go:69: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "test")
|
||||
internal\server\gin_test.go:89: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, logger, "test")
|
||||
internal\server\gin_test.go:100: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "v1.0.0")
|
||||
internal\server\gin_test.go:116: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{RouterPrefix: "/admin"}}), nil, emptyHandlers(), nil, nil, nil, nil, "v1.0.0")
|
||||
internal\server\gin_test.go:131: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "v1.0.0")
|
||||
internal\server\gin_test.go:140: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "test")
|
||||
internal\server\gin_test.go:154: engine := NewGinEngine(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, emptyHandlers(), nil, nil, nil, nil, "test")
|
||||
internal\server\gin_test.go:169: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: root, PathPrefix: "uploads/file"}, Storage: &config.Storage{Type: "local"}}})
|
||||
internal\server\gin_test.go:193: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: t.TempDir(), PathPrefix: "uploads/file"}}})
|
||||
internal\server\gin_test.go:216: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: root, PathPrefix: "uploads/file"}, Storage: &config.Storage{Type: "local"}}})
|
||||
internal\server\gin_test.go:218: runtime.Replace(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: root, PathPrefix: "files"}, Storage: &config.Storage{Type: "local"}}})
|
||||
internal\server\gin_test.go:233: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: t.TempDir(), PathPrefix: "api"}, Storage: &config.Storage{Type: "local"}}})
|
||||
internal\server\gin_test.go:243: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Local: &config.Local{StorePath: t.TempDir(), PathPrefix: "uploads/file"}, Storage: &config.Storage{Type: "s3"}}})
|
||||
internal\data\integration_runtime.go:19: d.integrations.Replace(configs)
|
||||
internal\data\mongo.go:9: "kra/internal/config"
|
||||
internal\worker\task_scheduler_test.go:10: first := scheduler.Subscribe(1)
|
||||
internal\worker\task_scheduler_test.go:11: second := scheduler.Subscribe(1)
|
||||
internal\worker\task_scheduler_test.go:12: third := scheduler.Subscribe(2)
|
||||
internal\worker\task_scheduler_test.go:34: channels = append(channels, scheduler.Subscribe(7))
|
||||
internal\worker\task_scheduler_test.go:60: ch := scheduler.Subscribe(9)
|
||||
internal\worker\task_registry.go:10: "kra/internal/config"
|
||||
internal\worker\task_registry.go:20: runtime *config.Runtime
|
||||
internal\worker\task_registry.go:23:func NewTaskMethods(tasks *taskbiz.TaskUsecase, maintenance *system.MaintenanceUsecase, media *system.MediaUsecase, runtime *config.Runtime) *TaskMethods {
|
||||
internal\worker\task_registry.go:58: if snapshot := methods.runtime.Snapshot(); snapshot != nil {
|
||||
internal\data\payment\payment.go:411: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\server\middleware\access_log.go:18: "kra/internal/config"
|
||||
internal\server\middleware\access_log.go:28:func AccessLog(runtime *config.Runtime, logger *slog.Logger, version string) gin.HandlerFunc {
|
||||
internal\server\middleware\access_log.go:36: if snapshot := runtime.Snapshot(); snapshot != nil {
|
||||
internal\server\staticfiles\staticfiles.go:12: "kra/internal/config"
|
||||
internal\server\staticfiles\staticfiles.go:19:func Register(engine *gin.Engine, runtime *config.Runtime) {
|
||||
internal\server\staticfiles\staticfiles.go:39:func Serve(c *gin.Context, runtime *config.Runtime) bool {
|
||||
internal\server\staticfiles\staticfiles.go:48:func localConfig(runtime *config.Runtime) *config.Local {
|
||||
internal\server\staticfiles\staticfiles.go:52: snapshot := runtime.Snapshot()
|
||||
internal\server\staticfiles\staticfiles.go:76:func serveAt(c *gin.Context, runtime *config.Runtime, prefix string) bool {
|
||||
internal\server\middleware\access.go:7: "kra/internal/config"
|
||||
internal\server\middleware\access.go:18:func AccessControl(runtime *config.Runtime, access accessController) gin.HandlerFunc {
|
||||
internal\server\middleware\access.go:33: if snapshot := runtime.Snapshot(); snapshot != nil && snapshot.Admin != nil {
|
||||
internal\server\websocket_auth_test.go:12: "kra/internal/config"
|
||||
internal\server\websocket_auth_test.go:47: engine := NewGinEngineWithRuntime(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, auth, nil, nil, nil, "test", nil, ws)
|
||||
internal\integration\websocket\server.go:40: s.stop = store.Subscribe("websocket", ProviderMelody, func(config runtimeconfig.Config) { s.apply(config) })
|
||||
internal\server\middleware\audit.go:15: "kra/internal/config"
|
||||
internal\server\middleware\audit.go:25:func OperationAudit(runtime *config.Runtime, recorder *service.AuditRecorder) gin.HandlerFunc {
|
||||
internal\server\middleware\audit.go:42: if snapshot := runtime.Snapshot(); snapshot != nil && snapshot.Admin != nil && snapshot.Admin.Zap != nil && snapshot.Admin.Zap.AccessLogMaxBytes > 0 {
|
||||
internal\server\middleware\cors_test.go:8: "kra/internal/config"
|
||||
internal\server\middleware\cors_test.go:16: engine.Use(CORS(config.NewRuntime(&config.Config{Admin: admin})))
|
||||
internal\server\middleware\cors.go:7: "kra/internal/config"
|
||||
internal\server\middleware\cors.go:20:func CORS(runtime *config.Runtime) gin.HandlerFunc {
|
||||
internal\server\middleware\cors.go:26: snapshot := runtime.Snapshot()
|
||||
internal\server\middleware\access_test.go:12: "kra/internal/config"
|
||||
internal\server\middleware\access_test.go:39: engine.Use(AccessControl(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), &accessControllerStub{scopeErr: errors.New("database unavailable")}))
|
||||
internal\server\middleware\access_log_test.go:12: "kra/internal/config"
|
||||
internal\server\middleware\access_log_test.go:21: engine.Use(AccessLog(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, "test"))
|
||||
internal\server\middleware\access_log_test.go:37: engine.Use(AccessLog(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, "test"))
|
||||
internal\server\middleware\access_log_test.go:54: engine.Use(AccessLog(config.NewRuntime(&config.Config{Admin: &config.Admin{}}), nil, "test"))
|
||||
internal\server\middleware\access_log_test.go:73: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Zap: &config.Zap{AccessReqBody: true, AccessReqHeaders: true, AccessRespData: true}}})
|
||||
internal\server\middleware\access_log_test.go:104: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{Zap: &config.Zap{AccessReqBody: true, AccessReqHeaders: true, AccessRespData: true}}})
|
||||
internal\server\middleware\access_log_test.go:130: runtime := config.NewRuntime(&config.Config{Admin: &config.Admin{}})
|
||||
internal\integration\email\email_test.go:7: "kra/internal/config"
|
||||
internal\integration\email\email_test.go:11: runtime := config.NewRuntime(&config.Data{}, &config.Admin{Email: &config.Email{To: " "}})
|
||||
internal\integration\email\email_test.go:19: runtime := config.NewRuntime(&config.Data{}, &config.Admin{Email: &config.Email{Host: "smtp.example.com", From: "from@example.com", Secret: "secret", Port: 25}})
|
||||
internal\integration\email\email.go:16: "kra/internal/config"
|
||||
internal\integration\email\email.go:19:type emailRepo struct{ runtime *config.Runtime }
|
||||
internal\integration\email\email.go:21:func NewEmailRepo(runtime *config.Runtime) system.EmailRepo {
|
||||
internal\integration\email\email.go:64: return strings.NewReplacer("\r", "", "\n", "").Replace(value)
|
||||
internal\service\task\task.go:81:func (s *TaskService) Subscribe(userID uint) chan []byte { return s.uc.Subscribe(userID) }
|
||||
internal\server\handler\task.go:138: events := h.service.Subscribe(claims.ID)
|
||||
internal\initialize\configuration_test.go:7: "kra/internal/conf"
|
||||
internal\integration\storage\tencent_storage.go:14: "kra/internal/config"
|
||||
internal\initialize\configuration.go:7: "kra/internal/conf"
|
||||
internal\integration\storage\s3_storage.go:12: "kra/internal/config"
|
||||
internal\integration\storage\reloadable_test.go:68: reloadable.Replace(&emptyStorage{})
|
||||
internal\initialize\compatibility.go:9: "kra/internal/conf"
|
||||
internal\initialize\backend.go:7: "kra/internal/conf"
|
||||
internal\data\system\api_policy_test.go:9: "kra/internal/config"
|
||||
internal\data\system\api_policy_test.go:23: runtime: config.NewRuntime(
|
||||
internal\integration\storage\reloadable.go:10: "kra/internal/config"
|
||||
internal\integration\storage\reloadable.go:18:func NewFileStorage(runtime *config.Runtime) (*Reloadable, error) {
|
||||
internal\integration\storage\qiniu_storage.go:14: "kra/internal/config"
|
||||
internal\integration\storage\local.go:14: "kra/internal/config"
|
||||
internal\integration\storage\huawei_storage.go:11: "kra/internal/config"
|
||||
internal\data\system\authority_test.go:9: "kra/internal/config"
|
||||
internal\data\system\authority_test.go:17: currentData, _ := data.runtime.Values()
|
||||
internal\data\system\authority_test.go:18: data.runtime.Replace(currentData, &config.Admin{System: &config.System{UseStrictAuth: true}})
|
||||
internal\integration\storage\aws_storage.go:11: "kra/internal/config"
|
||||
internal\integration\storage\aliyun_storage.go:11: "kra/internal/config"
|
||||
internal\integration\payment\alipay.go:439: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\integration\runtimeconfig\store_test.go:12: stop := store.Subscribe("mq", "rabbitmq", func(config Config) { updates <- config })
|
||||
internal\integration\runtimeconfig\store_test.go:36: stop := store.Subscribe("mq", "rabbitmq", func(config Config) { updates <- config })
|
||||
internal\integration\runtimeconfig\store_test.go:47: store.Replace([]Config{config})
|
||||
internal\integration\runtimeconfig\store_test.go:56: store.Replace([]Config{changed})
|
||||
internal\integration\runtimeconfig\store_test.go:70: stop := store.Subscribe("mq", "rabbitmq", func(config Config) { updates <- config })
|
||||
internal\integration\payment\allinpay.go:210: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\integration\mq\emqx.go:71: store.Subscribe("mq", ProviderEMQX, func(config runtimeconfig.Config) { r.apply(ProviderEMQX, config) }),
|
||||
internal\integration\mq\emqx.go:72: store.Subscribe("mq", ProviderRabbitMQ, func(config runtimeconfig.Config) { r.apply(ProviderRabbitMQ, config) }),
|
||||
internal\integration\mq\emqx.go:362: if err := client.Subscribe(context.Background(), topic, qos, r.dispatcher(provider, topic)); err != nil {
|
||||
internal\integration\mq\emqx.go:445: if err := client.Subscribe(context.Background(), topic, qos, r.dispatcher(provider, topic)); err != nil {
|
||||
internal\integration\payment\lakala.go:74: return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method)))
|
||||
internal\integration\mq\emqx_test.go:57: if err := r.Subscribe(context.Background(), "orders/+/paid", platformmq.AtLeastOnce, handler); err != nil {
|
||||
internal\integration\mq\emqx_test.go:204: err := r.Subscribe(context.Background(), "events.offline", platformmq.AtLeastOnce, func(context.Context, platformmq.Message) {})
|
||||
internal\integration\payment\douyin.go:113: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\data\system\log_file_test.go:11: "kra/internal/config"
|
||||
internal\data\system\log_file_test.go:25: runtime := config.NewRuntime(nil, &config.Admin{Zap: &config.Zap{Director: root}})
|
||||
internal\data\system\log_file_test.go:44: runtime := config.NewRuntime(nil, &config.Admin{Zap: &config.Zap{Director: t.TempDir()}})
|
||||
internal\integration\payment\saobei.go:92: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\data\system\menu_test.go:9: "kra/internal/config"
|
||||
internal\data\system\menu_test.go:23: runtime: config.NewRuntime(&config.Data{Database: &config.Database{Driver: "sqlite"}}, &config.Admin{}),
|
||||
internal\integration\payment\wechat_v3.go:123: return strings.NewReplacer(".", "", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\integration\payment\wechat_v2.go:189: normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
internal\data\system\permission_test.go:8: "kra/internal/config"
|
||||
internal\data\system\permission_test.go:19: data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewRuntime(&config.Data{Database: &config.Database{Driver: "sqlite"}}, &config.Admin{})}
|
||||
internal\data\system\permission_test.go:39: data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewRuntime(&config.Data{Database: &config.Database{Driver: "sqlite"}}, &config.Admin{})}
|
||||
internal\data\system\permission_test.go:70: data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewRuntime(&config.Data{Database: &config.Database{Driver: "sqlite"}}, &config.Admin{})}
|
||||
internal\data\system\provider.go:4: "kra/internal/config"
|
||||
internal\data\system\provider.go:15: Runtime() *config.Runtime
|
||||
internal\data\system\runtime.go:10: "kra/internal/config"
|
||||
internal\data\system\runtime.go:16:type runtimeSettings struct{ runtime *config.Runtime }
|
||||
internal\data\system\runtime.go:18:func NewRuntimeSettings(runtime *config.Runtime) system.RuntimeSettings {
|
||||
internal\data\system\testing_support_test.go:10: "kra/internal/config"
|
||||
internal\data\system\testing_support_test.go:19: runtime *config.Runtime
|
||||
internal\data\system\testing_support_test.go:32:func (d *Data) Runtime() *config.Runtime {
|
||||
internal\data\system\transactions_test.go:9: "kra/internal/config"
|
||||
internal\data\system\transactions_test.go:14: name := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
||||
internal\data\system\transactions_test.go:22: data := &Data{gormDB: newReloadableDB(db, nil), redis: newReloadableRedis(nil), runtime: config.NewRuntime(&config.Data{Database: &config.Database{Driver: "sqlite"}}, &config.Admin{})}
|
||||
Loading…
Reference in New Issue