feat: 支持从 config.yaml 读取 MySQL 配置 (#83)
This commit is contained in:
@@ -29,6 +29,9 @@
|
|||||||
`github.com/go-sql-driver/mysql` **v1.9.2**(纯 Go,兼容 Go 1.23)。
|
`github.com/go-sql-driver/mysql` **v1.9.2**(纯 Go,兼容 Go 1.23)。
|
||||||
- Admin 必须与 MySQL 同机或走私有网络;生产默认连接 `127.0.0.1:3307`,
|
- Admin 必须与 MySQL 同机或走私有网络;生产默认连接 `127.0.0.1:3307`,
|
||||||
**不得为了远程访问开放公网 MySQL 端口**。
|
**不得为了远程访问开放公网 MySQL 端口**。
|
||||||
|
- Windows 本地运行允许把 MySQL 账号密码写入已被 Git 忽略的 `admin/config.yaml`;
|
||||||
|
线上部署仍优先使用权限为 `600` 的环境文件。`CMAUTOBUY_DB_*` 环境变量按字段
|
||||||
|
覆盖 YAML,真实 `config.yaml` 不得提交、打包、截图或复制到工单和日志。
|
||||||
- `modernc.org/sqlite` 只保留给 SQLite → MySQL 单向迁移工具和历史库回归,
|
- `modernc.org/sqlite` 只保留给 SQLite → MySQL 单向迁移工具和历史库回归,
|
||||||
不得用于生产运行时,也不得做 SQLite/MySQL 双写或自动同步。
|
不得用于生产运行时,也不得做 SQLite/MySQL 双写或自动同步。
|
||||||
- 仍然**不得改用 `mattn/go-sqlite3`**,避免引入 cgo 和 gcc。
|
- 仍然**不得改用 `mattn/go-sqlite3`**,避免引入 cgo 和 gcc。
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
databaseConfig, err := config.LoadDatabaseFromEnv()
|
databaseConfig, err := config.LoadDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("读取 MySQL 配置失败: %v", err)
|
log.Fatalf("读取 MySQL 配置失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,24 @@
|
|||||||
# 密码一旦进了 git 历史就很难清干净。
|
# 密码一旦进了 git 历史就很难清干净。
|
||||||
# `[必须]` 打包发给别人时也不要带上 config.yaml。
|
# `[必须]` 打包发给别人时也不要带上 config.yaml。
|
||||||
#
|
#
|
||||||
# 接口细节见 docs/admin/08-顺运宝接口.md。
|
# MySQL 配置见 docs/admin/00-getting-started.md §6;
|
||||||
|
# 顺运宝接口细节见 docs/admin/08-顺运宝接口.md。
|
||||||
|
|
||||||
|
database:
|
||||||
|
# MySQL 8 地址。本机 MySQL 或 SSH 隧道都写 127.0.0.1,禁止为了本地开发
|
||||||
|
# 把线上 3307 端口开放到公网。
|
||||||
|
host: 127.0.0.1
|
||||||
|
|
||||||
|
# 本机 MySQL 默认可写 3307;连接线上 MySQL 的 SSH 隧道可改成 13307。
|
||||||
|
# 端口加引号,明确按字符串读取。
|
||||||
|
port: "3307"
|
||||||
|
|
||||||
|
name: autobuy
|
||||||
|
user: buy
|
||||||
|
|
||||||
|
# `[必须]` 数据库密码也是明文配置,必须加引号;config.yaml 只能留在
|
||||||
|
# 运行机器上,不得提交、打包、截图或粘贴到工单和日志。
|
||||||
|
password: "你的 MySQL 密码"
|
||||||
|
|
||||||
syb:
|
syb:
|
||||||
# 顺运宝 ERP 地址。一般不用改,域名变了才改。
|
# 顺运宝 ERP 地址。一般不用改,域名变了才改。
|
||||||
|
|||||||
+76
-24
@@ -31,14 +31,16 @@ const (
|
|||||||
databasePasswordEnv = "CMAUTOBUY_DB_PASSWORD"
|
databasePasswordEnv = "CMAUTOBUY_DB_PASSWORD"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DatabaseConfig 是生产 MySQL 8 的连接配置。
|
// DatabaseConfig 是 MySQL 8 的连接配置。
|
||||||
// 密码只从环境变量读取,不能写进 config.yaml、日志或工单。
|
//
|
||||||
|
// 本地运行可以从被 Git 忽略的 config.yaml 读取;线上部署仍建议使用
|
||||||
|
// CMAUTOBUY_DB_* 环境变量。无论来源是什么,密码都不能写进日志或工单。
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
Host string
|
Host string `yaml:"host"`
|
||||||
Port string
|
Port string `yaml:"port"`
|
||||||
Name string
|
Name string `yaml:"name"`
|
||||||
User string
|
User string `yaml:"user"`
|
||||||
Password string
|
Password string `yaml:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// String 永远隐藏密码,防止排错时用 %v 把凭据写进日志。
|
// String 永远隐藏密码,防止排错时用 %v 把凭据写进日志。
|
||||||
@@ -51,16 +53,66 @@ func (c DatabaseConfig) String() string {
|
|||||||
c.Host, c.Port, c.Name, c.User, password)
|
c.Host, c.Port, c.Name, c.User, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDatabaseFromEnv 读取生产数据库配置。
|
// LoadDatabase 读取 MySQL 配置。config.yaml 适合本地双击 run_admin.bat;
|
||||||
// Host/Port 使用线上同机部署的安全默认值;库名、账号和密码必须显式提供。
|
// CMAUTOBUY_DB_* 环境变量按字段覆盖文件配置,供线上 systemd 安全注入。
|
||||||
func LoadDatabaseFromEnv() (DatabaseConfig, error) {
|
//
|
||||||
cfg := DatabaseConfig{
|
// config.yaml 不存在时仍可完全依靠环境变量启动,保持线上部署兼容。
|
||||||
Host: strings.TrimSpace(os.Getenv(databaseHostEnv)),
|
func LoadDatabase() (DatabaseConfig, error) {
|
||||||
Port: strings.TrimSpace(os.Getenv(databasePortEnv)),
|
path, err := ConfigPath()
|
||||||
Name: strings.TrimSpace(os.Getenv(databaseNameEnv)),
|
if err != nil {
|
||||||
User: strings.TrimSpace(os.Getenv(databaseUserEnv)),
|
return DatabaseConfig{}, fmt.Errorf("无法确定 config.yaml 应该在的位置: %w", err)
|
||||||
Password: os.Getenv(databasePasswordEnv),
|
|
||||||
}
|
}
|
||||||
|
return loadDatabaseFromPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadDatabaseFromPath 把文件读取单独抽出来,方便单元测试使用临时配置文件,
|
||||||
|
// 不依赖测试可执行文件所在的临时目录。
|
||||||
|
func loadDatabaseFromPath(path string) (DatabaseConfig, error) {
|
||||||
|
var fileConfig DatabaseConfig
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
cfg, parseErr := parseConfig(raw, path)
|
||||||
|
if parseErr != nil {
|
||||||
|
return DatabaseConfig{}, parseErr
|
||||||
|
}
|
||||||
|
fileConfig = cfg.Database
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return DatabaseConfig{}, fmt.Errorf("读取配置文件 %s 失败: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeDatabaseConfig(fileConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDatabaseFromEnv 只读取环境变量,供集成测试创建隔离的 _test 数据库。
|
||||||
|
// 生产入口和迁移命令应使用 LoadDatabase。
|
||||||
|
func LoadDatabaseFromEnv() (DatabaseConfig, error) {
|
||||||
|
return mergeDatabaseConfig(DatabaseConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeDatabaseConfig 把环境变量合并到文件配置上。环境变量只要非空,
|
||||||
|
// 就覆盖对应 YAML 字段,避免线上误读部署目录里遗留的本地配置。
|
||||||
|
func mergeDatabaseConfig(cfg DatabaseConfig) (DatabaseConfig, error) {
|
||||||
|
overrides := map[string]*string{
|
||||||
|
databaseHostEnv: &cfg.Host,
|
||||||
|
databasePortEnv: &cfg.Port,
|
||||||
|
databaseNameEnv: &cfg.Name,
|
||||||
|
databaseUserEnv: &cfg.User,
|
||||||
|
databasePasswordEnv: &cfg.Password,
|
||||||
|
}
|
||||||
|
for envName, target := range overrides {
|
||||||
|
value := os.Getenv(envName)
|
||||||
|
if envName != databasePasswordEnv {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
if value != "" {
|
||||||
|
*target = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Host = strings.TrimSpace(cfg.Host)
|
||||||
|
cfg.Port = strings.TrimSpace(cfg.Port)
|
||||||
|
cfg.Name = strings.TrimSpace(cfg.Name)
|
||||||
|
cfg.User = strings.TrimSpace(cfg.User)
|
||||||
if cfg.Host == "" {
|
if cfg.Host == "" {
|
||||||
cfg.Host = "127.0.0.1"
|
cfg.Host = "127.0.0.1"
|
||||||
}
|
}
|
||||||
@@ -69,16 +121,16 @@ func LoadDatabaseFromEnv() (DatabaseConfig, error) {
|
|||||||
}
|
}
|
||||||
var missing []string
|
var missing []string
|
||||||
if cfg.Name == "" {
|
if cfg.Name == "" {
|
||||||
missing = append(missing, databaseNameEnv)
|
missing = append(missing, "database.name / "+databaseNameEnv)
|
||||||
}
|
}
|
||||||
if cfg.User == "" {
|
if cfg.User == "" {
|
||||||
missing = append(missing, databaseUserEnv)
|
missing = append(missing, "database.user / "+databaseUserEnv)
|
||||||
}
|
}
|
||||||
if cfg.Password == "" {
|
if cfg.Password == "" {
|
||||||
missing = append(missing, databasePasswordEnv)
|
missing = append(missing, "database.password / "+databasePasswordEnv)
|
||||||
}
|
}
|
||||||
if len(missing) > 0 {
|
if len(missing) > 0 {
|
||||||
return DatabaseConfig{}, fmt.Errorf("缺少 MySQL 配置环境变量:%s", strings.Join(missing, "、"))
|
return DatabaseConfig{}, fmt.Errorf("缺少 MySQL 配置:%s", strings.Join(missing, "、"))
|
||||||
}
|
}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
@@ -180,10 +232,10 @@ func (c SybConfig) String() string {
|
|||||||
c.BaseURL, c.Username, pw, c.PageSize, c.MaxMatches, c.SyncFrom, c.OcrURL, c.OcrMaxAttempts)
|
c.BaseURL, c.Username, pw, c.PageSize, c.MaxMatches, c.SyncFrom, c.OcrURL, c.OcrMaxAttempts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config 是 config.yaml 的顶层结构。目前只有顺运宝一节,
|
// Config 是 config.yaml 的顶层结构。
|
||||||
// 后续如果要给别的模块加配置,在这里加新的字段即可。
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Syb SybConfig `yaml:"syb"`
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
Syb SybConfig `yaml:"syb"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// configFileName 是 config.yaml 相对 exe(或 go run 时相对工作目录)的文件名。
|
// configFileName 是 config.yaml 相对 exe(或 go run 时相对工作目录)的文件名。
|
||||||
@@ -236,7 +288,7 @@ func Load() (*Config, error) {
|
|||||||
func missingConfigError(path string) error {
|
func missingConfigError(path string) error {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"没有找到配置文件 %s。\n"+
|
"没有找到配置文件 %s。\n"+
|
||||||
"请复制 config.example.yaml 为 config.yaml,并填入顺运宝账号密码:\n"+
|
"请复制 config.example.yaml 为 config.yaml,并填入 MySQL 和顺运宝账号密码:\n"+
|
||||||
" Windows: copy admin\\config.example.yaml admin\\config.yaml\n"+
|
" Windows: copy admin\\config.example.yaml admin\\config.yaml\n"+
|
||||||
" Linux: cp admin/config.example.yaml admin/config.yaml",
|
" Linux: cp admin/config.example.yaml admin/config.yaml",
|
||||||
path)
|
path)
|
||||||
|
|||||||
@@ -36,6 +36,95 @@ func TestLoadDatabaseFromEnv_缺少必填项时只报变量名(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadDatabaseFromPath_可以只用YAML配置(t *testing.T) {
|
||||||
|
clearDatabaseEnv(t)
|
||||||
|
path := writeConfigFile(t, `
|
||||||
|
database:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "13307"
|
||||||
|
name: autobuy
|
||||||
|
user: buy
|
||||||
|
password: "yaml-secret"
|
||||||
|
`)
|
||||||
|
|
||||||
|
cfg, err := loadDatabaseFromPath(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Host != "127.0.0.1" || cfg.Port != "13307" || cfg.Name != "autobuy" || cfg.User != "buy" {
|
||||||
|
t.Fatalf("YAML 数据库配置读取错误:%s", cfg)
|
||||||
|
}
|
||||||
|
if cfg.Password != "yaml-secret" {
|
||||||
|
t.Fatal("YAML 数据库密码没有原样读出")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadDatabaseFromPath_环境变量按字段覆盖YAML(t *testing.T) {
|
||||||
|
clearDatabaseEnv(t)
|
||||||
|
t.Setenv(databasePortEnv, "23307")
|
||||||
|
t.Setenv(databaseUserEnv, "env-user")
|
||||||
|
t.Setenv(databasePasswordEnv, "env-secret")
|
||||||
|
path := writeConfigFile(t, `
|
||||||
|
database:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "13307"
|
||||||
|
name: autobuy
|
||||||
|
user: yaml-user
|
||||||
|
password: "yaml-secret"
|
||||||
|
`)
|
||||||
|
|
||||||
|
cfg, err := loadDatabaseFromPath(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Host != "127.0.0.1" || cfg.Name != "autobuy" {
|
||||||
|
t.Fatalf("未覆盖字段应该保留 YAML 值:%s", cfg)
|
||||||
|
}
|
||||||
|
if cfg.Port != "23307" || cfg.User != "env-user" || cfg.Password != "env-secret" {
|
||||||
|
t.Fatalf("环境变量没有按字段覆盖 YAML:%s", cfg)
|
||||||
|
}
|
||||||
|
if strings.Contains(cfg.String(), "env-secret") || strings.Contains(cfg.String(), "yaml-secret") {
|
||||||
|
t.Fatalf("配置字符串泄露密码:%s", cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadDatabaseFromPath_文件不存在时完整环境变量仍可启动(t *testing.T) {
|
||||||
|
clearDatabaseEnv(t)
|
||||||
|
t.Setenv(databaseNameEnv, "autobuy")
|
||||||
|
t.Setenv(databaseUserEnv, "buy")
|
||||||
|
t.Setenv(databasePasswordEnv, "env-secret")
|
||||||
|
|
||||||
|
cfg, err := loadDatabaseFromPath(filepath.Join(t.TempDir(), "missing.yaml"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Host != "127.0.0.1" || cfg.Port != "3307" {
|
||||||
|
t.Fatalf("线上环境变量模式应该保留安全默认地址,实际:%s", cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearDatabaseEnv(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
for _, name := range []string{
|
||||||
|
databaseHostEnv,
|
||||||
|
databasePortEnv,
|
||||||
|
databaseNameEnv,
|
||||||
|
databaseUserEnv,
|
||||||
|
databasePasswordEnv,
|
||||||
|
} {
|
||||||
|
t.Setenv(name, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeConfigFile(t *testing.T, yamlText string) string {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join(t.TempDir(), configFileName)
|
||||||
|
if err := os.WriteFile(path, []byte(yamlText), 0o600); err != nil {
|
||||||
|
t.Fatalf("写测试配置文件失败: %v", err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
// loadFrom 是测试专用的小工具:把一段 YAML 文本写到临时目录里的
|
// loadFrom 是测试专用的小工具:把一段 YAML 文本写到临时目录里的
|
||||||
// config.yaml,绕开 ConfigPath()(它依赖 os.Executable,测试环境里
|
// config.yaml,绕开 ConfigPath()(它依赖 os.Executable,测试环境里
|
||||||
// 不可控),直接测 Load 里"读文件 + 解析"这段逻辑。
|
// 不可控),直接测 Load 里"读文件 + 解析"这段逻辑。
|
||||||
|
|||||||
+3
-2
@@ -48,8 +48,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Printf("数据目录: %s", dataDir)
|
log.Printf("数据目录: %s", dataDir)
|
||||||
|
|
||||||
// 2. 数据库。凭据只从环境变量读取,不写进配置文件或日志。
|
// 2. 数据库。本地可从被 Git 忽略的 config.yaml 读取,线上环境变量优先覆盖;
|
||||||
databaseConfig, err := config.LoadDatabaseFromEnv()
|
// 无论从哪里读取,凭据都不写进日志。
|
||||||
|
databaseConfig, err := config.LoadDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("读取 MySQL 配置失败: %v", err)
|
log.Fatalf("读取 MySQL 配置失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ go run .
|
|||||||
|
|
||||||
**这个命令是安全的**:Admin 只管理数据,不会连手机、不会下单。放心随便跑。
|
**这个命令是安全的**:Admin 只管理数据,不会连手机、不会下单。放心随便跑。
|
||||||
|
|
||||||
### 配置顺运宝账号(同步货运单需要,其余四个模块不需要)
|
### 配置 MySQL 和顺运宝账号
|
||||||
|
|
||||||
顺运宝数据页的「同步」需要读 `admin/config.yaml`。第一次用要自己建这个文件
|
顺运宝数据页的「同步」需要读 `admin/config.yaml`。第一次用要自己建这个文件
|
||||||
(已在 `.gitignore` 里,不会被提交):
|
(已在 `.gitignore` 里,不会被提交):
|
||||||
@@ -100,7 +100,13 @@ cd D:\chengma\cmautobuy\admin
|
|||||||
copy config.example.yaml config.yaml
|
copy config.example.yaml config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
用编辑器打开 `config.yaml`,把 `username` / `password` 改成真实的顺运宝账号密码。
|
用编辑器打开 `config.yaml`:
|
||||||
|
|
||||||
|
- `database:` 填 MySQL 8 的地址、数据库名、账号和密码,Admin 启动必须使用;
|
||||||
|
- `syb:` 填顺运宝账号密码,只在同步货运单时使用。
|
||||||
|
|
||||||
|
两个密码都是明文,只允许保存在这份被 Git 忽略的本机文件里,不得提交、打包、
|
||||||
|
截图或粘贴到日志和工单。
|
||||||
|
|
||||||
`[必须]` 密码要加引号,纯数字密码不加引号会被 YAML 解析成整数,前导 0 也会丢:
|
`[必须]` 密码要加引号,纯数字密码不加引号会被 YAML 解析成整数,前导 0 也会丢:
|
||||||
|
|
||||||
@@ -159,18 +165,32 @@ syb:
|
|||||||
|
|
||||||
## 6. 数据库在哪里、怎样配置
|
## 6. 数据库在哪里、怎样配置
|
||||||
|
|
||||||
生产数据在 MySQL 8.4 中。Admin 从环境变量读取连接信息:
|
生产数据在 MySQL 8.4 中。Windows 本地运行可以直接在被 Git 忽略的
|
||||||
|
`admin/config.yaml` 中配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "3307"
|
||||||
|
name: autobuy
|
||||||
|
user: buy
|
||||||
|
password: "从安全渠道取得"
|
||||||
|
```
|
||||||
|
|
||||||
|
连接线上 MySQL 时仍通过 SSH 隧道,把 `port` 改为隧道的本地端口;禁止开放线上
|
||||||
|
MySQL 公网端口。线上 systemd 部署继续使用环境变量:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
CMAUTOBUY_DB_HOST=127.0.0.1
|
CMAUTOBUY_DB_HOST=127.0.0.1
|
||||||
CMAUTOBUY_DB_PORT=3307
|
CMAUTOBUY_DB_PORT=3307
|
||||||
CMAUTOBUY_DB_NAME=<独立数据库名>
|
CMAUTOBUY_DB_NAME=<独立数据库名>
|
||||||
CMAUTOBUY_DB_USER=<最小权限账号>
|
CMAUTOBUY_DB_USER=<最小权限账号>
|
||||||
CMAUTOBUY_DB_PASSWORD=<只保存在服务器环境文件中的密码>
|
CMAUTOBUY_DB_PASSWORD=<从安全渠道取得>
|
||||||
```
|
```
|
||||||
|
|
||||||
密码不得写入 `config.yaml`、仓库、日志或工单。线上 MySQL 只监听本机,
|
环境变量按字段覆盖 `config.yaml`,所以现有线上部署方式不受影响。生产环境文件和
|
||||||
本地开发通过 SSH 隧道连接独立的 `_test` 数据库。
|
本地 `config.yaml` 都不得进入仓库、日志或工单;线上 MySQL 只监听本机,本地开发
|
||||||
|
通过 SSH 隧道连接独立的 `_test` 数据库。
|
||||||
|
|
||||||
程序旁边的 `data/` 只保留日志、上传文件和迁移前的 SQLite 只读备份:
|
程序旁边的 `data/` 只保留日志、上传文件和迁移前的 SQLite 只读备份:
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,10 @@
|
|||||||
|
|
||||||
`[必须]` **不得记录** token、密码、Cookie,也不要把完整请求体无脑打进日志。
|
`[必须]` **不得记录** token、密码、Cookie,也不要把完整请求体无脑打进日志。
|
||||||
|
|
||||||
|
`[必须]` 本机 `admin/config.yaml` 可以保存 MySQL 和顺运宝明文密码,但必须保持
|
||||||
|
Git 忽略且不得随程序打包、截图或粘贴到工单和日志;线上优先使用权限 `600` 的
|
||||||
|
环境文件,环境变量按字段覆盖 YAML。
|
||||||
|
|
||||||
## 7. 性能
|
## 7. 性能
|
||||||
|
|
||||||
规模很小(个位数操作员、集中部署),不要提前优化。但下面几条是基本功:
|
规模很小(个位数操作员、集中部署),不要提前优化。但下面几条是基本功:
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
- 源 `admin.db` 必须是 schema v8;工具使用操作系统只读模式和 SQLite `query_only` 打开。
|
- 源 `admin.db` 必须是 schema v8;工具使用操作系统只读模式和 SQLite `query_only` 打开。
|
||||||
- 目标必须是本程序刚建立且业务表为空的独立 MySQL 数据库;任何业务表非空都会拒绝执行。
|
- 目标必须是本程序刚建立且业务表为空的独立 MySQL 数据库;任何业务表非空都会拒绝执行。
|
||||||
- 不支持覆盖、合并、双写和重复导入。重新演练时新建另一个空测试库。
|
- 不支持覆盖、合并、双写和重复导入。重新演练时新建另一个空测试库。
|
||||||
- 数据库密码只放环境变量,不要写入命令历史、仓库、日志或工单。
|
- 迁移命令优先使用环境变量传入数据库密码;Windows 本地也可读取被 Git 忽略的
|
||||||
|
`admin/config.yaml`。两种方式都不得把密码写入命令历史、仓库、日志或工单。
|
||||||
- 输出只包含表名和数量,不包含账号、Cookie、Session、订单号或商品明细。
|
- 输出只包含表名和数量,不包含账号、Cookie、Session、订单号或商品明细。
|
||||||
|
|
||||||
## 2. 迁移前准备
|
## 2. 迁移前准备
|
||||||
|
|||||||
Reference in New Issue
Block a user