nsbp-cli 0.2.21 → 0.2.24
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/README.md +1 -1
- package/bin/nsbp.js +29 -11
- package/package.json +2 -2
- package/templates/basic/Makefile +60 -18
- package/templates/basic/README.md +155 -3
- package/templates/basic/{webpack.base.js → config/webpack.base.js} +2 -2
- package/templates/basic/{webpack.client.js → config/webpack.client.js} +2 -2
- package/templates/basic/{webpack.server.js → config/webpack.server.js} +3 -3
- package/templates/basic/{Dockerfile → docker/Dockerfile} +2 -2
- package/templates/basic/{Dockerfile.dev → docker/Dockerfile.dev} +1 -1
- package/templates/basic/{docker-compose.dev.yml → docker/docker-compose.dev.yml} +13 -8
- package/templates/basic/{docker-compose.yml → docker/docker-compose.yml} +8 -4
- package/templates/basic/gitignore +2 -0
- package/templates/basic/package.json +10 -7
- package/templates/basic/public/apple-touch-icon.png +0 -0
- package/templates/basic/public/favicon-16x16.png +0 -0
- package/templates/basic/public/favicon-192x192.png +0 -0
- package/templates/basic/public/favicon-32x32.png +0 -0
- package/templates/basic/public/favicon-512x512.png +0 -0
- package/templates/basic/public/favicon.svg +10 -0
- package/templates/basic/public/site.webmanifest +20 -0
- package/templates/basic/scripts/verify-dev.sh +6 -6
- package/templates/basic/scripts/verify-security.sh +124 -0
- package/templates/basic/src/containers/Home.tsx +1 -1
- package/templates/basic/src/server/index.ts +56 -2
- package/templates/basic/src/server/utils.tsx +7 -0
- /package/templates/basic/{postcss.config.js → config/postcss.config.js} +0 -0
- /package/templates/basic/{.dockerignore → docker/.dockerignore} +0 -0
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ node ./bin/nsbp.js --help # Test CLI locally
|
|
|
147
147
|
|
|
148
148
|
- **Package Name**: `nsbp-cli`
|
|
149
149
|
- **Bin Command**: `nsbp` (install globally and run `nsbp --help`)
|
|
150
|
-
- **Version**: `0.2.
|
|
150
|
+
- **Version**: `0.2.24`
|
|
151
151
|
- **Dependencies**: chalk, commander, fs-extra, inquirer
|
|
152
152
|
- **Package Manager**: Uses pnpm (also compatible with npm)
|
|
153
153
|
- **Node Version**: >=16.0.0
|
package/bin/nsbp.js
CHANGED
|
@@ -21,7 +21,7 @@ program
|
|
|
21
21
|
.command('create <project-name>')
|
|
22
22
|
.description('Create a new NSBP project')
|
|
23
23
|
.option('-t, --template <template>', 'Specify template (basic, blog, ecommerce)', 'basic')
|
|
24
|
-
.option('--skip-install', 'Skip
|
|
24
|
+
.option('--skip-install', 'Skip pnpm install')
|
|
25
25
|
.action(async (projectName, options) => {
|
|
26
26
|
console.log(chalk.cyan(`🚀 Creating NSBP project: ${projectName}`));
|
|
27
27
|
|
|
@@ -104,15 +104,10 @@ program
|
|
|
104
104
|
// Create package.json for new project
|
|
105
105
|
const originalPackage = require(path.join(templateSource, 'package.json'));
|
|
106
106
|
const newPackage = {
|
|
107
|
+
...originalPackage,
|
|
107
108
|
name: projectName,
|
|
108
109
|
version: '1.0.0',
|
|
109
|
-
description: `NSBP project: ${projectName}
|
|
110
|
-
scripts: originalPackage.scripts,
|
|
111
|
-
dependencies: originalPackage.dependencies,
|
|
112
|
-
devDependencies: originalPackage.devDependencies,
|
|
113
|
-
keywords: originalPackage.keywords,
|
|
114
|
-
author: originalPackage.author,
|
|
115
|
-
license: originalPackage.license
|
|
110
|
+
description: `NSBP project: ${projectName}`
|
|
116
111
|
};
|
|
117
112
|
|
|
118
113
|
fs.writeFileSync(
|
|
@@ -120,20 +115,43 @@ program
|
|
|
120
115
|
JSON.stringify(newPackage, null, 2)
|
|
121
116
|
);
|
|
122
117
|
|
|
118
|
+
// Remove package-lock.json if exists (use pnpm instead)
|
|
119
|
+
const packageLockPath = path.join(targetDir, 'package-lock.json');
|
|
120
|
+
if (fs.existsSync(packageLockPath)) {
|
|
121
|
+
fs.removeSync(packageLockPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create .npmignore to prevent package-lock.json from being committed
|
|
125
|
+
const npmignorePath = path.join(targetDir, '.npmignore');
|
|
126
|
+
if (!fs.existsSync(npmignorePath)) {
|
|
127
|
+
fs.writeFileSync(npmignorePath, 'package-lock.json\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
// Optionally install dependencies
|
|
124
131
|
if (!options.skipInstall) {
|
|
132
|
+
// Check if pnpm is available
|
|
133
|
+
try {
|
|
134
|
+
execSync('which pnpm', { stdio: 'ignore' });
|
|
135
|
+
} catch {
|
|
136
|
+
console.error(chalk.red('❌ pnpm is not installed or not available in PATH.'));
|
|
137
|
+
console.error(chalk.yellow('Please install pnpm before continuing:'));
|
|
138
|
+
console.error(chalk.cyan(' npm install -g pnpm'));
|
|
139
|
+
console.error(chalk.yellow('Or use --skip-install flag to skip dependency installation.'));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
console.log(chalk.cyan('📦 Installing dependencies...'));
|
|
126
144
|
process.chdir(targetDir);
|
|
127
|
-
execSync('
|
|
145
|
+
execSync('pnpm install', { stdio: 'inherit' });
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
console.log(chalk.green(`✅ NSBP project "${projectName}" created successfully!`));
|
|
131
149
|
console.log(chalk.yellow('\nNext steps:'));
|
|
132
150
|
console.log(` cd ${projectName}`);
|
|
133
151
|
if (options.skipInstall) {
|
|
134
|
-
console.log('
|
|
152
|
+
console.log(' pnpm install');
|
|
135
153
|
}
|
|
136
|
-
console.log('
|
|
154
|
+
console.log(' pnpm run dev');
|
|
137
155
|
console.log(chalk.cyan('\nHappy coding! 🎉'));
|
|
138
156
|
|
|
139
157
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nsbp-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "CLI tool for creating NSBP (Node React SSR by Webpack) projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"homepage": "https://nsbp.erishen.cn/",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"start": "node ./bin/nsbp.js",
|
|
18
18
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
19
19
|
"sync-template": "node ./scripts/sync-template.js",
|
|
20
|
-
"update": "
|
|
20
|
+
"update": "pnpm run sync-template",
|
|
21
21
|
"update-changelog": "node ./scripts/update-changelog.js",
|
|
22
22
|
"changelog": "npx conventional-changelog -p angular -i CHANGELOG.md -s"
|
|
23
23
|
},
|
package/templates/basic/Makefile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: help build dev prod down clean logs restart
|
|
1
|
+
.PHONY: help build dev prod down clean logs restart env-dev env-prod env-local
|
|
2
2
|
|
|
3
3
|
# Package manager detection
|
|
4
4
|
PNPM := $(shell which pnpm)
|
|
@@ -19,48 +19,90 @@ help: ## Show this help message
|
|
|
19
19
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
20
20
|
|
|
21
21
|
build: ## Build Docker images for production
|
|
22
|
-
$(COMPOSE) build
|
|
22
|
+
$(COMPOSE) -f docker/docker-compose.yml build
|
|
23
23
|
|
|
24
24
|
build-dev: ## Build Docker images for development
|
|
25
|
-
$(COMPOSE) -f docker-compose.dev.yml build
|
|
25
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml build
|
|
26
26
|
|
|
27
27
|
dev: ## Start development environment (removes orphan containers)
|
|
28
|
-
$(COMPOSE) -f docker-compose.dev.yml up --build --remove-orphans
|
|
28
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml up --build --remove-orphans
|
|
29
29
|
|
|
30
30
|
prod: ## Start production environment (removes orphan containers)
|
|
31
|
-
$(COMPOSE) up -d --remove-orphans
|
|
31
|
+
$(COMPOSE) -f docker/docker-compose.yml up -d --remove-orphans
|
|
32
32
|
|
|
33
33
|
down: ## Stop and remove containers (including orphan containers)
|
|
34
|
-
$(COMPOSE) down --remove-orphans
|
|
35
|
-
$(COMPOSE) -f docker-compose.dev.yml down --remove-orphans
|
|
34
|
+
$(COMPOSE) -f docker/docker-compose.yml down --remove-orphans
|
|
35
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml down --remove-orphans
|
|
36
36
|
|
|
37
37
|
clean: ## Stop containers, remove images and volumes (including orphan containers)
|
|
38
|
-
$(COMPOSE) down -v --rmi all --remove-orphans
|
|
39
|
-
$(COMPOSE) -f docker-compose.dev.yml down -v --rmi all --remove-orphans
|
|
38
|
+
$(COMPOSE) -f docker/docker-compose.yml down -v --rmi all --remove-orphans
|
|
39
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml down -v --rmi all --remove-orphans
|
|
40
40
|
|
|
41
41
|
logs: ## View logs
|
|
42
|
-
$(COMPOSE) logs -f
|
|
42
|
+
$(COMPOSE) -f docker/docker-compose.yml logs -f
|
|
43
43
|
|
|
44
44
|
logs-dev: ## View development logs
|
|
45
|
-
$(COMPOSE) -f docker-compose.dev.yml logs -f
|
|
45
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml logs -f
|
|
46
46
|
|
|
47
47
|
restart: ## Restart production containers
|
|
48
|
-
$(COMPOSE) restart
|
|
48
|
+
$(COMPOSE) -f docker/docker-compose.yml restart
|
|
49
49
|
|
|
50
50
|
restart-dev: ## Restart development containers
|
|
51
|
-
$(COMPOSE) -f docker-compose.dev.yml restart
|
|
51
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml restart
|
|
52
52
|
|
|
53
53
|
rebuild: ## Rebuild and restart production containers (removes orphan containers)
|
|
54
|
-
$(COMPOSE) up -d --build --remove-orphans
|
|
54
|
+
$(COMPOSE) -f docker/docker-compose.yml up -d --build --remove-orphans
|
|
55
55
|
|
|
56
56
|
rebuild-dev: ## Rebuild and restart development containers (removes orphan containers)
|
|
57
|
-
$(COMPOSE) -f docker-compose.dev.yml up -d --build --remove-orphans
|
|
57
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml up -d --build --remove-orphans
|
|
58
58
|
|
|
59
59
|
shell: ## Open shell in production container
|
|
60
|
-
$(COMPOSE) exec app sh
|
|
60
|
+
$(COMPOSE) -f docker/docker-compose.yml exec app sh
|
|
61
61
|
|
|
62
62
|
shell-dev: ## Open shell in development container
|
|
63
|
-
$(COMPOSE) -f docker-compose.dev.yml exec app sh
|
|
63
|
+
$(COMPOSE) -f docker/docker-compose.dev.yml exec app sh
|
|
64
64
|
|
|
65
65
|
test: ## Run tests (if configured)
|
|
66
|
-
$(COMPOSE) exec app
|
|
66
|
+
$(COMPOSE) -f docker/docker-compose.yml exec app $(PM) test
|
|
67
|
+
|
|
68
|
+
env-dev: ## Set up development environment
|
|
69
|
+
@if [ -f .env.development ]; then \
|
|
70
|
+
cp .env.development .env && \
|
|
71
|
+
echo -e "\033[0;32m✅ 开发环境已配置 (.env)\033[0m"; \
|
|
72
|
+
else \
|
|
73
|
+
echo -e "\033[0;33m⚠️ .env.development 不存在\033[0m"; \
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
env-prod: ## Set up production environment
|
|
77
|
+
@if [ -f .env.production ]; then \
|
|
78
|
+
cp .env.production .env && \
|
|
79
|
+
echo -e "\033[0;32m✅ 生产环境已配置 (.env)\033[0m"; \
|
|
80
|
+
else \
|
|
81
|
+
echo -e "\033[0;33m⚠️ .env.production 不存在\033[0m"; \
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
env-local: ## Set up local environment with sensitive data
|
|
85
|
+
@if [ ! -f .env.local ]; then \
|
|
86
|
+
echo "# Local environment (contains sensitive data)" > .env.local && \
|
|
87
|
+
echo "# DO NOT commit this file to Git" >> .env.local && \
|
|
88
|
+
echo -e "\033[0;32m✅ .env.local 已创建\033[0m"; \
|
|
89
|
+
echo "请编辑 .env.local 添加敏感信息"; \
|
|
90
|
+
else \
|
|
91
|
+
echo -e "\033[0;33m⚠️ .env.local 已存在\033[0m"; \
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
show-env: ## Show current environment variables
|
|
95
|
+
@echo "========================================="
|
|
96
|
+
@echo "当前环境变量配置"
|
|
97
|
+
@echo "========================================="
|
|
98
|
+
@echo ""
|
|
99
|
+
@if [ -f .env ]; then \
|
|
100
|
+
echo "📄 .env 文件内容:"; \
|
|
101
|
+
cat .env | grep -v "^#" | grep -v "^$$"; \
|
|
102
|
+
else \
|
|
103
|
+
echo "⚠️ .env 文件不存在"; \
|
|
104
|
+
fi
|
|
105
|
+
@echo ""
|
|
106
|
+
@if [ -f .env.local ]; then \
|
|
107
|
+
echo "📄 .env.local 文件存在(包含敏感信息)"; \
|
|
108
|
+
fi
|
|
@@ -2,10 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
🌐 **Online Demo**: [https://nsbp.erishen.cn/](https://nsbp.erishen.cn/)
|
|
4
4
|
|
|
5
|
+
## 环境变量配置
|
|
6
|
+
|
|
7
|
+
### 快速开始
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. 复制环境变量模板
|
|
11
|
+
cp .env.example .env
|
|
12
|
+
|
|
13
|
+
# 2. 根据需要编辑 .env 文件
|
|
14
|
+
# 编辑 NODE_ENV、PORT、ENABLE_RATE_LIMIT 等
|
|
15
|
+
|
|
16
|
+
# 3. 开始开发或部署
|
|
17
|
+
pnpm run dev # 本地开发
|
|
18
|
+
docker-compose up -d # Docker 部署
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 环境变量说明
|
|
22
|
+
|
|
23
|
+
| 变量名 | 默认值 | 说明 | 推荐环境 |
|
|
24
|
+
|-------|--------|------|---------|
|
|
25
|
+
| `NODE_ENV` | development | 运行环境 (development/production) | 全部 |
|
|
26
|
+
| `PORT` | 3001 | 服务端口 | 全部 |
|
|
27
|
+
| `ENABLE_RATE_LIMIT` | 0 | 启用速率限制 (1=启用, 0=禁用) | 生产环境 |
|
|
28
|
+
| `DEBUG` | - | 启用调试日志 | 开发环境 |
|
|
29
|
+
| `TZ` | Asia/Shanghai | 时区配置 | 生产环境 |
|
|
30
|
+
|
|
31
|
+
### 配置文件说明
|
|
32
|
+
|
|
33
|
+
- **`.env.example`** - 环境变量模板(提交到 Git)
|
|
34
|
+
- **`.env`** - 本地开发配置(不提交到 Git)
|
|
35
|
+
- **`.env.production`** - 生产环境配置(不提交到 Git)
|
|
36
|
+
- **`.env.development`** - 开发环境配置(不提交到 Git)
|
|
37
|
+
- **`.env.local`** - 本地敏感信息(最高优先级,不提交到 Git)
|
|
38
|
+
|
|
39
|
+
### 配置优先级
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
.env.local > .env > docker-compose.yml 默认值
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 本地开发配置
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 复制开发环境配置
|
|
49
|
+
cp .env.development .env
|
|
50
|
+
|
|
51
|
+
# 或手动创建 .env 文件
|
|
52
|
+
cat > .env << EOF
|
|
53
|
+
NODE_ENV=development
|
|
54
|
+
PORT=3001
|
|
55
|
+
ENABLE_RATE_LIMIT=0
|
|
56
|
+
EOF
|
|
57
|
+
|
|
58
|
+
# 启动开发环境
|
|
59
|
+
pnpm run dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Docker 部署配置
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 生产环境配置
|
|
66
|
+
cp .env.production .env
|
|
67
|
+
|
|
68
|
+
# Docker Compose 会自动读取 .env 文件
|
|
69
|
+
docker-compose up -d
|
|
70
|
+
|
|
71
|
+
# 查看环境变量是否生效
|
|
72
|
+
docker-compose exec app env | grep NODE_ENV
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 敏感信息管理
|
|
76
|
+
|
|
77
|
+
**重要:**
|
|
78
|
+
- ✅ `.env.example` 可以提交到 Git(模板文件)
|
|
79
|
+
- ❌ `.env`、`.env.local` 不要提交到 Git(已在 .gitignore 中)
|
|
80
|
+
- ✅ 敏感信息(密钥、数据库密码)放在 `.env.local` 中
|
|
81
|
+
- ✅ `.env.local` 会覆盖其他配置,优先级最高
|
|
82
|
+
|
|
5
83
|
## 开发
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
84
|
+
- pnpm run dev (开发运行)
|
|
85
|
+
- pnpm run build (生产编译)
|
|
86
|
+
- pnpm start (生产运行)
|
|
9
87
|
|
|
10
88
|
### 本地访问
|
|
11
89
|
|
|
@@ -141,6 +219,80 @@ make rebuild-dev # 重新构建并启动开发环境
|
|
|
141
219
|
|
|
142
220
|
- `NODE_ENV`: 运行环境 (production/development)
|
|
143
221
|
- `PORT`: 服务端口 (默认 3001)
|
|
222
|
+
- `ENABLE_RATE_LIMIT`: 启用速率限制 (1=启用,0=禁用,默认禁用)
|
|
223
|
+
|
|
224
|
+
## 安全特性
|
|
225
|
+
|
|
226
|
+
NSBP 内置了多层安全防护,默认启用生产级安全配置:
|
|
227
|
+
|
|
228
|
+
### 已启用的安全措施
|
|
229
|
+
|
|
230
|
+
#### 1. **HTTP 头部安全 (Helmet)**
|
|
231
|
+
- Content Security Policy (CSP): 防止 XSS 攻击
|
|
232
|
+
- X-Frame-Options: 防止点击劫持
|
|
233
|
+
- X-Content-Type-Options: 防止 MIME 类型嗅探
|
|
234
|
+
- Strict-Transport-Security: 强制 HTTPS
|
|
235
|
+
- X-XSS-Protection: XSS 保护
|
|
236
|
+
- Referrer-Policy: 控制引用信息
|
|
237
|
+
|
|
238
|
+
#### 2. **静态文件安全**
|
|
239
|
+
- ✅ 禁止访问 `.env`、`.git` 等敏感文件
|
|
240
|
+
- ✅ 静态资源缓存优化(1 年缓存)
|
|
241
|
+
- ✅ 请求体大小限制(10MB)
|
|
242
|
+
|
|
243
|
+
#### 3. **技术栈隐藏**
|
|
244
|
+
- ✅ 移除 `X-Powered-By` 头部
|
|
245
|
+
- ✅ 不暴露 Express 版本信息
|
|
246
|
+
|
|
247
|
+
#### 4. **速率限制 (可选)**
|
|
248
|
+
- ✅ 15 分钟内最多 100 次请求
|
|
249
|
+
- ✅ 自动限流恶意 IP
|
|
250
|
+
- ✅ 可通过环境变量启用/禁用
|
|
251
|
+
|
|
252
|
+
### 启用速率限制
|
|
253
|
+
|
|
254
|
+
在生产环境中,建议启用速率限制以防止 DDoS 攻击:
|
|
255
|
+
|
|
256
|
+
**Docker 方式:**
|
|
257
|
+
```bash
|
|
258
|
+
# docker-compose.yml 中添加
|
|
259
|
+
environment:
|
|
260
|
+
- ENABLE_RATE_LIMIT=1
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**本地开发方式:**
|
|
264
|
+
```bash
|
|
265
|
+
# .env 文件
|
|
266
|
+
ENABLE_RATE_LIMIT=1
|
|
267
|
+
|
|
268
|
+
# 或命令行
|
|
269
|
+
ENABLE_RATE_LIMIT=1 pnpm start
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 安全最佳实践
|
|
273
|
+
|
|
274
|
+
#### 生产环境建议
|
|
275
|
+
1. ✅ **启用 HTTPS**: 使用反向代理(Nginx/Apache)配置 SSL
|
|
276
|
+
2. ✅ **启用速率限制**: 防止暴力攻击和 DDoS
|
|
277
|
+
3. ✅ **设置强密码**: 数据库、API 密钥等
|
|
278
|
+
4. ✅ **定期更新依赖**: `pnpm update`
|
|
279
|
+
5. ✅ **配置防火墙**: 限制入站流量
|
|
280
|
+
|
|
281
|
+
#### 开发环境
|
|
282
|
+
- ✅ 默认配置已足够
|
|
283
|
+
- ❌ 不建议启用速率限制(影响开发效率)
|
|
284
|
+
- ✅ 保留详细错误日志便于调试
|
|
285
|
+
|
|
286
|
+
### 安全检查清单
|
|
287
|
+
|
|
288
|
+
部署前请确认:
|
|
289
|
+
- [ ] 已安装最新依赖 (`pnpm install`)
|
|
290
|
+
- [ ] 环境变量已配置(NODE_ENV=production)
|
|
291
|
+
- [ ] HTTPS 已配置
|
|
292
|
+
- [ ] 敏感信息(密钥、数据库密码)已移出代码库
|
|
293
|
+
- [ ] 速率限制已启用(生产环境)
|
|
294
|
+
- [ ] 静态文件访问已测试
|
|
295
|
+
- [ ] CSP 策略已测试(检查控制台错误)
|
|
144
296
|
|
|
145
297
|
### CLI 发布
|
|
146
298
|
|
|
@@ -2,7 +2,7 @@ const path = require('path')
|
|
|
2
2
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
3
3
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
|
4
4
|
const TerserPlugin = require('terser-webpack-plugin')
|
|
5
|
-
const { version } = require('
|
|
5
|
+
const { version } = require('../package.json')
|
|
6
6
|
const LoadablePlugin = require('@loadable/webpack-plugin')
|
|
7
7
|
const { createLoadableComponentsTransformer } = require('typescript-loadable-components-plugin')
|
|
8
8
|
const BrowserSyncPlugin = require('browser-sync-webpack-plugin')
|
|
@@ -54,7 +54,7 @@ module.exports = ({ mode, entry, server, init }) => {
|
|
|
54
54
|
logInfoToStdOut: true,
|
|
55
55
|
logLevel: 'info',
|
|
56
56
|
transpileOnly: true,
|
|
57
|
-
configFile: path.resolve(__dirname, '
|
|
57
|
+
configFile: path.resolve(__dirname, '../tsconfig.json'),
|
|
58
58
|
getCustomTransformers: (program) => {
|
|
59
59
|
// console.log('getCustomTransformers', program)
|
|
60
60
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path') //node的path模块
|
|
2
2
|
const { merge } = require('webpack-merge')
|
|
3
3
|
const config = require('./webpack.base.js')
|
|
4
|
-
const { version } = require('
|
|
4
|
+
const { version } = require('../package.json')
|
|
5
5
|
|
|
6
6
|
const server = false
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ const entry = {
|
|
|
12
12
|
const clientConfig = {
|
|
13
13
|
output: {
|
|
14
14
|
//打包出口
|
|
15
|
-
path: path.resolve(__dirname, 'public'),
|
|
15
|
+
path: path.resolve(__dirname, '../public'),
|
|
16
16
|
filename: ({ chunk }) => {
|
|
17
17
|
const { name } = chunk
|
|
18
18
|
// console.log('name', name)
|
|
@@ -2,7 +2,7 @@ const path = require('path') //node的path模块
|
|
|
2
2
|
const nodeExternals = require('webpack-node-externals')
|
|
3
3
|
const { merge } = require('webpack-merge')
|
|
4
4
|
const config = require('./webpack.base.js')
|
|
5
|
-
const { version } = require('
|
|
5
|
+
const { version } = require('../package.json')
|
|
6
6
|
|
|
7
7
|
const init = process.env.INIT || 0
|
|
8
8
|
const server = true
|
|
@@ -18,12 +18,12 @@ const serverConfig = {
|
|
|
18
18
|
output: {
|
|
19
19
|
//打包出口
|
|
20
20
|
filename: `bundle.${version}.js`, //打包后的文件名
|
|
21
|
-
path: path.resolve(__dirname, 'build'), //存放到根目录的build文件夹
|
|
21
|
+
path: path.resolve(__dirname, '../build'), //存放到根目录的build文件夹
|
|
22
22
|
clean: true,
|
|
23
23
|
libraryTarget: 'commonjs2'
|
|
24
24
|
},
|
|
25
25
|
externals: [
|
|
26
|
-
'@loadable/component',
|
|
26
|
+
'@loadable/component',
|
|
27
27
|
nodeExternals()
|
|
28
28
|
] //保持node中require的引用方式
|
|
29
29
|
}
|
|
@@ -16,7 +16,7 @@ RUN npm install -g pnpm && pnpm install
|
|
|
16
16
|
COPY . .
|
|
17
17
|
|
|
18
18
|
# Build application
|
|
19
|
-
RUN
|
|
19
|
+
RUN pnpm run build
|
|
20
20
|
|
|
21
21
|
# Stage 2: Production
|
|
22
22
|
FROM node:20-alpine AS production
|
|
@@ -43,7 +43,7 @@ COPY --from=builder /app/public ./public
|
|
|
43
43
|
|
|
44
44
|
# Copy scripts and config
|
|
45
45
|
COPY --from=builder /app/scripts ./scripts
|
|
46
|
-
COPY --from=builder /app/tsconfig.json ./
|
|
46
|
+
COPY --from=builder /app/tsconfig.json ./tsconfig.json
|
|
47
47
|
|
|
48
48
|
# Change ownership
|
|
49
49
|
RUN chown -R nodejs:nodejs /app
|
|
@@ -1,29 +1,34 @@
|
|
|
1
|
+
# 从 .env 文件读取环境变量(如果存在)
|
|
2
|
+
# 开发环境配置优先级:.env > docker-compose.dev.yml 默认值
|
|
3
|
+
|
|
1
4
|
services:
|
|
2
5
|
app:
|
|
3
6
|
build:
|
|
4
|
-
context:
|
|
5
|
-
dockerfile: Dockerfile.dev
|
|
7
|
+
context: ..
|
|
8
|
+
dockerfile: docker/Dockerfile.dev
|
|
6
9
|
container_name: nsbp-app-dev
|
|
7
10
|
ports:
|
|
8
11
|
- "3001:3001"
|
|
9
12
|
- "9229:9229" # Node.js debug port
|
|
10
13
|
environment:
|
|
11
|
-
- NODE_ENV
|
|
12
|
-
- PORT
|
|
14
|
+
- NODE_ENV=${NODE_ENV:-development}
|
|
15
|
+
- PORT=${PORT:-3001}
|
|
16
|
+
# 开发环境默认禁用速率限制,如需启用可在 .env 中设置
|
|
17
|
+
- ENABLE_RATE_LIMIT=${ENABLE_RATE_LIMIT:-0}
|
|
13
18
|
volumes:
|
|
14
19
|
# Mount source code for hot reload
|
|
15
20
|
- ./src:/app/src
|
|
16
21
|
- ./public:/app/public
|
|
17
22
|
- ./scripts:/app/scripts
|
|
18
|
-
- ./webpack.base.js:/app/webpack.base.js
|
|
19
|
-
- ./webpack.client.js:/app/webpack.client.js
|
|
20
|
-
- ./webpack.server.js:/app/webpack.server.js
|
|
23
|
+
- ./config/webpack.base.js:/app/config/webpack.base.js
|
|
24
|
+
- ./config/webpack.client.js:/app/config/webpack.client.js
|
|
25
|
+
- ./config/webpack.server.js:/app/config/webpack.server.js
|
|
21
26
|
- ./tsconfig.json:/app/tsconfig.json
|
|
22
27
|
# Persist node_modules
|
|
23
28
|
- node_modules:/app/node_modules
|
|
24
29
|
# Build output - named volume for persistence
|
|
25
30
|
- build_output:/app/build
|
|
26
|
-
command: ["dumb-init", "--", "
|
|
31
|
+
command: ["dumb-init", "--", "pnpm", "run", "dev"]
|
|
27
32
|
restart: unless-stopped
|
|
28
33
|
networks:
|
|
29
34
|
- nsbp-network
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
# 从 .env 文件读取环境变量(如果存在)
|
|
2
|
+
# 生产环境配置优先级:.env > docker-compose.yml 默认值
|
|
3
|
+
|
|
1
4
|
services:
|
|
2
5
|
app:
|
|
3
6
|
build:
|
|
4
|
-
context:
|
|
5
|
-
dockerfile: Dockerfile
|
|
7
|
+
context: ..
|
|
8
|
+
dockerfile: docker/Dockerfile
|
|
6
9
|
container_name: nsbp-app
|
|
7
10
|
ports:
|
|
8
11
|
- "8091:3001"
|
|
9
12
|
environment:
|
|
10
|
-
- NODE_ENV
|
|
11
|
-
- PORT
|
|
13
|
+
- NODE_ENV=${NODE_ENV:-production}
|
|
14
|
+
- PORT=${PORT:-3001}
|
|
15
|
+
- ENABLE_RATE_LIMIT=${ENABLE_RATE_LIMIT:-1}
|
|
12
16
|
restart: unless-stopped
|
|
13
17
|
healthcheck:
|
|
14
18
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"description": "node react ssr by webpack",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"homepage": "https://nsbp.erishen.cn
|
|
6
|
+
"homepage": "https://nsbp.erishen.cn",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "npm-run-all -l -s dev:init -p dev:build:*",
|
|
9
|
-
"dev:init": "cross-env INIT=1 webpack --config webpack.server.js --mode development",
|
|
10
|
-
"dev:build:server": "webpack --config webpack.server.js --mode development --watch",
|
|
11
|
-
"dev:build:client": "webpack --config webpack.client.js --mode development --watch",
|
|
9
|
+
"dev:init": "cross-env INIT=1 webpack --config config/webpack.server.js --mode development",
|
|
10
|
+
"dev:build:server": "webpack --config config/webpack.server.js --mode development --watch",
|
|
11
|
+
"dev:build:client": "webpack --config config/webpack.client.js --mode development --watch",
|
|
12
12
|
"dev:build:start": "node ./scripts/start.js",
|
|
13
|
-
"build": "
|
|
14
|
-
"build:server": "webpack --config webpack.server.js --mode production",
|
|
15
|
-
"build:client": "webpack --config webpack.client.js --mode production",
|
|
13
|
+
"build": "pnpm run clean && npm-run-all -l -p build:**",
|
|
14
|
+
"build:server": "webpack --config config/webpack.server.js --mode production",
|
|
15
|
+
"build:client": "webpack --config config/webpack.client.js --mode production",
|
|
16
16
|
"start": "node ./scripts/start.js",
|
|
17
17
|
"clean": "rimraf build && rimraf public/js && rimraf public/css && rimraf public/client.* && rimraf public/*.js && rimraf public/*.js.map && rimraf public/*.txt && rimraf public/*.json && rimraf .temp_cache",
|
|
18
18
|
"format": "prettier --write **/*.{js,css,less,scss,ts,tsx}",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"author": "Erishen Sun",
|
|
31
31
|
"license": "ISC",
|
|
32
|
+
"packageManager": "pnpm@10.27.0",
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@loadable/component": "^5.15.0",
|
|
34
35
|
"@loadable/server": "^5.15.0",
|
|
@@ -36,7 +37,9 @@
|
|
|
36
37
|
"@reduxjs/toolkit": "^2.11.2",
|
|
37
38
|
"axios": "^1.7.0",
|
|
38
39
|
"express": "^5.2.1",
|
|
40
|
+
"express-rate-limit": "^8.2.0",
|
|
39
41
|
"framer-motion": "^12.25.0",
|
|
42
|
+
"helmet": "^8.1.0",
|
|
40
43
|
"lodash": "^4.17.21",
|
|
41
44
|
"probe-image-size": "^7.1.0",
|
|
42
45
|
"react": "^19.2.3",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#2563eb" />
|
|
5
|
+
<stop offset="100%" stop-color="#1d4ed8" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="100" height="100" rx="20" fill="url(#gradient)" />
|
|
9
|
+
<text x="50" y="68" font-family="Arial, sans-serif" font-size="60" font-weight="bold" text-anchor="middle" fill="white">N</text>
|
|
10
|
+
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Nsbp.js - 轻量级 React SSR 框架",
|
|
3
|
+
"short_name": "Nsbp.js",
|
|
4
|
+
"description": "轻量级 React SSR 框架,专为低资源部署与高度可定制场景而生",
|
|
5
|
+
"icons": [
|
|
6
|
+
{
|
|
7
|
+
"src": "/favicon-192x192.png",
|
|
8
|
+
"sizes": "192x192",
|
|
9
|
+
"type": "image/png"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"src": "/favicon-512x512.png",
|
|
13
|
+
"sizes": "512x512",
|
|
14
|
+
"type": "image/png"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"theme_color": "#2563eb",
|
|
18
|
+
"background_color": "#ffffff",
|
|
19
|
+
"display": "standalone"
|
|
20
|
+
}
|
|
@@ -20,7 +20,7 @@ else
|
|
|
20
20
|
echo ""
|
|
21
21
|
echo "启动命令:"
|
|
22
22
|
echo " make dev"
|
|
23
|
-
echo " docker-compose -f docker-compose.dev.yml up -d --build"
|
|
23
|
+
echo " docker-compose -f docker/docker-compose.dev.yml up -d --build"
|
|
24
24
|
exit 1
|
|
25
25
|
fi
|
|
26
26
|
|
|
@@ -35,7 +35,7 @@ fi
|
|
|
35
35
|
|
|
36
36
|
echo ""
|
|
37
37
|
echo "3. 检查权限错误..."
|
|
38
|
-
if docker-compose -f docker-compose.dev.yml logs 2>&1 | grep -q "EACCES"; then
|
|
38
|
+
if docker-compose -f docker/docker-compose.dev.yml logs 2>&1 | grep -q "EACCES"; then
|
|
39
39
|
echo -e "${RED}❌ 发现权限错误${NC}"
|
|
40
40
|
echo ""
|
|
41
41
|
echo "最近的权限错误:"
|
|
@@ -46,7 +46,7 @@ fi
|
|
|
46
46
|
|
|
47
47
|
echo ""
|
|
48
48
|
echo "4. 检查构建状态..."
|
|
49
|
-
if docker-compose -f docker-compose.dev.yml logs 2>&1 | grep -q "compiled successfully"; then
|
|
49
|
+
if docker-compose -f docker/docker-compose.dev.yml logs 2>&1 | grep -q "compiled successfully"; then
|
|
50
50
|
echo -e "${GREEN}✅ 构建完成${NC}"
|
|
51
51
|
else
|
|
52
52
|
echo -e "${YELLOW}⚠️ 构建中或未开始${NC}"
|
|
@@ -54,7 +54,7 @@ fi
|
|
|
54
54
|
|
|
55
55
|
echo ""
|
|
56
56
|
echo "5. 检查服务器监听..."
|
|
57
|
-
if docker-compose -f docker-compose.dev.yml logs 2>&1 | grep -q "listening"; then
|
|
57
|
+
if docker-compose -f docker/docker-compose.dev.yml logs 2>&1 | grep -q "listening"; then
|
|
58
58
|
echo -e "${GREEN}✅ 服务器正在监听${NC}"
|
|
59
59
|
else
|
|
60
60
|
echo -e "${YELLOW}⚠️ 服务器尚未启动监听${NC}"
|
|
@@ -69,9 +69,9 @@ echo "访问应用:"
|
|
|
69
69
|
echo " http://localhost:3001"
|
|
70
70
|
echo ""
|
|
71
71
|
echo "查看日志:"
|
|
72
|
-
echo " docker-compose -f docker-compose.dev.yml logs -f"
|
|
72
|
+
echo " docker-compose -f docker/docker-compose.dev.yml logs -f"
|
|
73
73
|
echo ""
|
|
74
74
|
echo "停止服务:"
|
|
75
75
|
echo " make down"
|
|
76
|
-
echo " docker-compose -f docker-compose.dev.yml down"
|
|
76
|
+
echo " docker-compose -f docker/docker-compose.dev.yml down"
|
|
77
77
|
echo ""
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "========================================="
|
|
4
|
+
echo "NSBP 安全功能验证"
|
|
5
|
+
echo "========================================="
|
|
6
|
+
echo ""
|
|
7
|
+
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
NC='\033[0m'
|
|
12
|
+
|
|
13
|
+
# 检查依赖
|
|
14
|
+
echo "1. 检查安全依赖安装..."
|
|
15
|
+
if grep -q '"helmet"' package.json && grep -q '"express-rate-limit"' package.json; then
|
|
16
|
+
echo -e "${GREEN}✅ helmet 和 express-rate-limit 已安装${NC}"
|
|
17
|
+
else
|
|
18
|
+
echo -e "${RED}❌ 缺少安全依赖${NC}"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# 检查服务器代码
|
|
25
|
+
echo "2. 检查服务器代码安全配置..."
|
|
26
|
+
if grep -q "helmet(" src/server/index.ts; then
|
|
27
|
+
echo -e "${GREEN}✅ Helmet 已启用${NC}"
|
|
28
|
+
else
|
|
29
|
+
echo -e "${RED}❌ Helmet 未配置${NC}"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if grep -q "rateLimit" src/server/index.ts; then
|
|
33
|
+
echo -e "${GREEN}✅ 速率限制已配置(可选)${NC}"
|
|
34
|
+
else
|
|
35
|
+
echo -e "${RED}❌ 速率限制未配置${NC}"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if grep -q "dotfiles: 'ignore'" src/server/index.ts; then
|
|
39
|
+
echo -e "${GREEN}✅ dotfiles 访问已禁用${NC}"
|
|
40
|
+
else
|
|
41
|
+
echo -e "${RED}⚠️ dotfiles 配置需要检查${NC}"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if grep -q "disable('x-powered-by')" src/server/index.ts; then
|
|
45
|
+
echo -e "${GREEN}✅ X-Powered-By 已隐藏${NC}"
|
|
46
|
+
else
|
|
47
|
+
echo -e "${RED}❌ X-Powered-By 未隐藏${NC}"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if grep -q "express.json.*limit" src/server/index.ts && grep -q "express.urlencoded.*limit" src/server/index.ts; then
|
|
51
|
+
echo -e "${GREEN}✅ 请求体大小限制已配置${NC}"
|
|
52
|
+
else
|
|
53
|
+
echo -e "${RED}❌ 请求体大小限制未配置${NC}"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo ""
|
|
57
|
+
|
|
58
|
+
# 检查 CSP 配置
|
|
59
|
+
echo "3. 检查 Content Security Policy..."
|
|
60
|
+
if grep -q "contentSecurityPolicy" src/server/index.ts; then
|
|
61
|
+
echo -e "${GREEN}✅ CSP 已配置${NC}"
|
|
62
|
+
if grep -q "defaultSrc.*'self'" src/server/index.ts; then
|
|
63
|
+
echo -e "${GREEN} - default-src: 'self'${NC}"
|
|
64
|
+
fi
|
|
65
|
+
if grep -q "scriptSrc" src/server/index.ts; then
|
|
66
|
+
echo -e "${GREEN} - script-src 已配置${NC}"
|
|
67
|
+
fi
|
|
68
|
+
if grep -q "styleSrc" src/server/index.ts; then
|
|
69
|
+
echo -e "${GREEN} - style-src 已配置${NC}"
|
|
70
|
+
fi
|
|
71
|
+
if grep -q "imgSrc.*https:" src/server/index.ts; then
|
|
72
|
+
echo -e "${GREEN} - img-src 允许 HTTPS${NC}"
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
echo -e "${RED}❌ CSP 未配置${NC}"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo ""
|
|
79
|
+
|
|
80
|
+
# 检查文档
|
|
81
|
+
echo "4. 检查安全文档..."
|
|
82
|
+
if grep -q "## 安全特性" README.md; then
|
|
83
|
+
echo -e "${GREEN}✅ 安全章节已添加到 README${NC}"
|
|
84
|
+
else
|
|
85
|
+
echo -e "${YELLOW}⚠️ 建议添加安全章节到 README${NC}"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
# 统计
|
|
91
|
+
echo "========================================="
|
|
92
|
+
echo "安全配置统计"
|
|
93
|
+
echo "========================================="
|
|
94
|
+
|
|
95
|
+
TOTAL_CHECKS=7
|
|
96
|
+
PASSED_CHECKS=0
|
|
97
|
+
|
|
98
|
+
# 统计通过项
|
|
99
|
+
grep -q '"helmet"' package.json && grep -q '"express-rate-limit"' package.json && ((PASSED_CHECKS++))
|
|
100
|
+
grep -q "helmet(" src/server/index.ts && ((PASSED_CHECKS++))
|
|
101
|
+
grep -q "dotfiles: 'ignore'" src/server/index.ts && ((PASSED_CHECKS++))
|
|
102
|
+
grep -q "disable('x-powered-by')" src/server/index.ts && ((PASSED_CHECKS++))
|
|
103
|
+
grep -q "express.json.*limit" src/server/index.ts && ((PASSED_CHECKS++))
|
|
104
|
+
grep -q "contentSecurityPolicy" src/server/index.ts && ((PASSED_CHECKS++))
|
|
105
|
+
grep -q "## 安全特性" README.md && ((PASSED_CHECKS++))
|
|
106
|
+
|
|
107
|
+
SCORE=$(( PASSED_CHECKS * 100 / TOTAL_CHECKS ))
|
|
108
|
+
|
|
109
|
+
echo -e "通过率: ${PASSED_CHECKS}/${TOTAL_CHECKS} (${YELLOW}${SCORE}%${NC})"
|
|
110
|
+
echo ""
|
|
111
|
+
|
|
112
|
+
if [ $SCORE -eq 100 ]; then
|
|
113
|
+
echo -e "${GREEN}🎉 所有安全检查通过!${NC}"
|
|
114
|
+
echo "项目已达到生产级安全标准。"
|
|
115
|
+
elif [ $SCORE -ge 70 ]; then
|
|
116
|
+
echo -e "${YELLOW}✅ 安全配置良好${NC}"
|
|
117
|
+
echo "建议检查未通过的项目。"
|
|
118
|
+
else
|
|
119
|
+
echo -e "${RED}⚠️ 安全配置需要改进${NC}"
|
|
120
|
+
echo "请检查上述未通过的项目。"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo "========================================="
|
|
@@ -369,7 +369,7 @@ export const getPhotoMenu = (req: any, res: any) => {
|
|
|
369
369
|
|
|
370
370
|
<QuickStartCard>
|
|
371
371
|
<QuickStartTitle>2️⃣ 启动开发</QuickStartTitle>
|
|
372
|
-
<QuickStartCode>$
|
|
372
|
+
<QuickStartCode>$ pnpm run dev</QuickStartCode>
|
|
373
373
|
<QuickStartDescription>
|
|
374
374
|
启动开发服务器,默认端口 3001
|
|
375
375
|
</QuickStartDescription>
|
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
|
+
import helmet from 'helmet'
|
|
3
|
+
import rateLimit from 'express-rate-limit'
|
|
2
4
|
import { render } from './utils'
|
|
3
5
|
import { getPhotoWH, getPhotoMenu } from './photo'
|
|
4
6
|
import { useCurrentFlag, outPhotoDicPath } from '../utils/config'
|
|
5
7
|
|
|
6
8
|
const app = express()
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
// 1. Security headers (helmet)
|
|
11
|
+
app.use(helmet({
|
|
12
|
+
contentSecurityPolicy: {
|
|
13
|
+
directives: {
|
|
14
|
+
defaultSrc: ["'self'"],
|
|
15
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
16
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
17
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
18
|
+
connectSrc: ["'self'", "https:"],
|
|
19
|
+
fontSrc: ["'self'", "data:"],
|
|
20
|
+
objectSrc: ["'none'"],
|
|
21
|
+
mediaSrc: ["'self'"],
|
|
22
|
+
frameSrc: ["'none'"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
crossOriginEmbedderPolicy: false, // Allow inline scripts for SSR
|
|
26
|
+
crossOriginOpenerPolicy: false, // Allow window.open for development
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
// 2. Hide X-Powered-By header
|
|
30
|
+
app.disable('x-powered-by')
|
|
31
|
+
|
|
32
|
+
// 3. Rate limiting (optional, controlled by environment variable)
|
|
33
|
+
if (process.env.ENABLE_RATE_LIMIT === '1') {
|
|
34
|
+
const limiter = rateLimit({
|
|
35
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
36
|
+
max: 100, // Limit each IP to 100 requests per windowMs
|
|
37
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
38
|
+
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
39
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
40
|
+
})
|
|
41
|
+
app.use('/api', limiter)
|
|
42
|
+
console.log('🛡️ Rate limiting enabled for /api routes')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 4. Static file serving (disable dotfiles access)
|
|
46
|
+
app.use(express.static('public', {
|
|
47
|
+
dotfiles: 'ignore',
|
|
48
|
+
setHeaders: (res, filePath) => {
|
|
49
|
+
// Cache static assets for 1 year
|
|
50
|
+
if (filePath.match(/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$/)) {
|
|
51
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}))
|
|
55
|
+
!useCurrentFlag && app.use(express.static(outPhotoDicPath, { dotfiles: 'ignore' }))
|
|
56
|
+
|
|
57
|
+
// 5. Body parsing with size limits
|
|
58
|
+
app.use(express.json({ limit: '10mb' }))
|
|
59
|
+
app.use(express.urlencoded({ limit: '10mb', extended: true }))
|
|
10
60
|
|
|
11
61
|
//使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹
|
|
12
62
|
|
|
@@ -27,4 +77,8 @@ app.use((req, res) => {
|
|
|
27
77
|
const PORT = process.env.PORT || 3001
|
|
28
78
|
app.listen(PORT, () => {
|
|
29
79
|
console.log(`Server listening on port ${PORT}`)
|
|
80
|
+
console.log(`🔒 Security headers enabled`)
|
|
81
|
+
if (process.env.ENABLE_RATE_LIMIT === '1') {
|
|
82
|
+
console.log(`🚦 Rate limiting active`)
|
|
83
|
+
}
|
|
30
84
|
})
|
|
@@ -116,6 +116,13 @@ export const render = (req: any, res: any) => {
|
|
|
116
116
|
<html>
|
|
117
117
|
<head>
|
|
118
118
|
<meta charset="utf-8">
|
|
119
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
120
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
121
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
122
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
123
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
124
|
+
<link rel="manifest" href="/site.webmanifest">
|
|
125
|
+
<meta name="theme-color" content="#2563eb">
|
|
119
126
|
${helmet?.title?.toString()}
|
|
120
127
|
${helmet?.meta?.toString()}
|
|
121
128
|
${styleTags}
|
|
File without changes
|
|
File without changes
|