feat: 建立 MySQL 8 数据库基础 (#78)
This commit is contained in:
@@ -23,6 +23,66 @@ import (
|
||||
// 本项目有意不做心跳,见 docs/admin/04-client-api.md §3。
|
||||
const OnlineThreshold = 10 * time.Minute
|
||||
|
||||
const (
|
||||
databaseHostEnv = "CMAUTOBUY_DB_HOST"
|
||||
databasePortEnv = "CMAUTOBUY_DB_PORT"
|
||||
databaseNameEnv = "CMAUTOBUY_DB_NAME"
|
||||
databaseUserEnv = "CMAUTOBUY_DB_USER"
|
||||
databasePasswordEnv = "CMAUTOBUY_DB_PASSWORD"
|
||||
)
|
||||
|
||||
// DatabaseConfig 是生产 MySQL 8 的连接配置。
|
||||
// 密码只从环境变量读取,不能写进 config.yaml、日志或工单。
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Name string
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
// String 永远隐藏密码,防止排错时用 %v 把凭据写进日志。
|
||||
func (c DatabaseConfig) String() string {
|
||||
password := "(空)"
|
||||
if c.Password != "" {
|
||||
password = "****"
|
||||
}
|
||||
return fmt.Sprintf("DatabaseConfig{Host:%s Port:%s Name:%s User:%s Password:%s}",
|
||||
c.Host, c.Port, c.Name, c.User, password)
|
||||
}
|
||||
|
||||
// LoadDatabaseFromEnv 读取生产数据库配置。
|
||||
// Host/Port 使用线上同机部署的安全默认值;库名、账号和密码必须显式提供。
|
||||
func LoadDatabaseFromEnv() (DatabaseConfig, error) {
|
||||
cfg := DatabaseConfig{
|
||||
Host: strings.TrimSpace(os.Getenv(databaseHostEnv)),
|
||||
Port: strings.TrimSpace(os.Getenv(databasePortEnv)),
|
||||
Name: strings.TrimSpace(os.Getenv(databaseNameEnv)),
|
||||
User: strings.TrimSpace(os.Getenv(databaseUserEnv)),
|
||||
Password: os.Getenv(databasePasswordEnv),
|
||||
}
|
||||
if cfg.Host == "" {
|
||||
cfg.Host = "127.0.0.1"
|
||||
}
|
||||
if cfg.Port == "" {
|
||||
cfg.Port = "3307"
|
||||
}
|
||||
var missing []string
|
||||
if cfg.Name == "" {
|
||||
missing = append(missing, databaseNameEnv)
|
||||
}
|
||||
if cfg.User == "" {
|
||||
missing = append(missing, databaseUserEnv)
|
||||
}
|
||||
if cfg.Password == "" {
|
||||
missing = append(missing, databasePasswordEnv)
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return DatabaseConfig{}, fmt.Errorf("缺少 MySQL 配置环境变量:%s", strings.Join(missing, "、"))
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// DataDir 返回可写数据目录,不存在就创建。
|
||||
//
|
||||
// 打包成 exe 后 = exe 旁边的 data/
|
||||
|
||||
@@ -7,6 +7,35 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadDatabaseFromEnv_默认只连接本机3307且密码不泄露(t *testing.T) {
|
||||
t.Setenv(databaseHostEnv, "")
|
||||
t.Setenv(databasePortEnv, "")
|
||||
t.Setenv(databaseNameEnv, "cmautobuy_test")
|
||||
t.Setenv(databaseUserEnv, "cmautobuy_test")
|
||||
t.Setenv(databasePasswordEnv, "secret-do-not-print")
|
||||
|
||||
cfg, err := LoadDatabaseFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.Host != "127.0.0.1" || cfg.Port != "3307" {
|
||||
t.Fatalf("默认地址应为线上同机 MySQL 8,实际 %s:%s", cfg.Host, cfg.Port)
|
||||
}
|
||||
if got := cfg.String(); strings.Contains(got, cfg.Password) || !strings.Contains(got, "****") {
|
||||
t.Fatalf("配置字符串泄露密码或没有打码:%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDatabaseFromEnv_缺少必填项时只报变量名(t *testing.T) {
|
||||
t.Setenv(databaseNameEnv, "")
|
||||
t.Setenv(databaseUserEnv, "")
|
||||
t.Setenv(databasePasswordEnv, "")
|
||||
_, err := LoadDatabaseFromEnv()
|
||||
if err == nil || !strings.Contains(err.Error(), databasePasswordEnv) {
|
||||
t.Fatalf("应明确列出缺失变量,实际 %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// loadFrom 是测试专用的小工具:把一段 YAML 文本写到临时目录里的
|
||||
// config.yaml,绕开 ConfigPath()(它依赖 os.Executable,测试环境里
|
||||
// 不可控),直接测 Load 里"读文件 + 解析"这段逻辑。
|
||||
|
||||
Reference in New Issue
Block a user