优化结构

This commit is contained in:
Yvan
2026-08-21 10:29:14 +08:00
parent 56980c8087
commit 055f6811ca
340 changed files with 382 additions and 382 deletions
+2579
View File
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
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;
WebSocket websocket = 14;
MQ mq = 15;
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 WebSocket {
bool enabled = 1;
string path = 2;
repeated string allow_origins = 3;
int64 max_message_size = 4;
google.protobuf.Duration write_wait = 5;
google.protobuf.Duration pong_wait = 6;
google.protobuf.Duration ping_period = 7;
int32 message_buffer_size = 8;
bool concurrent_message_handling = 9;
}
// MQ config is persisted in sys_integration_configs (kind=mq/provider=emqx).
// The bootstrap fields are retained as a one-time migration source.
message MQ {
bool enabled = 1;
string broker = 2;
string client_id = 3;
string username = 4;
string password = 5;
int32 keep_alive = 6;
bool clean_session = 7;
int32 connect_timeout = 8;
}
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()
}