feat: integrate OA activation workflows and business migration

This commit is contained in:
Yvan
2026-11-07 00:09:19 +08:00
parent 64aca495f2
commit 9caf5b1abc
47 changed files with 1831 additions and 152 deletions
+34
View File
@@ -0,0 +1,34 @@
package config
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func TestBusinessOAEnvironmentAndRuntimeMerge(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte("server:\n http:\n addr: ':8000'\n"), 0600); err != nil {
t.Fatal(err)
}
t.Setenv("KRA_BUSINESS_OA_URL", "http://oa.test/api")
t.Setenv("KRA_BUSINESS_OA_KEY", "test-only-key")
value, err := Load(path)
if err != nil {
t.Fatal(err)
}
if value.BusinessOA == nil || value.BusinessOA.URL != "http://oa.test/api" || value.BusinessOA.Key != "test-only-key" {
t.Fatal("environment-only OA configuration not loaded")
}
merged := MergeRuntimeConfig(value, &Config{Admin: &Admin{}})
merged.BusinessOA.Key = "changed"
if value.BusinessOA.Key != "test-only-key" {
t.Fatal("configuration snapshot shared a pointer")
}
serialized, err := json.Marshal(value)
if err != nil || strings.Contains(string(serialized), "test-only-key") {
t.Fatal("service credential leaked through public configuration")
}
}
+3
View File
@@ -25,6 +25,9 @@ func MergeRuntimeConfig(current, next *Config) *Config {
}
return merged
}
if merged.BusinessOA == nil {
merged.BusinessOA = clonePtr(current.BusinessOA)
}
if merged.Server == nil {
merged.Server = clonePtr(current.Server)
}
+3
View File
@@ -332,6 +332,8 @@ func newViper(path string) *viper.Viper {
v.SetEnvPrefix("KRA")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
v.AutomaticEnv()
_ = v.BindEnv("business_oa.url")
_ = v.BindEnv("business_oa.key")
return v
}
@@ -385,6 +387,7 @@ func cloneConfig(value *Config) *Config {
return nil
}
copied := *value
copied.BusinessOA = clonePtr(value.BusinessOA)
copied.Server = clonePtr(value.Server)
if copied.Server != nil {
copied.Server.HTTP = clonePtr(value.Server.HTTP)
+10 -3
View File
@@ -6,9 +6,10 @@ import "time"
// It intentionally uses ordinary Go types so configuration is independent of
// protobuf generation and can be consumed by every internal module.
type Config struct {
Server *Server `mapstructure:"server" yaml:"server" json:"server"`
Data *Data `mapstructure:"data" yaml:"data" json:"data"`
Admin *Admin `mapstructure:"admin" yaml:"admin" json:"admin"`
BusinessOA *BusinessOA `mapstructure:"business_oa" yaml:"business_oa" json:"-"`
Server *Server `mapstructure:"server" yaml:"server" json:"server"`
Data *Data `mapstructure:"data" yaml:"data" json:"data"`
Admin *Admin `mapstructure:"admin" yaml:"admin" json:"admin"`
}
type Server struct {
@@ -224,3 +225,9 @@ type ObjectStore struct {
ForcePathStyle bool `mapstructure:"force_path_style" yaml:"force_path_style" json:"force_path_style"`
AccountID string `mapstructure:"account_id" yaml:"account_id" json:"account_id"`
}
// BusinessOA.URL 包含 OA 的路由前缀;Key 与 OA 服务间密钥一致。
type BusinessOA struct {
URL string `mapstructure:"url" yaml:"url" json:"url"`
Key string `mapstructure:"key" yaml:"key" json:"-"`
}