Skip to content

Commit 3e4e696

Browse files
tobenotcursoragent
andauthored
Add centralized configuration system with environment-aware URLs (#2)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 66b0f98 commit 3e4e696

File tree

6 files changed

+211
-9
lines changed

6 files changed

+211
-9
lines changed

.env.example

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# 数据库配置
2+
DATABASE_URL="postgresql://username:password@localhost:5432/database_name"
3+
4+
# JWT密钥
5+
JWT_SECRET="your-super-secret-jwt-key-here"
6+
7+
# Resend邮件服务API密钥
8+
RESEND_API_KEY="your-resend-api-key-here"
9+
10+
# 服务器配置
11+
PORT=3000
12+
HOST=localhost
13+
14+
# 前端URL配置
15+
FRONTEND_LOCAL_URL="http://localhost:5174"
16+
FRONTEND_PRODUCTION_URL="https://tobenot.top/Basic-Web-Game/#/demo-with-backend"
17+
18+
# 后端URL配置
19+
BACKEND_LOCAL_URL="http://localhost:3000"
20+
BACKEND_PRODUCTION_URL="https://api.tobenot.top"
21+
22+
# 邮件配置
23+
EMAIL_FROM="noreply@sendmail.tobenot.top"
24+
25+
# 环境配置
26+
NODE_ENV="development"
27+
28+
# 兼容旧版本的PUBLIC_URL(可选)
29+
PUBLIC_URL="http://localhost:3000"

CONFIGURATION.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# 🔧 项目配置说明
2+
3+
## 📋 环境变量配置
4+
5+
本项目使用统一的环境变量配置系统,所有URL和配置都通过环境变量管理。
6+
7+
### 🚀 快速开始
8+
9+
1. 复制环境变量示例文件:
10+
```bash
11+
cp .env.example .env
12+
```
13+
14+
2. 根据你的环境修改 `.env` 文件中的配置
15+
16+
### 📝 配置项说明
17+
18+
#### 服务器配置
19+
- `PORT`: 服务器端口(默认:3000)
20+
- `HOST`: 服务器主机(默认:localhost)
21+
22+
#### 前端URL配置
23+
- `FRONTEND_LOCAL_URL`: 本地开发前端URL(默认:http://localhost:5174)
24+
- `FRONTEND_PRODUCTION_URL`: 生产环境前端URL(默认:https://tobenot.top/Basic-Web-Game/#/demo-with-backend)
25+
26+
#### 后端URL配置
27+
- `BACKEND_LOCAL_URL`: 本地开发后端URL(默认:http://localhost:3000)
28+
- `BACKEND_PRODUCTION_URL`: 生产环境后端URL(默认:https://api.tobenot.top)
29+
30+
#### 邮件配置
31+
- `EMAIL_FROM`: 邮件发送地址(默认:noreply@sendmail.tobenot.top
32+
33+
#### 数据库配置
34+
- `DATABASE_URL`: PostgreSQL数据库连接字符串
35+
36+
#### 安全配置
37+
- `JWT_SECRET`: JWT签名密钥
38+
- `RESEND_API_KEY`: Resend邮件服务API密钥
39+
40+
#### 环境配置
41+
- `NODE_ENV`: 运行环境(development/production)
42+
43+
### 🔄 配置自动切换
44+
45+
系统会根据 `NODE_ENV` 环境变量自动切换配置:
46+
47+
- **开发环境** (`NODE_ENV=development`):使用本地URL
48+
- **生产环境** (`NODE_ENV=production`):使用生产URL
49+
50+
### 🌐 CORS配置
51+
52+
CORS允许的源会根据环境自动配置:
53+
- 开发环境:包含所有本地开发URL
54+
- 生产环境:包含生产环境URL
55+
56+
### 📧 邮件链接
57+
58+
邮件中的魔法链接会根据当前环境自动生成正确的URL。
59+
60+
### 🔍 前端API调用
61+
62+
前端会自动检测当前环境并调用正确的API地址:
63+
- 本地开发:`http://localhost:3000/api/trpc`
64+
- 生产环境:使用当前域名
65+
66+
## 🛠️ 部署配置
67+
68+
### 本地开发
69+
```bash
70+
# 使用默认配置启动
71+
npm run dev
72+
73+
# 或指定环境变量
74+
NODE_ENV=development npm run dev
75+
```
76+
77+
### 生产部署
78+
```bash
79+
# 设置生产环境
80+
NODE_ENV=production npm start
81+
```
82+
83+
确保在生产环境中设置正确的环境变量:
84+
- `NODE_ENV=production`
85+
- `FRONTEND_PRODUCTION_URL`
86+
- `BACKEND_PRODUCTION_URL`
87+
- `EMAIL_FROM`
88+
89+
## 📊 配置迁移
90+
91+
### 从旧版本迁移
92+
93+
如果你之前使用的是硬编码的URL,现在可以通过环境变量来配置:
94+
95+
1. 创建 `.env` 文件
96+
2. 复制 `.env.example` 中的配置
97+
3. 根据你的环境修改相应的URL
98+
99+
### 兼容性
100+
101+
系统保持向后兼容,旧的 `PUBLIC_URL` 环境变量仍然可以使用。

src/config.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// 环境变量配置
2+
export const config = {
3+
// 服务器配置
4+
server: {
5+
port: process.env.PORT || 3000,
6+
host: process.env.HOST || 'localhost',
7+
},
8+
9+
// 前端配置
10+
frontend: {
11+
// 本地开发前端URL
12+
localDev: process.env.FRONTEND_LOCAL_URL || 'http://localhost:5174',
13+
// 生产环境前端URL
14+
production: process.env.FRONTEND_PRODUCTION_URL || 'https://tobenot.top/Basic-Web-Game/#/demo-with-backend',
15+
},
16+
17+
// 服务器URL配置
18+
backend: {
19+
// 本地开发服务器URL
20+
localDev: process.env.BACKEND_LOCAL_URL || 'http://localhost:3000',
21+
// 生产环境服务器URL
22+
production: process.env.BACKEND_PRODUCTION_URL || 'https://api.tobenot.top',
23+
},
24+
25+
// 邮件配置
26+
email: {
27+
from: process.env.EMAIL_FROM || 'noreply@sendmail.tobenot.top',
28+
},
29+
30+
// 环境判断
31+
isProduction: process.env.NODE_ENV === 'production',
32+
33+
// 获取当前环境的前端URL
34+
getFrontendUrl: () => {
35+
return config.isProduction ? config.frontend.production : config.frontend.localDev;
36+
},
37+
38+
// 获取当前环境的服务器URL
39+
getBackendUrl: () => {
40+
return config.isProduction ? config.backend.production : config.backend.localDev;
41+
},
42+
43+
// 获取CORS允许的源
44+
getCorsOrigins: () => {
45+
const origins = [
46+
config.frontend.localDev,
47+
config.backend.localDev,
48+
'http://localhost:5173', // 兼容其他可能的本地端口
49+
'http://127.0.0.1:5173',
50+
'http://127.0.0.1:5174',
51+
];
52+
53+
if (config.isProduction) {
54+
origins.push(config.frontend.production);
55+
origins.push(config.backend.production);
56+
}
57+
58+
return origins;
59+
}
60+
};

src/routers/auth.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { randomBytes, createHash } from 'crypto';
66
import * as jwt from 'jsonwebtoken';
77
import * as fs from 'fs';
88
import * as path from 'path';
9+
import { config } from '../config';
910

1011
const resend = new Resend(process.env.RESEND_API_KEY);
1112

@@ -40,19 +41,19 @@ export const authRouter = router({
4041
});
4142

4243
const inputForLink = { token: rawToken };
43-
const magicLink = `${process.env.PUBLIC_URL}/test.html?token=${rawToken}`;
44+
const magicLink = `${config.getBackendUrl()}/test.html?token=${rawToken}`;
4445

4546
const emailHtml = emailTemplate.replace('{{magicLink}}', magicLink);
4647

4748
// 在开发环境下,为了方便调试,同时在控制台打印出魔法链接
48-
if (process.env.NODE_ENV !== 'production') {
49+
if (!config.isProduction) {
4950
console.log(`✨ Magic Link for ${email}: ${magicLink}`);
5051
}
5152

5253
// 真正发送邮件
5354
try {
5455
await resend.emails.send({
55-
from: 'noreply@sendmail.tobenot.top',
56+
from: config.email.from,
5657
to: email,
5758
subject: '登录到 YourApp',
5859
html: emailHtml,

src/server.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { userRouter } from './routers/user';
77
import { announcementRouter } from './routers/announcement';
88
import { router } from './trpc';
99
import { join } from 'path';
10+
import { config } from './config';
1011

1112
// 1. 定义应用的主路由
1213
const appRouter = router({
@@ -34,7 +35,7 @@ export async function createContext({ req }: { req: any }) {
3435
// 3. 创建并配置 Fastify 服务器
3536
const server = fastify({ maxParamLength: 5000 });
3637
server.register(cors, {
37-
origin: ['http://localhost:5173', 'http://localhost:5174', 'http://localhost:3000', 'http://127.0.0.1:5173', 'http://127.0.0.1:5174', 'http://tobenot.top'],
38+
origin: config.getCorsOrigins(),
3839
credentials: true
3940
});
4041
server.register(fastifyTRPCPlugin, {
@@ -58,10 +59,10 @@ server.register(require('@fastify/static'), {
5859
const start = async () => {
5960
console.log('🔍 Starting server...');
6061
try {
61-
console.log('🔍 About to start listening on port 3000...');
62-
await server.listen({ port: 3000 });
63-
console.log('🚀 Server listening on http://localhost:3000');
64-
console.log('📱 Test page available at http://localhost:3000/test.html');
62+
console.log(`🔍 About to start listening on port ${config.server.port}...`);
63+
await server.listen({ port: config.server.port, host: config.server.host });
64+
console.log(`🚀 Server listening on ${config.getBackendUrl()}`);
65+
console.log(`📱 Test page available at ${config.getBackendUrl()}/test.html`);
6566
} catch (err) {
6667
console.error('❌ Error starting server:', err);
6768
server.log.error(err);

test.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,17 @@ <h1>登录成功!</h1>
242242
</div>
243243

244244
<script>
245-
const API_BASE = 'http://localhost:3000/api/trpc';
245+
// 动态获取API基础URL
246+
const getApiBase = () => {
247+
// 如果是本地开发环境,使用localhost:3000
248+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
249+
return 'http://localhost:3000/api/trpc';
250+
}
251+
// 生产环境,使用当前域名
252+
return `${window.location.protocol}//${window.location.host}/api/trpc`;
253+
};
254+
255+
const API_BASE = getApiBase();
246256
let sessionToken = null;
247257

248258
// 页面加载时检查 URL 参数,看是否是从邮件链接过来的

0 commit comments

Comments
 (0)