create-dwy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +31 -0
- package/package.json +29 -0
- package/src/create.js +93 -0
- package/src/sync.js +75 -0
- package/src/utils.js +61 -0
- package/templates/claude-global/commands/.gitkeep +0 -0
- package/templates/claude-global/hooks/.gitkeep +0 -0
- package/templates/claude-global/rules/git-security.md +144 -0
- package/templates/claude-global/rules/python-code-style.md +875 -0
- package/templates/claude-global/rules/server-security.md +376 -0
- package/templates/claude-global/skills/.gitkeep +0 -0
- package/templates/project/web/.claude/commands/deploy.md +4 -0
- package/templates/project/web/.dwy.hbs +5 -0
- package/templates/project/web/CLAUDE.md.hbs +52 -0
- package/templates/project/web/backend/.python-version +1 -0
- package/templates/project/web/backend/Dockerfile +17 -0
- package/templates/project/web/backend/alembic/env.py.hbs +44 -0
- package/templates/project/web/backend/alembic/script.py.mako +24 -0
- package/templates/project/web/backend/alembic/versions/.gitkeep +0 -0
- package/templates/project/web/backend/alembic.ini.hbs +36 -0
- package/templates/project/web/backend/pyproject.toml.hbs +52 -0
- package/templates/project/web/backend/src/app/__init__.py +0 -0
- package/templates/project/web/backend/src/app/config.py.hbs +12 -0
- package/templates/project/web/backend/src/app/main.py.hbs +40 -0
- package/templates/project/web/backend/src/app/models/__init__.py +0 -0
- package/templates/project/web/backend/src/app/models/base.py +5 -0
- package/templates/project/web/backend/src/app/routers/__init__.py +0 -0
- package/templates/project/web/backend/src/app/routers/health.py +11 -0
- package/templates/project/web/backend/src/app/schemas/__init__.py +0 -0
- package/templates/project/web/backend/src/app/services/__init__.py +0 -0
- package/templates/project/web/backend/tests/__init__.py +0 -0
- package/templates/project/web/backend/tests/conftest.py.hbs +16 -0
- package/templates/project/web/deploy.sh.hbs +113 -0
- package/templates/project/web/docker-compose.dev.yml.hbs +19 -0
- package/templates/project/web/docker-compose.prod.yml.hbs +31 -0
- package/templates/project/web/frontend/index.html.hbs +12 -0
- package/templates/project/web/frontend/package.json.hbs +28 -0
- package/templates/project/web/frontend/src/App.vue +3 -0
- package/templates/project/web/frontend/src/api/client.ts +13 -0
- package/templates/project/web/frontend/src/components/.gitkeep +0 -0
- package/templates/project/web/frontend/src/main.ts +10 -0
- package/templates/project/web/frontend/src/router/index.ts +14 -0
- package/templates/project/web/frontend/src/stores/auth.ts +20 -0
- package/templates/project/web/frontend/src/style.css +1 -0
- package/templates/project/web/frontend/src/views/HomeView.vue +5 -0
- package/templates/project/web/frontend/tsconfig.app.json +14 -0
- package/templates/project/web/frontend/tsconfig.json +4 -0
- package/templates/project/web/frontend/vite.config.ts.hbs +16 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-security
|
|
3
|
+
description: 服务器安全部署规则
|
|
4
|
+
type: claude-rule
|
|
5
|
+
tags: [security, server, docker, nginx]
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
author: chances
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 服务器安全部署规则
|
|
11
|
+
|
|
12
|
+
所有项目部署到云服务器时必须遵循以下规则。如果任何操作违反这些规则,必须**立即提示用户并获得明确确认**后才能继续。
|
|
13
|
+
|
|
14
|
+
## 一、端口与网络隔离
|
|
15
|
+
|
|
16
|
+
### 强制规则
|
|
17
|
+
- 数据库(PostgreSQL/MySQL/Redis 等)**禁止暴露到宿主机端口**,仅通过 Docker 内部网络访问
|
|
18
|
+
- 后端应用端口绑定 `127.0.0.1`,不允许 `0.0.0.0`
|
|
19
|
+
- 仅 Nginx/反向代理的 80/443 端口绑定 `0.0.0.0`
|
|
20
|
+
|
|
21
|
+
### docker-compose 示例
|
|
22
|
+
```yaml
|
|
23
|
+
# ✅ 正确
|
|
24
|
+
backend:
|
|
25
|
+
ports:
|
|
26
|
+
- "127.0.0.1:{port}:{port}"
|
|
27
|
+
|
|
28
|
+
db:
|
|
29
|
+
# 不写 ports,仅内部网络
|
|
30
|
+
|
|
31
|
+
# ❌ 违规 — 需要提示用户
|
|
32
|
+
db:
|
|
33
|
+
ports:
|
|
34
|
+
- "{db_port}:{db_port}"
|
|
35
|
+
backend:
|
|
36
|
+
ports:
|
|
37
|
+
- "{port}:{port}"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 二、凭证与密钥管理
|
|
41
|
+
|
|
42
|
+
### 强制规则
|
|
43
|
+
- **禁止在代码中硬编码**数据库密码、API Key、Secret 等凭证
|
|
44
|
+
- 所有凭证通过 `.env` 文件或环境变量注入
|
|
45
|
+
- `.env` 文件必须在 `.gitignore` 中排除
|
|
46
|
+
- 数据库密码最少 24 位,包含大小写字母和数字
|
|
47
|
+
- 提供 `.env.example` 模板文件(仅含占位符,不含真实值)
|
|
48
|
+
|
|
49
|
+
### 违规检测
|
|
50
|
+
如果在代码中发现以下内容,必须提示用户:
|
|
51
|
+
- 硬编码的数据库连接字符串含真实密码
|
|
52
|
+
- API Key 出现在非 `.env` 文件中
|
|
53
|
+
- SSH 密码、私钥路径出现在代码或文档中
|
|
54
|
+
|
|
55
|
+
## 三、Nginx 安全配置(最小访问控制)
|
|
56
|
+
|
|
57
|
+
### 核心原则
|
|
58
|
+
**只开放业务必需的路径和方法,其余一律拒绝。**
|
|
59
|
+
|
|
60
|
+
### 强制规则
|
|
61
|
+
|
|
62
|
+
每个项目的 Nginx 配置必须包含以下安全基线(具体路径、端口、超时时间根据项目在项目级 CLAUDE.md 中配置):
|
|
63
|
+
|
|
64
|
+
**1. 频率限制(http 块)**
|
|
65
|
+
```nginx
|
|
66
|
+
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
|
67
|
+
limit_req_zone $binary_remote_addr zone=page_limit:10m rate=30r/s;
|
|
68
|
+
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**2. 安全响应头(server 块)**
|
|
72
|
+
```nginx
|
|
73
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
74
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
75
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
76
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**3. 全局限制(server 块)**
|
|
80
|
+
```nginx
|
|
81
|
+
client_max_body_size 1m; # 无文件上传业务时默认 1m
|
|
82
|
+
client_body_timeout 10s;
|
|
83
|
+
client_header_timeout 10s;
|
|
84
|
+
limit_conn conn_limit 20;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**4. 敏感文件屏蔽(最高优先级)**
|
|
88
|
+
```nginx
|
|
89
|
+
location ~ /\. {
|
|
90
|
+
deny all;
|
|
91
|
+
return 404;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
location ~* \.(env|git|xlsx|sql|log|bak|dump|tar|gz|zip|csv)$ {
|
|
95
|
+
deny all;
|
|
96
|
+
return 404;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**5. API 路径最小化**
|
|
101
|
+
```nginx
|
|
102
|
+
location /api {
|
|
103
|
+
limit_except GET POST {
|
|
104
|
+
deny all;
|
|
105
|
+
}
|
|
106
|
+
limit_req zone=api_limit burst=20 nodelay;
|
|
107
|
+
# proxy_pass 等具体配置由项目决定
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**6. 攻击路径屏蔽**
|
|
112
|
+
```nginx
|
|
113
|
+
location ~ ^/(admin|phpmyadmin|wp-admin|cgi-bin|actuator|console|debug) {
|
|
114
|
+
deny all;
|
|
115
|
+
return 404;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 违规检测
|
|
120
|
+
如果 Nginx 配置中出现以下情况,必须提示用户:
|
|
121
|
+
- 缺少 `limit_req` 频率限制
|
|
122
|
+
- `client_max_body_size` 大于 10m(除非有文件上传业务)
|
|
123
|
+
- API 路径允许 PUT/DELETE 但业务不需要
|
|
124
|
+
- 缺少 `location ~ /\.` 敏感文件屏蔽
|
|
125
|
+
- 使用 `proxy_pass` 但未设置 `proxy_connect_timeout`
|
|
126
|
+
|
|
127
|
+
### 部署后必须验证
|
|
128
|
+
```bash
|
|
129
|
+
# 1. 敏感文件返回 404
|
|
130
|
+
curl -s -o /dev/null -w "%{http_code}" http://{server}/.git/config
|
|
131
|
+
curl -s -o /dev/null -w "%{http_code}" http://{server}/.env
|
|
132
|
+
|
|
133
|
+
# 2. 安全头存在
|
|
134
|
+
curl -sI http://{server}/ | grep -i "x-frame-options"
|
|
135
|
+
curl -sI http://{server}/ | grep -i "x-content-type-options"
|
|
136
|
+
|
|
137
|
+
# 3. 非法方法被拒绝
|
|
138
|
+
curl -s -o /dev/null -w "%{http_code}" -X DELETE http://{server}/api/health
|
|
139
|
+
|
|
140
|
+
# 4. 攻击路径被屏蔽
|
|
141
|
+
curl -s -o /dev/null -w "%{http_code}" http://{server}/phpmyadmin
|
|
142
|
+
|
|
143
|
+
# 5. 正常服务可用
|
|
144
|
+
curl -s http://{server}/api/health
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 四、Docker 容器安全
|
|
148
|
+
|
|
149
|
+
### 强制规则
|
|
150
|
+
- Dockerfile 必须使用非 root 用户运行应用
|
|
151
|
+
- 必须创建 `.dockerignore` 排除 `.env`、`.git`、`cache/`、`__pycache__`
|
|
152
|
+
- Python 项目设置 `ENV PYTHONUNBUFFERED=1`
|
|
153
|
+
- Node.js 项目设置 `ENV NODE_ENV=production`
|
|
154
|
+
|
|
155
|
+
## 五、CORS 配置
|
|
156
|
+
|
|
157
|
+
### 强制规则
|
|
158
|
+
- **禁止生产环境使用 `*` 通配符**
|
|
159
|
+
- 必须明确列出允许的域名/IP
|
|
160
|
+
- 通过环境变量配置,便于不同环境切换
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
# ✅ 正确 — 明确列出
|
|
164
|
+
ALLOWED_ORIGINS=http://localhost,http://{server_ip}
|
|
165
|
+
|
|
166
|
+
# ❌ 违规
|
|
167
|
+
ALLOWED_ORIGINS=*
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 六、API 安全
|
|
171
|
+
|
|
172
|
+
### 强制规则
|
|
173
|
+
- 敏感数据查询接口必须有限流(推荐 5-10 次/分钟/IP)
|
|
174
|
+
- 错误消息不得回显用户输入内容(防止信息泄露)
|
|
175
|
+
- 不得提供批量查询/列表接口暴露全量数据
|
|
176
|
+
- 如项目含认证机制,未配置时必须在日志中输出 WARNING
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
# ✅ 正确 — 通用错误消息
|
|
180
|
+
return {"message": "记录不存在"}
|
|
181
|
+
|
|
182
|
+
# ❌ 违规 — 回显用户输入
|
|
183
|
+
return {"message": f"未找到: {user_input}"}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 七、数据安全
|
|
187
|
+
|
|
188
|
+
### 强制规则
|
|
189
|
+
- 手机号在返回前必须脱敏(如 `138****5678`)
|
|
190
|
+
- 身份证号在返回前必须脱敏(如 `420***********1234`)
|
|
191
|
+
- 查询日志中的 PII(姓名、手机号等)建议加密存储
|
|
192
|
+
- 数据源文件(Excel/CSV 等)不可通过 Web 直接访问
|
|
193
|
+
- 数据库数据仅通过应用层访问,不暴露数据库管理端口
|
|
194
|
+
|
|
195
|
+
## 八、Git 仓库安全
|
|
196
|
+
|
|
197
|
+
### 强制规则
|
|
198
|
+
`.gitignore` 必须包含:
|
|
199
|
+
```
|
|
200
|
+
.env
|
|
201
|
+
*.env
|
|
202
|
+
__pycache__/
|
|
203
|
+
*.pyc
|
|
204
|
+
node_modules/
|
|
205
|
+
dist/
|
|
206
|
+
*.log
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 违规检测
|
|
210
|
+
如果发现以下文件被 `git add`,必须立即阻止并提示用户:
|
|
211
|
+
- `.env` 文件
|
|
212
|
+
- 含真实密码/密钥的配置文件
|
|
213
|
+
- SSH 私钥文件
|
|
214
|
+
- 数据库备份文件(`.sql`、`.dump`)
|
|
215
|
+
- 含敏感数据的原始数据文件(`.xlsx`、`.csv`)
|
|
216
|
+
|
|
217
|
+
## 九、SSH 安全
|
|
218
|
+
|
|
219
|
+
### 强制规则
|
|
220
|
+
- **禁止密码登录**,仅允许密钥认证
|
|
221
|
+
- **禁止 root 密码登录**(`PermitRootLogin prohibit-password` 或 `without-password`)
|
|
222
|
+
- **禁止使用默认 22 端口**,改为非标端口(如 66)
|
|
223
|
+
- SSH 私钥文件(`id_rsa`、`id_ed25519` 等)**禁止出现在代码仓库、文档、聊天记录中**
|
|
224
|
+
- 服务器 IP、SSH 端口、用户名**禁止写入代码仓库**(包括 README、部署文档)
|
|
225
|
+
|
|
226
|
+
### 违规检测
|
|
227
|
+
如果在任何文件中发现以下内容,必须立即提示用户:
|
|
228
|
+
- SSH 连接命令含完整 IP + 端口 + 用户名
|
|
229
|
+
- 服务器凭证信息(IP、端口、密码、密钥路径)
|
|
230
|
+
- `sshd_config` 中 `PasswordAuthentication yes`
|
|
231
|
+
|
|
232
|
+
### 安全基线配置(`/etc/ssh/sshd_config`)
|
|
233
|
+
```
|
|
234
|
+
Port 66 # 非标端口
|
|
235
|
+
PermitRootLogin prohibit-password # 禁止 root 密码登录
|
|
236
|
+
PasswordAuthentication no # 禁止所有密码登录
|
|
237
|
+
PubkeyAuthentication yes # 启用密钥认证
|
|
238
|
+
MaxAuthTries 3 # 最大尝试次数
|
|
239
|
+
LoginGraceTime 30 # 登录超时 30 秒
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 配合 fail2ban
|
|
243
|
+
建议服务器配置 fail2ban 防暴力破解:
|
|
244
|
+
```
|
|
245
|
+
# /etc/fail2ban/jail.local
|
|
246
|
+
[sshd]
|
|
247
|
+
enabled = true
|
|
248
|
+
port = 66
|
|
249
|
+
maxretry = 5
|
|
250
|
+
bantime = 3600
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 部署操作规范
|
|
254
|
+
- 通过 SSH 连接服务器时,连接信息从本地 `~/.ssh/config` 读取,不在命令行暴露
|
|
255
|
+
- 推荐在 `~/.ssh/config` 中配置 Host 别名:
|
|
256
|
+
```
|
|
257
|
+
Host myserver
|
|
258
|
+
HostName <ip>
|
|
259
|
+
Port <port>
|
|
260
|
+
User root
|
|
261
|
+
IdentityFile ~/.ssh/id_rsa
|
|
262
|
+
```
|
|
263
|
+
- 部署脚本中使用 `ssh myserver` 而非完整连接命令
|
|
264
|
+
|
|
265
|
+
## 十、数据持久化与防删除
|
|
266
|
+
|
|
267
|
+
### 核心原则
|
|
268
|
+
**所有业务数据必须通过 Docker Named Volume 持久化。任何可能导致数据丢失的操作,必须提前说明原因并获得用户确认。**
|
|
269
|
+
|
|
270
|
+
### 强制规则 — Docker Volume
|
|
271
|
+
|
|
272
|
+
- 数据库容器**必须挂载 Named Volume**到数据目录
|
|
273
|
+
- **禁止使用匿名卷或仅容器内存储**
|
|
274
|
+
- 重启策略必须设为 `unless-stopped` 或 `always`
|
|
275
|
+
|
|
276
|
+
```yaml
|
|
277
|
+
# ✅ 正确 — Named Volume 持久化
|
|
278
|
+
db:
|
|
279
|
+
volumes:
|
|
280
|
+
- dbdata:/var/lib/postgresql/data # PostgreSQL
|
|
281
|
+
# 或 - dbdata:/var/lib/mysql # MySQL
|
|
282
|
+
restart: unless-stopped
|
|
283
|
+
|
|
284
|
+
volumes:
|
|
285
|
+
dbdata:
|
|
286
|
+
|
|
287
|
+
# ❌ 违规 — 数据在容器内,删除即丢失
|
|
288
|
+
db:
|
|
289
|
+
# 没有 volumes 配置
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 危险操作清单
|
|
293
|
+
|
|
294
|
+
以下命令**执行前必须停止、说明原因、说明影响、等待用户确认**:
|
|
295
|
+
|
|
296
|
+
| 命令 | 风险等级 | 后果 |
|
|
297
|
+
|---|---|---|
|
|
298
|
+
| `docker compose down -v` | **致命** | 删除所有 Volume,数据永久丢失 |
|
|
299
|
+
| `docker volume rm <name>` | **致命** | 删除指定 Volume,数据永久丢失 |
|
|
300
|
+
| `docker volume prune` | **致命** | 删除所有未使用的 Volume |
|
|
301
|
+
| `docker system prune -a --volumes` | **致命** | 清除所有镜像、容器、Volume |
|
|
302
|
+
| `rm -rf /var/lib/docker/volumes/` | **致命** | 删除所有 Docker 数据 |
|
|
303
|
+
| `DROP TABLE` / `DROP DATABASE` | **致命** | 删除数据库表或整个数据库 |
|
|
304
|
+
| `DELETE FROM <table>` 无 WHERE | **高危** | 清空整张表数据 |
|
|
305
|
+
| `TRUNCATE TABLE` | **高危** | 清空表数据且无法回滚 |
|
|
306
|
+
| `rm -rf` 含数据目录 | **高危** | 删除文件系统数据 |
|
|
307
|
+
| `git clean -fdx` | **高危** | 删除所有未跟踪文件(含数据文件) |
|
|
308
|
+
|
|
309
|
+
### 违规处理
|
|
310
|
+
当 AI 需要执行以上任何操作时,必须:
|
|
311
|
+
|
|
312
|
+
1. **立即停止**,不执行该命令
|
|
313
|
+
2. **说明原因**:为什么需要执行这个操作
|
|
314
|
+
3. **说明影响**:会删除哪些数据、多少条记录
|
|
315
|
+
4. **提供替代方案**(如有):是否有不删除数据的方式达到目的
|
|
316
|
+
5. **等待用户明确回复"确认"**后才能执行
|
|
317
|
+
|
|
318
|
+
示例对话:
|
|
319
|
+
```
|
|
320
|
+
AI: 我需要执行 `docker compose down -v` 来重建数据库。
|
|
321
|
+
原因:数据库 schema 发生了不兼容的变更。
|
|
322
|
+
影响:将删除数据库 Volume,所有业务数据将永久丢失。
|
|
323
|
+
替代方案:可以用 ALTER TABLE 迁移 schema,不需要删库。
|
|
324
|
+
是否确认执行?
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 备份建议
|
|
328
|
+
生产环境建议配置定期备份:
|
|
329
|
+
```bash
|
|
330
|
+
# PostgreSQL 备份
|
|
331
|
+
docker exec <db_container> pg_dump -U <user> <database> > backup_$(date +%Y%m%d).sql
|
|
332
|
+
|
|
333
|
+
# MySQL 备份
|
|
334
|
+
docker exec <db_container> mysqldump -u <user> -p<password> <database> > backup_$(date +%Y%m%d).sql
|
|
335
|
+
|
|
336
|
+
# Volume 备份
|
|
337
|
+
docker run --rm -v <volume_name>:/data -v $(pwd):/backup alpine tar czf /backup/volume_$(date +%Y%m%d).tar.gz /data
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 十一、部署后检查清单
|
|
341
|
+
|
|
342
|
+
每次部署完成后,必须执行以下验证:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# 1. 数据库端口不可外部访问
|
|
346
|
+
timeout 3 nc -z {server_ip} {db_port} && echo "FAIL" || echo "PASS"
|
|
347
|
+
|
|
348
|
+
# 2. 后端端口不可外部直连
|
|
349
|
+
timeout 3 nc -z {server_ip} {backend_port} && echo "FAIL" || echo "PASS"
|
|
350
|
+
|
|
351
|
+
# 3. 敏感文件不可下载
|
|
352
|
+
curl -s -o /dev/null -w "%{http_code}" http://{server}/.git/config # 必须 404
|
|
353
|
+
curl -s -o /dev/null -w "%{http_code}" http://{server}/.env # 必须 404
|
|
354
|
+
|
|
355
|
+
# 4. 正常服务可用
|
|
356
|
+
curl -s http://{server}/api/health # 必须返回 200
|
|
357
|
+
|
|
358
|
+
# 5. 安全头存在
|
|
359
|
+
curl -sI http://{server}/ | grep -i "x-frame-options" # 必须有输出
|
|
360
|
+
|
|
361
|
+
# 6. 非法方法被拒绝
|
|
362
|
+
curl -s -o /dev/null -w "%{http_code}" -X DELETE http://{server}/api/health # 必须 403/405
|
|
363
|
+
|
|
364
|
+
# 7. Volume 存在且数据完整
|
|
365
|
+
docker volume ls | grep {volume_name} # 必须存在
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## 十二、违规处理流程
|
|
369
|
+
|
|
370
|
+
当 AI 检测到操作违反以上任何规则时:
|
|
371
|
+
|
|
372
|
+
1. **立即停止**当前操作
|
|
373
|
+
2. **明确告知**用户违反了哪条规则
|
|
374
|
+
3. **说明风险**(可能导致的安全后果)
|
|
375
|
+
4. **等待用户确认**后才能继续执行
|
|
376
|
+
5. 如果用户坚持违规操作,在代码中添加 `# SECURITY WARNING: 用户已确认此操作` 注释
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
{{projectDescription}}
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- Frontend: Vue 3 + TypeScript + Tailwind CSS 4 + Vite
|
|
8
|
+
- Backend: Python 3.11 + FastAPI + SQLAlchemy 2.0 (async)
|
|
9
|
+
- Database: PostgreSQL 16 + Redis 7
|
|
10
|
+
- Shared: @danweiyuan/eui, @danweiyuan/core, danweiyuan-base
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
### Development
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Infrastructure
|
|
18
|
+
docker compose -f docker-compose.dev.yml up -d
|
|
19
|
+
|
|
20
|
+
# Backend
|
|
21
|
+
cd backend
|
|
22
|
+
uv run uvicorn app.main:app --reload --port 8000
|
|
23
|
+
|
|
24
|
+
# Frontend
|
|
25
|
+
cd frontend
|
|
26
|
+
pnpm dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Deploy
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
./deploy.sh # Full deploy
|
|
33
|
+
./deploy.sh --frontend # Frontend only
|
|
34
|
+
./deploy.sh --backend # Backend only
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Test
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd backend && uv run pytest tests/ -v
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
frontend/ → Vue 3 SPA
|
|
47
|
+
backend/ → FastAPI (src/app/)
|
|
48
|
+
routers/ → HTTP endpoints
|
|
49
|
+
services/ → Business logic
|
|
50
|
+
models/ → SQLAlchemy ORM
|
|
51
|
+
schemas/ → Pydantic request/response
|
|
52
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM python:3.11-slim
|
|
2
|
+
|
|
3
|
+
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/*
|
|
4
|
+
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
COPY pyproject.toml .
|
|
8
|
+
RUN pip install --no-cache-dir .
|
|
9
|
+
|
|
10
|
+
COPY src/ src/
|
|
11
|
+
COPY alembic/ alembic/
|
|
12
|
+
COPY alembic.ini .
|
|
13
|
+
|
|
14
|
+
RUN useradd -m appuser
|
|
15
|
+
USER appuser
|
|
16
|
+
|
|
17
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Alembic environment configuration."""
|
|
2
|
+
|
|
3
|
+
from logging.config import fileConfig
|
|
4
|
+
|
|
5
|
+
from alembic import context
|
|
6
|
+
from sqlalchemy import engine_from_config, pool
|
|
7
|
+
|
|
8
|
+
from app.config import settings
|
|
9
|
+
from app.models.base import Base
|
|
10
|
+
|
|
11
|
+
config = context.config
|
|
12
|
+
config.set_main_option("sqlalchemy.url", settings.database_url.replace("+asyncpg", ""))
|
|
13
|
+
|
|
14
|
+
if config.config_file_name is not None:
|
|
15
|
+
fileConfig(config.config_file_name)
|
|
16
|
+
|
|
17
|
+
target_metadata = Base.metadata
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_migrations_offline() -> None:
|
|
21
|
+
"""Run migrations in offline mode."""
|
|
22
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
23
|
+
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
|
24
|
+
with context.begin_transaction():
|
|
25
|
+
context.run_migrations()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_migrations_online() -> None:
|
|
29
|
+
"""Run migrations in online mode."""
|
|
30
|
+
connectable = engine_from_config(
|
|
31
|
+
config.get_section(config.config_ini_section, {}),
|
|
32
|
+
prefix="sqlalchemy.",
|
|
33
|
+
poolclass=pool.NullPool,
|
|
34
|
+
)
|
|
35
|
+
with connectable.connect() as connection:
|
|
36
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
37
|
+
with context.begin_transaction():
|
|
38
|
+
context.run_migrations()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if context.is_offline_mode():
|
|
42
|
+
run_migrations_offline()
|
|
43
|
+
else:
|
|
44
|
+
run_migrations_online()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
"""
|
|
7
|
+
from typing import Sequence, Union
|
|
8
|
+
|
|
9
|
+
from alembic import op
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
${imports if imports else ""}
|
|
12
|
+
|
|
13
|
+
revision: str = ${repr(up_revision)}
|
|
14
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
15
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
16
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
${upgrades if upgrades else "pass"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def downgrade() -> None:
|
|
24
|
+
${downgrades if downgrades else "pass"}
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = alembic
|
|
3
|
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
4
|
+
|
|
5
|
+
[loggers]
|
|
6
|
+
keys = root,sqlalchemy,alembic
|
|
7
|
+
|
|
8
|
+
[handlers]
|
|
9
|
+
keys = console
|
|
10
|
+
|
|
11
|
+
[formatters]
|
|
12
|
+
keys = generic
|
|
13
|
+
|
|
14
|
+
[logger_root]
|
|
15
|
+
level = WARN
|
|
16
|
+
handlers = console
|
|
17
|
+
|
|
18
|
+
[logger_sqlalchemy]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers =
|
|
21
|
+
qualname = sqlalchemy.engine
|
|
22
|
+
|
|
23
|
+
[logger_alembic]
|
|
24
|
+
level = INFO
|
|
25
|
+
handlers =
|
|
26
|
+
qualname = alembic
|
|
27
|
+
|
|
28
|
+
[handler_console]
|
|
29
|
+
class = StreamHandler
|
|
30
|
+
args = (sys.stderr,)
|
|
31
|
+
level = NOTSET
|
|
32
|
+
formatter = generic
|
|
33
|
+
|
|
34
|
+
[formatter_generic]
|
|
35
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
36
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{projectName}}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "{{projectDescription}}"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"danweiyuan-base>=0.1.0",
|
|
8
|
+
"fastapi>=0.115.0",
|
|
9
|
+
"uvicorn[standard]>=0.34.0",
|
|
10
|
+
"sqlalchemy[asyncio]>=2.0.0",
|
|
11
|
+
"asyncpg>=0.30.0",
|
|
12
|
+
"pydantic-settings>=2.7.0",
|
|
13
|
+
"alembic>=1.13.0",
|
|
14
|
+
"python-jose[cryptography]>=3.3.0",
|
|
15
|
+
"passlib[bcrypt]>=1.7.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=8.0.0",
|
|
21
|
+
"pytest-asyncio>=0.24.0",
|
|
22
|
+
"httpx>=0.27.0",
|
|
23
|
+
"aiosqlite>=0.20.0",
|
|
24
|
+
"ruff>=0.6.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=68.0"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
target-version = "py311"
|
|
36
|
+
line-length = 120
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = ["E", "W", "F", "I", "N", "UP", "B", "SIM", "RUF"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint.isort]
|
|
42
|
+
known-first-party = ["app"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
asyncio_mode = "auto"
|
|
46
|
+
|
|
47
|
+
[tool.uv]
|
|
48
|
+
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
|
|
49
|
+
extra-index-url = ["https://pypi.org/simple/"]
|
|
50
|
+
|
|
51
|
+
[tool.uv.sources]
|
|
52
|
+
danweiyuan-base = { index = "https://pypi.org/simple/" }
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""{{projectDescription}} — FastAPI application entry point."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
6
|
+
from danweiyuan_base.cache import close_redis, configure
|
|
7
|
+
from danweiyuan_base.database import create_async_engine_factory, create_session_factory
|
|
8
|
+
from danweiyuan_base.exceptions import register_exception_handlers
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
|
|
12
|
+
from app.config import settings
|
|
13
|
+
from app.routers import health
|
|
14
|
+
|
|
15
|
+
engine = create_async_engine_factory(settings.database_url)
|
|
16
|
+
async_session_factory = create_session_factory(engine)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
21
|
+
"""Application lifespan — startup and shutdown."""
|
|
22
|
+
configure(settings.redis_url)
|
|
23
|
+
yield
|
|
24
|
+
await engine.dispose()
|
|
25
|
+
await close_redis()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
app = FastAPI(title="{{projectName}}", version="0.1.0", lifespan=lifespan)
|
|
29
|
+
|
|
30
|
+
register_exception_handlers(app)
|
|
31
|
+
|
|
32
|
+
app.add_middleware(
|
|
33
|
+
CORSMiddleware,
|
|
34
|
+
allow_origins=settings.allowed_origins,
|
|
35
|
+
allow_credentials=True,
|
|
36
|
+
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
37
|
+
allow_headers=["*"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
app.include_router(health.router)
|
|
File without changes
|
|
File without changes
|