All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
主要改动: - 新增交互式环境配置脚本 (scripts/setup-env.sh) - 新增本地启动快捷脚本 (scripts/run-local.sh) - 新增环境变量模板文件 (.env.example) - 部署模式改版:使用嵌入式配置 + 环境变量覆盖 - 添加对象存储功能支持 - 改进 IoT 卡片导入任务 - 优化 OpenAPI 文档生成 - 删除旧的配置文件,改用嵌入式默认配置
282 lines
7.3 KiB
Markdown
282 lines
7.3 KiB
Markdown
# 技术设计:部署自初始化
|
||
|
||
## Context
|
||
|
||
### 当前状态
|
||
|
||
1. **目录创建分散**
|
||
- `pkg/storage/s3.go` 在初始化时创建临时目录
|
||
- 日志目录依赖 Dockerfile 预创建
|
||
- 没有统一的初始化入口
|
||
|
||
2. **配置管理复杂**
|
||
- 4 个外部配置文件:`config.yaml`、`config.dev.yaml`、`config.staging.yaml`、`config.prod.yaml`
|
||
- `pkg/config/watcher.go` 实现热重载(开发阶段不需要)
|
||
- 必须手动拷贝配置文件才能启动
|
||
|
||
3. **部署流程繁琐**
|
||
- 需要手动创建目录结构
|
||
- 需要手动拷贝配置文件
|
||
- Docker Compose 挂载 configs 目录
|
||
|
||
### 约束
|
||
|
||
- 使用 Go 1.16+ 的 `go:embed` 特性
|
||
- 保持 Viper 作为配置解析库
|
||
- 容器内以非 root 用户 (appuser:1000) 运行
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- 应用启动时自动创建所有必需目录
|
||
- 配置嵌入二进制,无需外部配置文件
|
||
- 环境变量作为配置覆盖机制
|
||
- 部署流程简化到 1 步
|
||
|
||
**Non-Goals:**
|
||
- 配置热重载(开发阶段移除)
|
||
- 多配置文件支持(统一用嵌入默认值 + 环境变量)
|
||
- 向后兼容旧的配置方式
|
||
|
||
## Decisions
|
||
|
||
### Decision 1: 目录初始化放在应用层
|
||
|
||
**选择**: 在 `main.go` 启动时调用集中化的目录初始化函数
|
||
|
||
**备选方案**:
|
||
| 方案 | 优点 | 缺点 |
|
||
|------|------|------|
|
||
| Dockerfile RUN mkdir | 镜像固定 | 不支持运行时配置路径 |
|
||
| Entrypoint 脚本 | 运行时动态 | Shell 脚本维护成本高 |
|
||
| **应用代码初始化** | 跨环境通用、错误处理清晰 | 无 |
|
||
|
||
**理由**: 应用代码可以读取配置中的路径,提供 Go 级别的错误处理,并在所有部署环境(Docker、K8s、裸机)通用。
|
||
|
||
### Decision 2: 配置嵌入 + 环境变量覆盖
|
||
|
||
**选择**: 使用 `go:embed` 嵌入默认配置,环境变量覆盖
|
||
|
||
**配置优先级**:
|
||
```
|
||
环境变量 (JUNHONG_*) > 嵌入默认值
|
||
```
|
||
|
||
**备选方案**:
|
||
| 方案 | 优点 | 缺点 |
|
||
|------|------|------|
|
||
| 纯外部文件 | 运行时可改 | 部署复杂 |
|
||
| 纯环境变量 | 12-Factor | 复杂配置难表达 |
|
||
| **嵌入 + 环境变量** | 开箱即用 + 灵活覆盖 | 改默认值需重编译 |
|
||
|
||
**理由**: 嵌入配置保证"开箱即用",环境变量覆盖满足生产环境定制需求。开发阶段不需要频繁改默认值。
|
||
|
||
### Decision 3: 环境变量命名规范
|
||
|
||
**选择**: `JUNHONG_` 前缀 + 下划线分隔
|
||
|
||
**格式**: `JUNHONG_{SECTION}_{KEY}`
|
||
|
||
**示例**:
|
||
```bash
|
||
JUNHONG_DATABASE_HOST=localhost
|
||
JUNHONG_DATABASE_PORT=5432
|
||
JUNHONG_REDIS_ADDRESS=localhost:6379
|
||
JUNHONG_SERVER_ADDRESS=:3000
|
||
```
|
||
|
||
**理由**:
|
||
- 前缀避免与系统环境变量冲突
|
||
- 下划线分隔便于 Viper 的 `SetEnvKeyReplacer` 处理
|
||
- 符合业界惯例(类似 `POSTGRES_`、`REDIS_` 等)
|
||
|
||
### Decision 4: 删除配置热重载
|
||
|
||
**选择**: 删除 `pkg/config/watcher.go` 及相关逻辑
|
||
|
||
**理由**:
|
||
- 开发阶段,配置变更频率低
|
||
- 重启容器即可应用新配置
|
||
- 减少代码复杂度
|
||
|
||
### Decision 5: 目录降级策略
|
||
|
||
**选择**: 权限不足时使用系统临时目录作为 fallback
|
||
|
||
```go
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
if os.IsPermission(err) {
|
||
fallback := filepath.Join(os.TempDir(), "junhong", filepath.Base(dir))
|
||
os.MkdirAll(fallback, 0755)
|
||
return fallback, nil
|
||
}
|
||
return "", err
|
||
}
|
||
```
|
||
|
||
**理由**: 提高容错性,即使权限配置不当也能启动(降级运行)。
|
||
|
||
## Architecture
|
||
|
||
### 启动流程
|
||
|
||
```
|
||
main.go
|
||
│
|
||
├── 1. config.Load() // 加载嵌入配置 + 环境变量覆盖
|
||
│ ├── 读取 go:embed 的 defaults/config.yaml
|
||
│ ├── 应用 JUNHONG_* 环境变量覆盖
|
||
│ └── 验证必填配置
|
||
│
|
||
├── 2. bootstrap.EnsureDirectories(cfg) // 创建所有必需目录
|
||
│ ├── 临时文件目录
|
||
│ ├── 日志目录
|
||
│ └── 其他运行时目录
|
||
│
|
||
├── 3. logger.Init(cfg) // 初始化日志
|
||
│
|
||
├── 4. database.Init(cfg) // 初始化数据库
|
||
│
|
||
└── 5. ... 其他组件初始化
|
||
```
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
pkg/
|
||
├── bootstrap/
|
||
│ └── directories.go # 新增:目录初始化
|
||
├── config/
|
||
│ ├── config.go # 保留:配置结构定义
|
||
│ ├── loader.go # 重写:嵌入配置加载
|
||
│ ├── embedded.go # 新增:go:embed 逻辑
|
||
│ ├── defaults/
|
||
│ │ └── config.yaml # 新增:嵌入的默认配置
|
||
│ └── watcher.go # 删除
|
||
```
|
||
|
||
### 嵌入配置内容
|
||
|
||
```yaml
|
||
# pkg/config/defaults/config.yaml
|
||
server:
|
||
address: ":3000"
|
||
read_timeout: 30s
|
||
write_timeout: 30s
|
||
shutdown_timeout: 30s
|
||
prefork: false
|
||
|
||
database:
|
||
host: "" # 必须通过 JUNHONG_DATABASE_HOST 设置
|
||
port: 5432
|
||
user: "" # 必须通过 JUNHONG_DATABASE_USER 设置
|
||
password: "" # 必须通过 JUNHONG_DATABASE_PASSWORD 设置
|
||
dbname: "" # 必须通过 JUNHONG_DATABASE_DBNAME 设置
|
||
sslmode: "disable"
|
||
max_open_conns: 25
|
||
max_idle_conns: 10
|
||
conn_max_lifetime: 5m
|
||
|
||
redis:
|
||
address: "" # 必须通过 JUNHONG_REDIS_ADDRESS 设置
|
||
port: 6379
|
||
password: ""
|
||
db: 0
|
||
pool_size: 10
|
||
min_idle_conns: 5
|
||
dial_timeout: 5s
|
||
read_timeout: 3s
|
||
write_timeout: 3s
|
||
|
||
storage:
|
||
provider: "s3"
|
||
temp_dir: "/tmp/junhong-storage"
|
||
s3:
|
||
endpoint: ""
|
||
region: ""
|
||
bucket: ""
|
||
access_key_id: ""
|
||
secret_access_key: ""
|
||
use_ssl: false
|
||
path_style: true
|
||
presign:
|
||
upload_expires: 15m
|
||
download_expires: 24h
|
||
|
||
logging:
|
||
level: "info"
|
||
development: false
|
||
app_log:
|
||
filename: "/app/logs/app.log"
|
||
max_size: 100
|
||
max_backups: 3
|
||
max_age: 7
|
||
compress: true
|
||
access_log:
|
||
filename: "/app/logs/access.log"
|
||
max_size: 100
|
||
max_backups: 3
|
||
max_age: 7
|
||
compress: true
|
||
|
||
queue:
|
||
concurrency: 10
|
||
retry_max: 5
|
||
timeout: 10m
|
||
|
||
jwt:
|
||
secret_key: "" # 必须通过 JUNHONG_JWT_SECRET_KEY 设置
|
||
token_duration: 24h
|
||
access_token_ttl: 24h
|
||
refresh_token_ttl: 168h
|
||
|
||
middleware:
|
||
enable_rate_limiter: false
|
||
rate_limiter:
|
||
max: 100
|
||
expiration: 1m
|
||
storage: "memory"
|
||
```
|
||
|
||
## Risks / Trade-offs
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
|------|------|----------|
|
||
| 嵌入配置修改需重新编译 | 低(开发阶段) | 敏感配置通过环境变量,嵌入的只是默认值 |
|
||
| 环境变量泄露 | 中 | 使用 K8s Secrets 或 Docker Secrets 管理 |
|
||
| 目录降级后行为不一致 | 低 | 降级时打印 WARN 日志,明确告知 |
|
||
| 删除热重载后调试不便 | 低 | 开发时直接重启进程,生产用 rolling update |
|
||
|
||
## Migration Plan
|
||
|
||
### 实施步骤
|
||
|
||
1. **新增目录初始化模块**
|
||
- 创建 `pkg/bootstrap/directories.go`
|
||
- 在 main.go 中调用
|
||
|
||
2. **新增配置嵌入模块**
|
||
- 创建 `pkg/config/defaults/config.yaml`
|
||
- 创建 `pkg/config/embedded.go`
|
||
- 重写 `pkg/config/loader.go`
|
||
|
||
3. **清理旧配置逻辑**
|
||
- 删除 `pkg/config/watcher.go`
|
||
- 删除 `configs/*.yaml`
|
||
|
||
4. **更新 Docker 配置**
|
||
- 更新 `Dockerfile.api` 和 `Dockerfile.worker`
|
||
- 重写 `docker-compose.prod.yml`
|
||
|
||
5. **更新文档**
|
||
- 更新 README.md 部署说明
|
||
- 更新环境变量文档
|
||
|
||
### 回滚策略
|
||
|
||
如需回滚,恢复 git 提交即可(开发阶段无生产数据风险)。
|
||
|
||
## Open Questions
|
||
|
||
无。设计已明确,可直接实施。
|