fix: 修复/api路由重复问题,完善路由转换逻辑
问题:
- Java的@RequestMapping("/api")被转换为@Controller('api')
- 导致最终路径变成/api/api/*而不是/api/*
- 影响8个controller,30条路由
解决方案:
1. 如果cleanPath === 'api',转换为空字符串
2. 如果cleanPath.startsWith('api/'),去掉'api/'前缀
3. 空字符串使用@Controller()形式
修复结果:
✅ /api/api/*路由数量: 30 → 0
✅ 总路由数量: 678 (不变)
✅ 路由分布正确:
- /api/adminapi/*: 534 (管理后台)
- /api/*: 144 (用户端)
测试:
✅ 编译通过
✅ Docker启动成功
✅ 路由验证通过
✅ 认证守卫正确
文档: docs/ROUTE_STRUCTURE.md
This commit is contained in:
248
wwjcloud-nest-v1/docs/ROUTE_STRUCTURE.md
Normal file
248
wwjcloud-nest-v1/docs/ROUTE_STRUCTURE.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# 📋 路由结构说明
|
||||||
|
|
||||||
|
生成时间: 2025-10-26
|
||||||
|
|
||||||
|
## ✅ 路由架构
|
||||||
|
|
||||||
|
是的,你的理解完全正确!
|
||||||
|
|
||||||
|
### 🔐 管理后台 API
|
||||||
|
|
||||||
|
**路径**: `/api/adminapi/*`
|
||||||
|
**数量**: 534个路由
|
||||||
|
**用途**: 管理后台的所有功能(用户管理、系统配置、商品管理等)
|
||||||
|
**认证**: 默认需要认证 (`@UseGuards(AuthGuard)`)
|
||||||
|
|
||||||
|
**Java定义**:
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/adminapi")
|
||||||
|
@SaCheckLogin // 默认需要认证
|
||||||
|
public class SysUserController { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**NestJS转换**:
|
||||||
|
```typescript
|
||||||
|
@Controller('adminapi')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
export class SysUserController { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**最终路径**: `/api/adminapi/*`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 👤 用户端 API
|
||||||
|
|
||||||
|
**路径**: `/api/*` (除了 `/api/adminapi/*`)
|
||||||
|
**数量**: 144个路由
|
||||||
|
**用途**: 用户端/前台的所有功能(会员、支付、微信、文件等)
|
||||||
|
**认证**: 默认不需要认证,按需添加
|
||||||
|
|
||||||
|
#### 主要子模块
|
||||||
|
|
||||||
|
| 路径 | 数量 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/api/member/*` | 41 | 会员相关 |
|
||||||
|
| `/api/diy/*` | 10 | 自定义装修 |
|
||||||
|
| `/api/wechat/*` | 9 | 微信功能 |
|
||||||
|
| `/api/core/*` | 9 | 核心功能(队列、任务) |
|
||||||
|
| `/api/weapp/*` | 6 | 微信小程序 |
|
||||||
|
| `/api/file/*` | 4 | 文件上传/下载 |
|
||||||
|
| `/api/area/*` | 4 | 地区数据 |
|
||||||
|
| `/api/ai/*` | 4 | AI功能 |
|
||||||
|
| `/api/pay/*` | 2 | 支付 |
|
||||||
|
| `/api/login` | 2 | 登录 |
|
||||||
|
| 其他 | 53 | 各种杂项功能 |
|
||||||
|
|
||||||
|
**Java定义示例**:
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api") // ← 这个 'api' 会被NestJS全局前缀处理
|
||||||
|
public class PayController {
|
||||||
|
|
||||||
|
@PostMapping("/pay")
|
||||||
|
public Result<?> pay(@RequestBody PayParam param) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**NestJS转换**:
|
||||||
|
```typescript
|
||||||
|
@Controller() // ← 空路径,因为NestJS已有全局 '/api' 前缀
|
||||||
|
export class PayController {
|
||||||
|
|
||||||
|
@Post('pay')
|
||||||
|
async pay(@Body() param: PayParam) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**最终路径**: `/api/pay`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 路径转换规则
|
||||||
|
|
||||||
|
### Java → NestJS 转换逻辑
|
||||||
|
|
||||||
|
| Java `@RequestMapping` | NestJS `@Controller` | 全局前缀 | 最终路径 |
|
||||||
|
|------------------------|---------------------|---------|---------|
|
||||||
|
| `/adminapi` | `adminapi` | `/api` | `/api/adminapi/*` ✅ |
|
||||||
|
| `adminapi` | `adminapi` | `/api` | `/api/adminapi/*` ✅ |
|
||||||
|
| `/api` | *(empty)* | `/api` | `/api/*` ✅ |
|
||||||
|
| `/api/member` | `member` | `/api` | `/api/member/*` ✅ |
|
||||||
|
| `api/member` | `member` | `/api` | `/api/member/*` ✅ |
|
||||||
|
|
||||||
|
### 转换代码逻辑
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// tools/java-to-nestjs-migration/generators/controller-generator.js
|
||||||
|
|
||||||
|
generateDecorators(routeInfo) {
|
||||||
|
let cleanPath = routeInfo.controllerPath.replace(/^\/+/, ''); // 去掉前导斜杠
|
||||||
|
|
||||||
|
// 如果路径是 'api' 单独出现,变为空字符串
|
||||||
|
if (cleanPath === 'api') {
|
||||||
|
cleanPath = '';
|
||||||
|
}
|
||||||
|
// 如果路径以 'api/' 开头,去掉这个前缀
|
||||||
|
else if (cleanPath.startsWith('api/')) {
|
||||||
|
cleanPath = cleanPath.substring(4); // 'api/member' → 'member'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成装饰器
|
||||||
|
if (cleanPath) {
|
||||||
|
decorators.push(`@Controller('${cleanPath}')`);
|
||||||
|
} else {
|
||||||
|
decorators.push('@Controller()'); // 空路径
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 之前的问题
|
||||||
|
|
||||||
|
### 问题1: `/api/member` → `/api/api/member`
|
||||||
|
|
||||||
|
**原因**: Java的 `@RequestMapping("/api/member")` 被完整转换为 `@Controller('api/member')`,与NestJS全局前缀 `/api` 组合后变成 `/api/api/member`。
|
||||||
|
|
||||||
|
**修复**: 去掉路径中的 `api/` 前缀,只保留 `member`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题2: `/api` → `/api/api/*`
|
||||||
|
|
||||||
|
**原因**: Java的 `@RequestMapping("/api")` 被转换为 `@Controller('api')`,与NestJS全局前缀 `/api` 组合后变成 `/api/api/*`。
|
||||||
|
|
||||||
|
**修复**: 将路径 `api` 转换为空字符串 `@Controller()`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 修复前后对比
|
||||||
|
|
||||||
|
| 状态 | `/api/api/*` 路由数量 | 正确性 |
|
||||||
|
|------|---------------------|--------|
|
||||||
|
| ❌ 修复前 | 30个 | 错误 |
|
||||||
|
| ✅ 修复后 | 0个 | 正确 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最终路由统计
|
||||||
|
|
||||||
|
| 路由前缀 | 数量 | 用途 |
|
||||||
|
|----------|------|------|
|
||||||
|
| `/api/adminapi/*` | 534 | 管理后台 |
|
||||||
|
| `/api/member/*` | 41 | 会员 |
|
||||||
|
| `/api/diy/*` | 10 | 自定义装修 |
|
||||||
|
| `/api/wechat/*` | 9 | 微信 |
|
||||||
|
| `/api/core/*` | 9 | 核心功能 |
|
||||||
|
| `/api/weapp/*` | 6 | 微信小程序 |
|
||||||
|
| `/api/file/*` | 4 | 文件 |
|
||||||
|
| `/api/area/*` | 4 | 地区 |
|
||||||
|
| `/api/ai/*` | 4 | AI |
|
||||||
|
| `/api/*` (其他) | 57 | 杂项 |
|
||||||
|
| **总计** | **678** | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 验证命令
|
||||||
|
|
||||||
|
### 查看路由分布
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docker
|
||||||
|
docker compose logs api | grep "Mapped {" | \
|
||||||
|
sed 's/.*Mapped {\([^}]*\)}.*/\1/' | \
|
||||||
|
grep -oE '/api/[^/]+' | \
|
||||||
|
sort | uniq -c | sort -rn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查错误路由
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 应该返回 0
|
||||||
|
docker compose logs api | grep "Mapped {" | grep "/api/api/" | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试具体API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 健康检查
|
||||||
|
curl http://localhost:3000/api/health | jq
|
||||||
|
|
||||||
|
# 管理后台API(需认证)
|
||||||
|
curl http://localhost:3000/api/adminapi/addon/list | jq
|
||||||
|
|
||||||
|
# 用户端API(按需认证)
|
||||||
|
curl http://localhost:3000/api/pay | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [Docker测试报告](./DOCKER_TEST_REPORT.md)
|
||||||
|
- [认证修复方案](./AUTH_FIX.md)
|
||||||
|
- [认证验证报告](./AUTH_VERIFICATION_REPORT.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 设计原理
|
||||||
|
|
||||||
|
### 为什么要去掉 `api` 前缀?
|
||||||
|
|
||||||
|
NestJS应用配置了全局前缀 `/api` (在 `main.ts` 中):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// apps/api/src/main.ts
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.setGlobalPrefix('api'); // ← 全局前缀
|
||||||
|
await app.listen(3000);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
因此:
|
||||||
|
- ✅ `@Controller('adminapi')` → `/api/adminapi/*`
|
||||||
|
- ✅ `@Controller('member')` → `/api/member/*`
|
||||||
|
- ✅ `@Controller()` → `/api/*`
|
||||||
|
- ❌ `@Controller('api')` → `/api/api/*` (错误!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 总结
|
||||||
|
|
||||||
|
你的理解**完全正确**:
|
||||||
|
|
||||||
|
1. **后端管理端** = `/api/adminapi/*` (534个路由)
|
||||||
|
2. **用户端** = `/api/*` (144个路由,分布在member, pay, wechat等子路径)
|
||||||
|
|
||||||
|
这种设计:
|
||||||
|
- ✅ 清晰分离管理后台和用户端
|
||||||
|
- ✅ 统一使用 `/api` 作为API前缀
|
||||||
|
- ✅ 与前端路由规范一致
|
||||||
|
- ✅ 与Java版本完全对应
|
||||||
|
|
||||||
|
**🎉 完美对接!**
|
||||||
|
|
||||||
@@ -329,12 +329,18 @@ ${methods}
|
|||||||
if (routeInfo.controllerPath) {
|
if (routeInfo.controllerPath) {
|
||||||
let cleanPath = routeInfo.controllerPath.replace(/^\/+/, ''); // 去掉前导斜杠
|
let cleanPath = routeInfo.controllerPath.replace(/^\/+/, ''); // 去掉前导斜杠
|
||||||
|
|
||||||
// 如果路径以 'api/' 开头,去掉这个前缀(NestJS应用已有全局/api前缀)
|
// 如果路径是 'api' 单独出现,或以 'api/' 开头,需要去掉(NestJS应用已有全局/api前缀)
|
||||||
if (cleanPath.startsWith('api/')) {
|
if (cleanPath === 'api') {
|
||||||
|
cleanPath = ''; // 空字符串,表示根路径
|
||||||
|
} else if (cleanPath.startsWith('api/')) {
|
||||||
cleanPath = cleanPath.substring(4); // 去掉 'api/'
|
cleanPath = cleanPath.substring(4); // 去掉 'api/'
|
||||||
}
|
}
|
||||||
|
|
||||||
decorators.push(`@Controller('${cleanPath}')`);
|
if (cleanPath) {
|
||||||
|
decorators.push(`@Controller('${cleanPath}')`);
|
||||||
|
} else {
|
||||||
|
decorators.push('@Controller()'); // 空路径使用无参数形式
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
decorators.push('@Controller()');
|
decorators.push('@Controller()');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"timestamp": "2025-10-26T13:23:27.810Z",
|
"timestamp": "2025-10-26T13:30:05.823Z",
|
||||||
"stats": {
|
"stats": {
|
||||||
"startTime": "2025-10-26T13:23:25.676Z",
|
"startTime": "2025-10-26T13:30:03.678Z",
|
||||||
"endTime": null,
|
"endTime": null,
|
||||||
"filesProcessed": 1390,
|
"filesProcessed": 1390,
|
||||||
"modulesGenerated": 6,
|
"modulesGenerated": 6,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
||||||
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { WechatServiceImplService } from '../../../services/api/wechat/impl/wech
|
|||||||
import { WeappServiceImplService } from '../../../services/api/weapp/impl/weapp-service-impl.service';
|
import { WeappServiceImplService } from '../../../services/api/weapp/impl/weapp-service-impl.service';
|
||||||
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class LoginController {
|
export class LoginController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RegisterServiceImplService } from '../../../services/api/login/impl/reg
|
|||||||
import { WechatServiceImplService } from '../../../services/api/wechat/impl/wechat-service-impl.service';
|
import { WechatServiceImplService } from '../../../services/api/wechat/impl/wechat-service-impl.service';
|
||||||
import { WeappServiceImplService } from '../../../services/api/weapp/impl/weapp-service-impl.service';
|
import { WeappServiceImplService } from '../../../services/api/weapp/impl/weapp-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class RegisterController {
|
export class RegisterController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
||||||
import { PayServiceImplService } from '../../../services/admin/pay/impl/pay-service-impl.service';
|
import { PayServiceImplService } from '../../../services/admin/pay/impl/pay-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class PayController {
|
export class PayController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
||||||
import { TaskServiceImplService } from '../../../services/api/sys/impl/task-service-impl.service';
|
import { TaskServiceImplService } from '../../../services/api/sys/impl/task-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class TaskController {
|
export class TaskController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
||||||
import { CaptchaServiceImplService } from '../../../services/admin/captcha/impl/captcha-service-impl.service';
|
import { CaptchaServiceImplService } from '../../../services/admin/captcha/impl/captcha-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class CaptchaController {
|
export class CaptchaController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { MemberLevelServiceImplService } from '../../../services/admin/member/im
|
|||||||
import { DiyThemeServiceImplService } from '../../../services/admin/diy/impl/diy-theme-service-impl.service';
|
import { DiyThemeServiceImplService } from '../../../services/admin/diy/impl/diy-theme-service-impl.service';
|
||||||
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
import { AppServiceImplService } from '../../../services/api/channel/impl/app-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class SysConfigController {
|
export class SysConfigController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagg
|
|||||||
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';
|
||||||
import { SysVerifyServiceImplService } from '../../../services/api/sys/impl/sys-verify-service-impl.service';
|
import { SysVerifyServiceImplService } from '../../../services/api/sys/impl/sys-verify-service-impl.service';
|
||||||
|
|
||||||
@Controller('api')
|
@Controller()
|
||||||
@ApiTags('API')
|
@ApiTags('API')
|
||||||
export class SysVerifyController {
|
export class SysVerifyController {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
Reference in New Issue
Block a user