Compare commits
8
Commits
3ad3e42ed6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7804075303 | ||
|
|
6e676d870b | ||
|
|
4412a416ed | ||
|
|
a836d0c1be | ||
|
|
df31337054 | ||
|
|
888f762543 | ||
|
|
5aa500e82f | ||
|
|
2e3966dd7f |
@@ -0,0 +1 @@
|
|||||||
|
wxbdf7fb77d5a6076a_verify_key_2026
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
337be6b2b4b6649ab530c2bfc058238a
|
||||||
+30
-11
@@ -1,26 +1,45 @@
|
|||||||
FROM node:24.13.0-alpine AS build
|
FROM node:24.13.0-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com && pnpm config set registry https://registry.npmmirror.com
|
||||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
|
||||||
COPY apps/api/package.json apps/api/package.json
|
COPY apps/api/package.json apps/api/package.json
|
||||||
COPY packages/contracts/package.json packages/contracts/package.json
|
COPY packages/contracts/package.json packages/contracts/package.json
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
COPY apps/api apps/api
|
COPY . .
|
||||||
COPY packages/contracts packages/contracts
|
ENV DATABASE_URL="mysql://lehuo:***@mysql:3306/lehuo"
|
||||||
RUN DATABASE_URL=mysql://build:build@127.0.0.1:3306/build pnpm --filter @lehuo/api... build
|
RUN cd apps/api && pnpm run build
|
||||||
|
|
||||||
FROM node:24.13.0-alpine AS runtime
|
FROM node:24.13.0-alpine
|
||||||
ENV NODE_ENV=production
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
|
RUN apk add --no-cache tini
|
||||||
|
# Copy all node_modules from build stage
|
||||||
COPY --from=build /app/node_modules ./node_modules
|
COPY --from=build /app/node_modules ./node_modules
|
||||||
COPY --from=build /app/apps/api/package.json ./apps/api/package.json
|
|
||||||
COPY --from=build /app/apps/api/node_modules ./apps/api/node_modules
|
COPY --from=build /app/apps/api/node_modules ./apps/api/node_modules
|
||||||
|
COPY --from=build /app/packages/contracts/node_modules ./packages/contracts/node_modules
|
||||||
|
# Copy built artifacts
|
||||||
COPY --from=build /app/apps/api/dist ./apps/api/dist
|
COPY --from=build /app/apps/api/dist ./apps/api/dist
|
||||||
COPY --from=build /app/apps/api/prisma ./apps/api/prisma
|
COPY --from=build /app/apps/api/prisma ./apps/api/prisma
|
||||||
COPY --from=build /app/apps/api/prisma.config.ts ./apps/api/prisma.config.ts
|
COPY --from=build /app/apps/api/src/generated ./apps/api/src/generated
|
||||||
COPY --from=build /app/packages/contracts/package.json ./packages/contracts/package.json
|
|
||||||
COPY --from=build /app/packages/contracts/dist ./packages/contracts/dist
|
COPY --from=build /app/packages/contracts/dist ./packages/contracts/dist
|
||||||
USER node
|
# Create @lehuo/contracts module in node_modules
|
||||||
EXPOSE 3000
|
RUN mkdir -p /app/node_modules/@lehuo/contracts && \
|
||||||
|
find /app/packages/contracts/dist -type f -exec cp {} /app/node_modules/@lehuo/contracts/ \; && \
|
||||||
|
echo '{"name":"@lehuo/contracts","version":"1.1.0","main":"./index.js"}' > /app/node_modules/@lehuo/contracts/package.json
|
||||||
|
# Generate correct default.js for Prisma client
|
||||||
|
RUN mkdir -p /app/node_modules/.prisma/client && \
|
||||||
|
echo "'use strict';" > /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'Object.defineProperty(exports, "__esModule", { value: true });' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.Prisma = void 0;' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.$Enums = void 0;' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.PrismaClient = void 0;' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'const $Class = require("/app/apps/api/dist/generated/prisma/internal/class");' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'const Prisma = require("/app/apps/api/dist/generated/prisma/internal/prismaNamespace");' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.Prisma = Prisma;' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'const enums = require("/app/apps/api/dist/generated/prisma/enums");' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.$Enums = enums.$Enums;' >> /app/node_modules/.prisma/client/default.js && \
|
||||||
|
echo 'exports.PrismaClient = $Class.getPrismaClientClass();' >> /app/node_modules/.prisma/client/default.js
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD ["node", "apps/api/dist/main.js"]
|
CMD ["node", "apps/api/dist/main.js"]
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Patch Prisma client for runtime
|
||||||
|
PRISMA_CLIENT_PATH="/app/node_modules/.pnpm/@prisma+client@7.9.1_prisma@7.9.1_@types+react@19.2.18_react-dom@19.2.8_react@19.2.8__r_28fab601f639d915a0a31cb79a401325/node_modules/@prisma/client/default.js"
|
||||||
|
TARGET_DIR="/app/node_modules/.prisma/client"
|
||||||
|
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
mkdir -p "$TARGET_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$PRISMA_CLIENT_PATH" ] && [ ! -f "$TARGET_DIR/default.js" ]; then
|
||||||
|
cp "$PRISMA_CLIENT_PATH" "$TARGET_DIR/default.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the API
|
||||||
|
exec "$@"
|
||||||
@@ -27,7 +27,13 @@ export class ApiExceptionFilter implements ExceptionFilter {
|
|||||||
const mapped = this.mapException(exception);
|
const mapped = this.mapException(exception);
|
||||||
if (mapped.status >= 500) {
|
if (mapped.status >= 500) {
|
||||||
const name = exception instanceof Error ? exception.name : 'UnknownError';
|
const name = exception instanceof Error ? exception.name : 'UnknownError';
|
||||||
this.logger.error(`Unhandled ${name}; requestId=${request.requestId}`);
|
const details = exception instanceof Error ? {
|
||||||
|
message: exception.message,
|
||||||
|
code: (exception as any).code,
|
||||||
|
meta: JSON.stringify((exception as any).meta),
|
||||||
|
stack: exception.stack?.split('\n').slice(1, 5).join(' | '),
|
||||||
|
} : null;
|
||||||
|
this.logger.error(`Unhandled ${name}; requestId=${request.requestId}; details=${JSON.stringify(details)}`);
|
||||||
}
|
}
|
||||||
response.status(mapped.status).json({
|
response.status(mapped.status).json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export interface DatabaseConnectionOptions {
|
|||||||
user: string;
|
user: string;
|
||||||
password: string;
|
password: string;
|
||||||
database: string;
|
database: string;
|
||||||
|
allowPublicKeyRetrieval?: boolean;
|
||||||
|
ssl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function requiredEnvironmentValue(name: string): string {
|
function requiredEnvironmentValue(name: string): string {
|
||||||
@@ -110,6 +112,8 @@ export class AppConfigService {
|
|||||||
user: decodeURIComponent(url.username),
|
user: decodeURIComponent(url.username),
|
||||||
password: decodeURIComponent(url.password),
|
password: decodeURIComponent(url.password),
|
||||||
database,
|
database,
|
||||||
|
allowPublicKeyRetrieval: url.searchParams.get('allowPublicKeyRetrieval') === 'true',
|
||||||
|
ssl: url.searchParams.get('useSSL') === 'true',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
// BigInt JSON serialization support for Prisma 7.x - MUST be before any Prisma imports
|
||||||
|
;(BigInt.prototype as any).toJSON = function () {
|
||||||
|
return this.toString();
|
||||||
|
};
|
||||||
|
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
App<IAppOption>({
|
App<IAppOption>({
|
||||||
globalData: {
|
globalData: {
|
||||||
// 本地开发使用回环地址;体验版/正式版会由请求层拒绝非 HTTPS 地址。
|
// 生产环境使用外网 HTTPS 地址(不含 /api 前缀,避免路径重复)
|
||||||
// 提审前必须替换为已配置微信合法域名的 HTTPS API 地址。
|
apiBaseUrl: 'https://lhgy.net',
|
||||||
apiBaseUrl: 'http://localhost:3000',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
image: minio/minio:RELEASE.2024-09-03T20-15-17Z
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server /data --console-address ':9001'
|
command: server /data --console-address ':9001'
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
const mysql = require('mysql2/promise');
|
||||||
|
|
||||||
|
async function fixEncoding() {
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
host: '172.18.0.2',
|
||||||
|
user: 'lehuo',
|
||||||
|
password: 'change-me-local-only',
|
||||||
|
database: 'lehuo',
|
||||||
|
charset: 'utf8mb4'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 查询当前乱码值
|
||||||
|
const [rows] = await connection.execute(
|
||||||
|
'SELECT user_id, display_name, HEX(display_name) FROM staff_profiles WHERE user_id = ?',
|
||||||
|
[2]
|
||||||
|
);
|
||||||
|
console.log('修复前:', rows[0]);
|
||||||
|
|
||||||
|
// 双编码修复:latin1 -> utf8
|
||||||
|
const [result] = await connection.execute(
|
||||||
|
'UPDATE staff_profiles SET display_name = CONVERT(CAST(CONVERT(? USING latin1) AS BINARY), utf8mb4) WHERE user_id = ?',
|
||||||
|
[rows[0].display_name, 2]
|
||||||
|
);
|
||||||
|
console.log('修复影响行数:', result.affectedRows);
|
||||||
|
|
||||||
|
// 复核
|
||||||
|
const [check] = await connection.execute(
|
||||||
|
'SELECT user_id, display_name, HEX(display_name) FROM staff_profiles WHERE user_id = ?',
|
||||||
|
[2]
|
||||||
|
);
|
||||||
|
console.log('修复后:', check[0]);
|
||||||
|
|
||||||
|
// 检查正确HEX
|
||||||
|
const expectedHex = Buffer.from('系统管理员').toString('hex').toUpperCase();
|
||||||
|
const actualHex = check[0].HEX(display_name);
|
||||||
|
console.log('期望HEX:', expectedHex);
|
||||||
|
console.log('实际HEX:', actualHex);
|
||||||
|
console.log('修复成功:', expectedHex === actualHex.toUpperCase());
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('错误:', error);
|
||||||
|
} finally {
|
||||||
|
await connection.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fixEncoding();
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# FRP服务器Nginx配置修复任务
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
乐活公益平台(lhgy.net)的HTTPS访问出现问题。通过域名访问API时,路径 `/api/v1/...` 被错误地修改为 `/v1/...`(缺少 `/api/` 前缀),导致所有API请求返回404错误。
|
||||||
|
|
||||||
|
**症状**:
|
||||||
|
- `https://lhgy.net/api/v1/admin/auth/login` → 返回 `Cannot POST /v1/admin/auth/login`
|
||||||
|
- 本地访问正常:`https://127.0.0.1/api/v1/test` → 成功
|
||||||
|
- 公网IP访问失败:`https://47.116.99.193/api/v1/test` → 同样失败
|
||||||
|
|
||||||
|
**根本原因**: FRP服务器上的nginx配置有问题,正在修改请求路径。
|
||||||
|
|
||||||
|
## 任务步骤
|
||||||
|
|
||||||
|
### 第一步:诊断问题
|
||||||
|
请执行以下命令检查nginx配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 查看完整的nginx配置
|
||||||
|
sudo nginx -T
|
||||||
|
|
||||||
|
# 2. 检查是否有rewrite规则修改路径
|
||||||
|
sudo nginx -T | grep -i "rewrite"
|
||||||
|
|
||||||
|
# 3. 检查所有location块配置
|
||||||
|
sudo nginx -T | grep -A 20 "location /api"
|
||||||
|
|
||||||
|
# 4. 检查proxy_pass配置
|
||||||
|
sudo nginx -T | grep -B 5 -A 10 "proxy_pass"
|
||||||
|
|
||||||
|
# 5. 查看nginx配置文件列表
|
||||||
|
sudo ls -la /etc/nginx/
|
||||||
|
sudo ls -la /etc/nginx/conf.d/
|
||||||
|
sudo ls -la /etc/nginx/sites-enabled/
|
||||||
|
sudo ls -la /etc/nginx/sites-available/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:定位问题配置
|
||||||
|
根据诊断结果,找到正在修改路径的配置。常见问题包括:
|
||||||
|
|
||||||
|
1. **有rewrite规则剥离 `/api/` 前缀**
|
||||||
|
```nginx
|
||||||
|
# 问题配置示例(需要删除或修改):
|
||||||
|
rewrite ^/api/(.*)$ /$1 break;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **proxy_pass带有尾部斜杠**
|
||||||
|
```nginx
|
||||||
|
# 问题配置示例(需要移除尾部斜杠):
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000/; # 注意尾部斜杠
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **有多个server块冲突**
|
||||||
|
- 检查是否有多个配置监听443端口
|
||||||
|
- 检查是否有default配置干扰
|
||||||
|
|
||||||
|
### 第三步:修复配置
|
||||||
|
找到问题后,编辑相应的nginx配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑主配置文件或conf.d下的配置文件
|
||||||
|
sudo nano /etc/nginx/nginx.conf
|
||||||
|
# 或
|
||||||
|
sudo nano /etc/nginx/conf.d/*.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复原则**:
|
||||||
|
- 删除或注释掉修改路径的rewrite规则
|
||||||
|
- 确保 `proxy_pass` 后面没有路径和尾部斜杠
|
||||||
|
- 确保 `location /api/` 块的配置正确
|
||||||
|
|
||||||
|
**正确的配置示例**:
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name lhgy.net www.lhgy.net;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/ssl/your-cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/your-key.pem;
|
||||||
|
|
||||||
|
# API代理 - 保留完整路径
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000; # 不要有尾部斜杠
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
}
|
||||||
|
|
||||||
|
# 管理后台
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:5173;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第四步:验证修复
|
||||||
|
修复后执行以下步骤:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 测试nginx配置语法
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# 2. 重载nginx
|
||||||
|
sudo nginx -s reload
|
||||||
|
|
||||||
|
# 3. 验证API访问
|
||||||
|
curl -sk https://lhgy.net/api/v1/admin/auth/login \
|
||||||
|
-X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Origin: https://lhgy.net" \
|
||||||
|
-d '{"username":"admin","password":"lehuo@123456"}'
|
||||||
|
|
||||||
|
# 期望返回包含session信息的JSON响应
|
||||||
|
# 如果仍然失败,检查nginx错误日志
|
||||||
|
sudo tail -20 /var/log/nginx/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第五步:记录修改
|
||||||
|
```bash
|
||||||
|
# 备份修改前的配置
|
||||||
|
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# 记录修改内容
|
||||||
|
echo "修复时间: $(date)" >> /tmp/nginx-fix-log.txt
|
||||||
|
echo "修改文件: xxx" >> /tmp/nginx-fix-log.txt
|
||||||
|
echo "修改内容: 删除了xxx规则" >> /tmp/nginx-fix-log.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
1. 修改配置前务必备份
|
||||||
|
2. 修改后先用 `nginx -t` 测试语法
|
||||||
|
3. 使用 `nginx -s reload` 而不是重启服务
|
||||||
|
4. 如果无法确定问题,请提供完整的 `nginx -T` 输出
|
||||||
|
|
||||||
|
## 验证成功的标准
|
||||||
|
修复后,以下命令应该返回包含session信息的JSON响应:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk https://lhgy.net/api/v1/admin/auth/login \
|
||||||
|
-X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Origin: https://lhgy.net" \
|
||||||
|
-d '{"username":"admin","password":"lehuo@123456"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
期望响应示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"user": {
|
||||||
|
"publicId": "admin-final-test",
|
||||||
|
"displayName": "系统管理员",
|
||||||
|
"roles": ["SUPER_ADMIN"]
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"token": "...",
|
||||||
|
"expiresAt": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
name: lehuo-local
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.4.8
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: lehuo
|
||||||
|
MYSQL_PASSWORD: change-me-local-only
|
||||||
|
MYSQL_ROOT_PASSWORD: root-lehuo-2026
|
||||||
|
MYSQL_USER: lehuo
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- '3306:3306'
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -u root -proot-lehuo-2026 --silent']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.4-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ['redis-server', '--appendonly', 'yes']
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'redis-cli', 'ping']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: apps/api/Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
APP_ENV: demo
|
||||||
|
API_PORT: '3000'
|
||||||
|
DATABASE_URL: mysql://lehuo:change-me-local-only@mysql:3306/lehuo?allowPublicKeyRetrieval=true&useSSL=false
|
||||||
|
REDIS_URL: redis://redis:6379
|
||||||
|
SESSION_SECRET: lehuo-local-session-secret-2026
|
||||||
|
SESSION_TOKEN_PEPPER: lehuo-local-token-pepper-2026
|
||||||
|
AUDIT_IP_HASH_KEY: lehuo-local-audit-key-2026
|
||||||
|
FIELD_ENCRYPTION_KEY: eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHg=
|
||||||
|
OBJECT_STORAGE_ENDPOINT: http://seaweedfs:8333/seaweedfs/s3
|
||||||
|
OBJECT_STORAGE_REGION: auto
|
||||||
|
OBJECT_STORAGE_BUCKET: lehuo
|
||||||
|
OBJECT_STORAGE_ACCESS_KEY: local-dev-key
|
||||||
|
OBJECT_STORAGE_SECRET_KEY: local-dev-secret
|
||||||
|
OBJECT_STORAGE_FORCE_PATH_STYLE: 'true'
|
||||||
|
ADMIN_WEB_ORIGIN: https://www.lhgy.net
|
||||||
|
ADMIN_COOKIE_SECURE: 'true'
|
||||||
|
AUTH_SESSION_TTL_SECONDS: '86400'
|
||||||
|
WECHAT_APP_ID: wx6cfca7c83002d678
|
||||||
|
WECHAT_APP_SECRET: 28ba8995b5329fab7e8207edf290ec74
|
||||||
|
WECHAT_PRIVATE_KEY_PATH: /app/ssl/private.key
|
||||||
|
NOTIFICATION_WORKER_ENABLED: 'false'
|
||||||
|
BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
|
BOOTSTRAP_ADMIN_PASSWORD: lehuo@123456
|
||||||
|
BOOTSTRAP_ADMIN_DISPLAY_NAME: System Administrator
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:size=64m,noexec,nosuid
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
admin-web:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: apps/admin-web/Dockerfile
|
||||||
|
args:
|
||||||
|
VITE_API_BASE_URL: https://lhgy.net/api
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '5173:80'
|
||||||
|
environment:
|
||||||
|
ADMIN_WEB_ORIGIN: https://lhgy.net
|
||||||
|
VITE_API_BASE_URL: https://lhgy.net/api
|
||||||
|
PUBLIC_HOST: lhgy.net
|
||||||
|
PUBLIC_WS: wss://lhgy.net
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
- edge
|
||||||
|
volumes:
|
||||||
|
- ./nginx.local.conf.template:/etc/nginx/templates/default.conf.template
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:size=64m,noexec,nosuid
|
||||||
|
- /var/cache/nginx:size=32m,noexec,nosuid
|
||||||
|
- /var/run:size=4m,noexec,nosuid
|
||||||
|
- /var/log/nginx:size=32m,noexec,nosuid
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql-data:
|
||||||
|
redis-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
edge:
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
client_max_body_size 12m;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_pass http://api:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /health/ {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_pass http://api:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 乐活公益平台 HTTPS 访问问题诊断报告
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
- **本地访问正常**: `http://localhost:3000/api/v1/...` ✅
|
||||||
|
- **HTTPS访问失败**: `https://lhgy.net/api/v1/...` ❌
|
||||||
|
- **错误信息**: `"Cannot POST /v1/admin/auth/login"` (缺少 `/api/` 前缀)
|
||||||
|
- **影响范围**: 所有 `/api/v1/` 开头的API请求都被修改路径
|
||||||
|
|
||||||
|
## 根本原因
|
||||||
|
FRP服务器 (47.116.99.193) 上的 nginx 配置有问题。
|
||||||
|
|
||||||
|
**请求链路**:
|
||||||
|
```
|
||||||
|
客户端 → lhgy.net (443端口) → FRP服务器nginx → TCP隧道 → 本地nginx → API容器
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题位置**: FRP服务器的nginx在转发请求时剥离了 `/api/` 前缀。
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
- 直接访问本地nginx: `curl -k https://127.0.0.1/api/v1/test` → ✅ 成功
|
||||||
|
- 通过域名访问: `curl -k https://lhgy.net/api/v1/test` → ❌ 路径变成 `/v1/test`
|
||||||
|
- 直接访问公网IP: `curl -k https://47.116.99.193/api/v1/test -H "Host: lhgy.net"` → ❌ 同样失败
|
||||||
|
|
||||||
|
## 需要检查的文件
|
||||||
|
在FRP服务器上执行以下命令检查nginx配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 查看所有nginx配置
|
||||||
|
sudo nginx -T | grep -A 20 "location /api"
|
||||||
|
|
||||||
|
# 2. 检查rewrite规则
|
||||||
|
sudo nginx -T | grep -i rewrite
|
||||||
|
|
||||||
|
# 3. 检查proxy_pass配置
|
||||||
|
sudo nginx -T | grep -B 5 -A 10 "proxy_pass"
|
||||||
|
|
||||||
|
# 4. 查看完整配置
|
||||||
|
sudo cat /etc/nginx/nginx.conf
|
||||||
|
sudo ls -la /etc/nginx/conf.d/
|
||||||
|
sudo ls -la /etc/nginx/sites-enabled/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
找到有问题的配置后,确保:
|
||||||
|
|
||||||
|
1. **`location /api/` 块的 proxy_pass 后面不要有路径**
|
||||||
|
```nginx
|
||||||
|
# 错误示例 (会修改路径):
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000/; # 注意尾部斜杠
|
||||||
|
}
|
||||||
|
|
||||||
|
# 正确示例 (保留完整路径):
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000; # 没有尾部斜杠
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **不要有修改路径的 rewrite 规则**
|
||||||
|
```nginx
|
||||||
|
# 删除或注释掉类似这样的规则:
|
||||||
|
# rewrite ^/api/(.*)$ /$1 break;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **重载nginx**
|
||||||
|
```bash
|
||||||
|
sudo nginx -t && sudo nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 临时绕过方案
|
||||||
|
如果无法立即修复FRP服务器,可以修改前端配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 修改 .env.production
|
||||||
|
ADMIN_API_URL=https://lhgy.net:3000
|
||||||
|
|
||||||
|
# 或者使用FRP映射的其他端口
|
||||||
|
```
|
||||||
|
|
||||||
|
但这是临时方案,建议尽快修复FRP服务器的nginx配置。
|
||||||
|
|
||||||
|
## 验证方法
|
||||||
|
修复后测试:
|
||||||
|
```bash
|
||||||
|
curl -sk https://lhgy.net/api/v1/admin/auth/login \
|
||||||
|
-X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Origin: https://lhgy.net" \
|
||||||
|
-d '{"username":"admin","password":"lehuo@123456"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
期望返回包含 session 信息的JSON响应。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA/UUYMAXeQfVdNEAi9gHWElVbk9u3fK5ppWLDPW2JJ3QF7n9S
|
||||||
|
FBrdeYHysA14hwRuizXL/DwIP7Koi1oFWZfXGXA/dtnUPjCgHlSWpgFT49ect+I7
|
||||||
|
Wnrr+ctnkBoqcbQHQqydS/iObeCzMS/VknbQkK5ofxAl4YHa3W/fN4a40oUI6tUB
|
||||||
|
zLh9dpbkJTDfjW6GxKNEwKI6nZ/zow/4jXzXa+LdrjFfjv4xjXIs5BRoy2B5YUIe
|
||||||
|
QcRIp99eEq2J+wgm3jgCxc4J/agk5gH4/FuUOHy4hWwkIMc37Hrk2wBcTGqbGjb9
|
||||||
|
K/15GYQLhlu2zWTSWYj84wPllh5zU5n3RBZnjwIDAQABAoIBAQCzIvXSsbPcj0GX
|
||||||
|
PW389puFaB5X3GZRp8NNmSdjAWCrKSMw55kUY1QXJ1SSNVT0mqvdhvV6uLNU6nbP
|
||||||
|
6aIzFnSrp3scA0JdomL+UPIlBHdjhhNw9HDqtFrH2ZiskfVHawBfpluzamDDUtUr
|
||||||
|
ixvSdntaeiGrDa7da810JjaJvZ5oNww8lp0Xm6L8kEQxHxwunsTjO77akSqTqNgb
|
||||||
|
Tx9ChsReZ0QqUsxTcDzWEHU4VxfHYWm8a1npOsv2w/zqBc/M0K6PwlcTdqeEzfb4
|
||||||
|
LiQaWiJRAp5AgmfKjiDj8qyxcma2M0x0uDicU2j2Tpf82UF3uuLYwWRgxW8BIWxF
|
||||||
|
otmJdZaBAoGBAP+eLltn5hk285RY8iHWJu9zZMI+L0nMy5Rt3ZNRlrYq6n6QUsAB
|
||||||
|
+LzQ2rY9yKXa9umKryWyyYxXUL7AchQvm11rlK71DzfDe5S2v9Q/gzGUQ8SGtPBk
|
||||||
|
KF/DERomdMft5NkQEjaufGt0zb322FIo+VIXGDk5Bdtx7r6aDKRhjOxhAoGBAP2m
|
||||||
|
A88VgCTeaLvNr1Hp++ZCUXxBkWihzSNwq3NDfKyYtMJiXjpAfAFPbCiFVasn2pVn
|
||||||
|
7+IfPWvctn+Fd9A4WjNtIeR02VBTLDjY+fLxMDFciyKcjR4Jy51VV+UJcgfVuYkL
|
||||||
|
iu1d/csVzwsJeymcKsKzJZJg32PIcMzMSjoyWFnvAoGBAKbJhaHqtz64llKzfroJ
|
||||||
|
S7pt5s6/WIek8yjUTwFy+Q3ADuWqMfRK1QwOksbapXNi3j0gWowUjmHeDzeHxmfZ
|
||||||
|
cFySd595EwblrFsqqxxInMfa4CT5aFJ6Z8WkOCKB+S0C0pG3ZMr9Kv3KZvyOdjOV
|
||||||
|
vKiNnSapSGMZghgn2Bvh8tvBAoGAKdx8RHEH9Q2/uz728E3thNLtH9SyatJJSNAP
|
||||||
|
e7ZnnRSWwH37jyGVVaIF96oJuXWaXY4JyQfe2y5R8DnWzEaAnHwmW83tD5Sqd7dX
|
||||||
|
iQDFM2BSliPAJnT3Zb/wAZzNdP2Q4U1GYGZivGGdCfVGnldntzpZuQ7Dz3QFKceN
|
||||||
|
eI1Wh4ECgYEAm2SeR6afWMD/KzU13epoCwFE7M6M1YZVEppLtHCHjGf5hpyKa194
|
||||||
|
uoOnmeg/VfxnOIKJg903bIEplq+2VL3Tkc0SRMVk7gtISj+uEq/TZJCAZ5Whiafa
|
||||||
|
yqxBQgphKf6RtSM9y5vUGuhCj0VCRq8ySrf+RvTcgG6s0ItXTPYowVo=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Add BigInt JSON serialization support
|
||||||
|
node -e 'BigInt.prototype.toJSON = function() { return this.toString(); };'
|
||||||
|
exec node apps/api/dist/main.js
|
||||||
Reference in New Issue
Block a user