Files
rent/REFACTOR_GUIDE.md
T
2026-05-14 19:12:26 +08:00

646 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端模块重构实施指南
## 概述
本指南提供了完整的后端模块重构步骤和代码示例。重构涉及:
- 创建 50+ 个新文件
- 修改所有 Controller 的路由前缀
- 更新前端 API 调用路径
- 创建新的模块组织结构
## 一、快速开始
### 方案选择
**方案A:手动逐步执行**(推荐,风险低)
- 按照本文档逐个模块迁移
- 每完成一个端就测试验证
- 适合希望完全掌控过程的团队
**方案B:使用自动化脚本**(快速,需要验证)
- 使用提供的 Node.js 脚本批量迁移
- 完成后统一测试和修复
- 适合有经验的团队
**方案CAI辅助完成**(平衡)
- AI 创建关键示例和模板
- 人工复制粘贴并调整其他模块
- 本文档提供所有必要的模板
## 二、目录结构
```
apps/server/src/modules/
├── user/ # 用户端(C端)
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── auth.module.ts
│ │ └── dto/
│ ├── profile/
│ ├── guest/
│ ├── order/
│ ├── review/
│ ├── coupon/
│ ├── finance/
│ ├── activity/
│ └── user.module.ts # 用户端总模块
├── merchant/ # 商家端(B端)
│ ├── auth/
│ ├── profile/
│ ├── room/
│ ├── room-calendar/
│ ├── order/
│ ├── review/
│ ├── finance/
│ ├── statistics/
│ └── merchant.module.ts # 商家端总模块
├── admin/ # 平台管理端
│ ├── auth/
│ ├── user/
│ ├── merchant/
│ ├── room/
│ ├── order/
│ ├── review/
│ ├── coupon/
│ ├── activity/
│ ├── config/
│ ├── finance/
│ ├── website/
│ └── admin.module.ts # 管理端总模块
├── website/ # 官网
│ ├── info/
│ └── website.module.ts
└── shared/ # 公共模块
├── room/
├── merchant/
├── activity/
├── upload/
└── shared.module.ts
```
## 三、路由前缀映射表
### 用户端
| 旧路由 | 新路由 | 说明 |
|--------|--------|------|
| `/auth/*` | `/api/user/auth/*` | 用户认证 |
| `/user/*` | `/api/user/profile/*` | 个人信息 |
| `/user/guests` | `/api/user/guests` | 入住人管理 |
| `/orders` | `/api/user/orders` | 用户订单 |
| `/reviews` | `/api/user/reviews` | 用户评价 |
| `/user/coupons` | `/api/user/coupons` | 用户优惠券 |
| `/user/finance` | `/api/user/finance` | 用户财务 |
| `/user/activity/invite` | `/api/user/activity/invite` | 邀请活动 |
### 商家端
| 旧路由 | 新路由 | 说明 |
|--------|--------|------|
| `/seller/auth/*` | `/api/merchant/auth/*` | 商家认证 |
| `/seller/merchant` | `/api/merchant/profile` | 商家信息 |
| `/seller/statistics` | `/api/merchant/statistics` | 数据统计 |
| `/seller/rooms` | `/api/merchant/rooms` | 房源管理 |
| `/seller/room-calendar` | `/api/merchant/room-calendar` | 房量房价 |
| `/seller/orders` | `/api/merchant/orders` | 商家订单 |
| `/seller/reviews` | `/api/merchant/reviews` | 商家评价 |
| `/merchant/finance/*` | `/api/merchant/finance/*` | 商家财务 |
### 平台管理端
| 旧路由 | 新路由 | 说明 |
|--------|--------|------|
| `/admin/auth/*` | `/api/admin/auth/*` | 管理员认证 |
| `/admin/users` | `/api/admin/users` | 用户管理 |
| `/admin/merchants` | `/api/admin/merchants` | 商家管理 |
| `/admin/rooms` | `/api/admin/rooms` | 房源审核 |
| `/admin/orders` | `/api/admin/orders` | 订单管理 |
| `/admin/reviews` | `/api/admin/reviews` | 评价审核 |
| `/admin/coupons` | `/api/admin/coupons` | 优惠券管理 |
| `/admin/activity` | `/api/admin/activity` | 活动管理 |
| `/admin/config` | `/api/admin/config` | 系统配置 |
| `/admin/finance/*` | `/api/admin/finance/*` | 财务管理 |
### 公共接口
| 旧路由 | 新路由 | 说明 |
|--------|--------|------|
| `/rooms` | `/api/public/rooms` | 房源公开查询 |
| `/merchants` | `/api/public/merchants` | 商家公开信息 |
| `/activity` | `/api/public/activity` | 活动公开信息 |
| `/upload` | `/api/user/upload` | 用户上传 |
| `/seller/upload` | `/api/merchant/upload` | 商家上传 |
| `/admin/upload` | `/api/admin/upload` | 管理员上传 |
## 四、代码模板
### 1. Controller 模板(用户端认证示例)
**文件位置**: `apps/server/src/modules/user/auth/auth.controller.ts`
```typescript
import { Controller, Post, Body, UseGuards, Get, Req } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import {
LoginByPhoneDto,
LoginByPasswordDto,
RegisterDto,
SendCodeDto,
WechatLoginDto,
} from './dto/auth.dto';
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
@ApiTags('用户认证')
@Controller('api/user/auth') // ⚠️ 关键:新的路由前缀
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('send-code')
@ApiOperation({ summary: '发送验证码' })
async sendCode(@Body() dto: SendCodeDto) {
return this.authService.sendCode(dto.phone);
}
@Post('login/phone')
@ApiOperation({ summary: '手机号验证码登录' })
async loginByPhone(@Body() dto: LoginByPhoneDto) {
return this.authService.loginByPhone(dto);
}
@Post('login/password')
@ApiOperation({ summary: '账号密码登录' })
async loginByPassword(@Body() dto: LoginByPasswordDto) {
return this.authService.loginByPassword(dto);
}
@Post('login/wechat')
@ApiOperation({ summary: '微信授权登录' })
async loginByWechat(@Body() dto: WechatLoginDto) {
return this.authService.loginByWechat(dto);
}
@Post('register')
@ApiOperation({ summary: '用户注册' })
async register(@Body() dto: RegisterDto) {
return this.authService.register(dto);
}
@Post('refresh')
@ApiOperation({ summary: '刷新令牌' })
async refresh(@Body('refreshToken') refreshToken: string) {
return this.authService.refresh(refreshToken);
}
@Get('profile')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: '获取当前用户信息' })
async getProfile(@Req() req) {
return req.user;
}
}
```
### 2. Module 模板(子模块)
**文件位置**: `apps/server/src/modules/user/auth/auth.module.ts`
```typescript
import { Module, Global } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { User } from '@/entities/user.entity';
import { UserAccount } from '@/entities/user-account.entity';
@Global()
@Module({
imports: [
TypeOrmModule.forFeature([User, UserAccount]),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('jwt.secret') || 'dev_secret_key',
signOptions: {
expiresIn: configService.get<string>('jwt.expiresIn') || '7d',
},
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService],
exports: [AuthService, JwtModule],
})
export class UserAuthModule {} // ⚠️ 重命名避免冲突
```
### 3. 端总模块模板
**文件位置**: `apps/server/src/modules/user/user.module.ts`
```typescript
import { Module } from '@nestjs/common';
import { UserAuthModule } from './auth/auth.module';
import { UserProfileModule } from './profile/profile.module';
import { UserGuestModule } from './guest/guest.module';
import { UserOrderModule } from './order/order.module';
import { UserReviewModule } from './review/review.module';
import { UserCouponModule } from './coupon/coupon.module';
import { UserFinanceModule } from './finance/finance.module';
import { UserActivityModule } from './activity/activity.module';
@Module({
imports: [
UserAuthModule,
UserProfileModule,
UserGuestModule,
UserOrderModule,
UserReviewModule,
UserCouponModule,
UserFinanceModule,
UserActivityModule,
],
})
export class UserModule {}
```
### 4. 根模块更新
**文件位置**: `apps/server/src/app.module.ts`
```typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './modules/user/user.module';
import { MerchantModule } from './modules/merchant/merchant.module';
import { AdminModule } from './modules/admin/admin.module';
import { WebsiteModule } from './modules/website/website.module';
import { SharedModule } from './modules/shared/shared.module';
// ... 其他导入
@Module({
imports: [
ConfigModule.forRoot(/* ... */),
TypeOrmModule.forRoot(/* ... */),
UserModule, // 用户端
MerchantModule, // 商家端
AdminModule, // 管理端
WebsiteModule, // 官网
SharedModule, // 公共模块
// 删除旧的模块导入
],
})
export class AppModule {}
```
## 五、前端 API 路径更新
### 小程序 (miniapp)
**文件**: `apps/miniapp/src/api/auth.ts`
```typescript
// 旧代码
export function login(data: any) {
return request.post('/auth/login/phone', data);
}
// 新代码
export function login(data: any) {
return request.post('/api/user/auth/login/phone', data);
}
```
### 批量替换规则(使用 VS Code 全局搜索替换)
**用户端接口**:
- 搜索: `'/auth/`
- 替换: `'/api/user/auth/`
- 搜索: `'/user/`
- 替换: `'/api/user/profile/`
- 搜索: `'/orders`
- 替换: `'/api/user/orders`
**商家端接口**:
- 搜索: `'/seller/auth/`
- 替换: `'/api/merchant/auth/`
- 搜索: `'/seller/`
- 替换: `'/api/merchant/`
- 搜索: `'/merchant/finance/`
- 替换: `'/api/merchant/finance/`
**管理端接口**:
- 搜索: `'/admin/`
- 替换: `'/api/admin/`
**公共接口**:
- 搜索: `'/rooms`
- 替换: `'/api/public/rooms`
- 搜索: `'/merchants`
- 替换: `'/api/public/merchants`
## 六、官网模块实现
### 1. 创建 WebsiteInfo 实体
**文件**: `apps/server/src/entities/website-info.entity.ts`
```typescript
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('website_info')
export class WebsiteInfo {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'site_name', length: 100 })
siteName: string;
@Column({ name: 'site_title', length: 200 })
siteTitle: string;
@Column({ name: 'site_description', type: 'text' })
siteDescription: string;
@Column({ name: 'site_keywords', length: 500 })
siteKeywords: string;
@Column({ length: 500 })
logo: string;
@Column({ length: 500 })
favicon: string;
@Column({ name: 'contact_phone', length: 50 })
contactPhone: string;
@Column({ name: 'contact_email', length: 100 })
contactEmail: string;
@Column({ name: 'contact_address', length: 500 })
contactAddress: string;
@Column({ length: 100 })
icp: string;
@Column({ type: 'text' })
copyright: string;
@Column({ name: 'about_us', type: 'text' })
aboutUs: string;
@Column({ name: 'service_agreement', type: 'text' })
serviceAgreement: string;
@Column({ name: 'privacy_policy', type: 'text' })
privacyPolicy: string;
@Column({ name: 'social_links', type: 'json', nullable: true })
socialLinks: Record<string, string>;
@Column({
type: 'enum',
enum: ['active', 'inactive'],
default: 'active',
})
status: 'active' | 'inactive';
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
### 2. 数据库迁移脚本
**文件**: `database/migrations/002_create_website_info.sql`
```sql
CREATE TABLE IF NOT EXISTS `website_info` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`site_name` VARCHAR(100) NOT NULL COMMENT '网站名称',
`site_title` VARCHAR(200) NOT NULL COMMENT '网站标题',
`site_description` TEXT NOT NULL COMMENT '网站描述',
`site_keywords` VARCHAR(500) NOT NULL COMMENT 'SEO关键词',
`logo` VARCHAR(500) NOT NULL COMMENT 'Logo URL',
`favicon` VARCHAR(500) NOT NULL COMMENT '网站图标',
`contact_phone` VARCHAR(50) NOT NULL COMMENT '联系电话',
`contact_email` VARCHAR(100) NOT NULL COMMENT '联系邮箱',
`contact_address` VARCHAR(500) NOT NULL COMMENT '联系地址',
`icp` VARCHAR(100) NOT NULL COMMENT 'ICP备案号',
`copyright` TEXT NOT NULL COMMENT '版权信息',
`about_us` TEXT COMMENT '关于我们',
`service_agreement` TEXT COMMENT '服务协议',
`privacy_policy` TEXT COMMENT '隐私政策',
`social_links` JSON COMMENT '社交媒体链接',
`status` ENUM('active', 'inactive') DEFAULT 'active' COMMENT '状态',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='网站信息表';
-- 插入默认数据
INSERT INTO `website_info` (
`site_name`, `site_title`, `site_description`, `site_keywords`,
`logo`, `favicon`, `contact_phone`, `contact_email`, `contact_address`,
`icp`, `copyright`, `about_us`, `service_agreement`, `privacy_policy`
) VALUES (
'租赁平台', '租赁平台 - 您的租赁服务专家', '提供优质的租赁服务', '租赁,房屋租赁,短租',
'', '', '400-000-0000', 'contact@example.com', '北京市朝阳区',
'京ICP备00000000号', '© 2024 租赁平台 版权所有', '关于我们的内容...', '服务协议内容...', '隐私政策内容...'
);
```
### 3. WebsiteInfo Service
**文件**: `apps/server/src/modules/website/info/info.service.ts`
```typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WebsiteInfo } from '@/entities/website-info.entity';
@Injectable()
export class WebsiteInfoService {
constructor(
@InjectRepository(WebsiteInfo)
private websiteInfoRepo: Repository<WebsiteInfo>,
) {}
async getInfo(): Promise<WebsiteInfo> {
// 获取第一条记录(通常只有一条)
const info = await this.websiteInfoRepo.findOne({
where: { status: 'active' },
order: { id: 'ASC' },
});
if (!info) {
throw new Error('网站信息未配置');
}
return info;
}
async updateInfo(data: Partial<WebsiteInfo>): Promise<WebsiteInfo> {
const info = await this.getInfo();
Object.assign(info, data);
return this.websiteInfoRepo.save(info);
}
}
```
### 4. WebsiteInfo Controller(公开接口)
**文件**: `apps/server/src/modules/website/info/info.controller.ts`
```typescript
import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { WebsiteInfoService } from './info.service';
@ApiTags('网站信息')
@Controller('api/website/info')
export class WebsiteInfoController {
constructor(private readonly websiteInfoService: WebsiteInfoService) {}
@Get()
@ApiOperation({ summary: '获取网站信息' })
async getInfo() {
return this.websiteInfoService.getInfo();
}
}
```
### 5. WebsiteInfo 管理接口
**文件**: `apps/server/src/modules/admin/website/website.controller.ts`
```typescript
import { Controller, Put, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
import { RolesGuard } from '@/common/guards/roles.guard';
import { Roles } from '@/common/decorators/roles.decorator';
import { WebsiteInfoService } from '@/modules/website/info/info.service';
@ApiTags('网站管理')
@Controller('api/admin/website/info')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@ApiBearerAuth()
export class AdminWebsiteController {
constructor(private readonly websiteInfoService: WebsiteInfoService) {}
@Put()
@ApiOperation({ summary: '更新网站信息' })
async updateInfo(@Body() data: any) {
return this.websiteInfoService.updateInfo(data);
}
}
```
## 七、执行步骤
### 步骤1:创建目录结构
```bash
cd d:/project/company/rent/apps/server/src/modules
mkdir -p user/{auth,profile,guest,order,review,coupon,finance,activity}
mkdir -p merchant/{auth,profile,room,room-calendar,order,review,finance,statistics}
mkdir -p admin/{auth,user,merchant,room,order,review,coupon,activity,config,finance,website}
mkdir -p website/info
mkdir -p shared/{room,merchant,activity,upload}
```
### 步骤2:复制并修改文件
对于每个模块:
1. 复制原文件到新位置
2. 修改 Controller 的 `@Controller()` 装饰器路由前缀
3. 如果是 Module 文件,重命名类名避免冲突(如 `AuthModule``UserAuthModule`
4. 复制 Service 和 DTO 文件(通常不需要修改)
### 步骤3:创建端总模块
为每个端创建总模块文件(user.module.ts, merchant.module.ts, admin.module.ts等)
### 步骤4:更新 app.module.ts
导入新的四个端模块,删除旧模块导入
### 步骤5:创建官网模块
1. 创建 WebsiteInfo 实体
2. 运行数据库迁移
3. 创建 Service、Controller
4. 创建管理接口
### 步骤6:更新前端 API 路径
使用 VS Code 全局搜索替换,按照第五节的规则批量替换
### 步骤7:测试验证
1. 启动后端服务
2. 检查 Swagger 文档
3. 测试各端的核心接口
4. 启动三个前端应用测试
### 步骤8:清理旧模块
确认新模块工作正常后,删除旧的模块文件
## 八、常见问题
### Q1: 模块导入路径错误
**问题**: Service 或 Entity 导入路径找不到
**解决**: 使用绝对路径 `@/entities/...``@/modules/...`
### Q2: 模块名称冲突
**问题**: 多个模块都叫 `AuthModule`
**解决**: 重命名为 `UserAuthModule`, `MerchantAuthModule`, `AdminAuthModule`
### Q3: 前端接口404
**问题**: 前端调用新接口返回404
**解决**: 检查是否遗漏了某些路径的替换,特别注意动态路径
### Q4: Guards 不生效
**问题**: 认证守卫没有正确应用
**解决**: 确保 JwtModule 在对应的 Auth Module 中正确导出
## 九、验证清单
- [ ] 后端服务启动无错误
- [ ] Swagger 文档显示所有新接口
- [ ] 用户端接口测试通过(登录、下单、支付等)
- [ ] 商家端接口测试通过(登录、房源管理、订单处理等)
- [ ] 管理端接口测试通过(登录、审核、财务管理等)
- [ ] 公共接口测试通过(房源列表、商家列表等)
- [ ] 官网接口测试通过(获取网站信息)
- [ ] 小程序正常运行
- [ ] 商家后台正常运行
- [ ] 平台后台正常运行
- [ ] 旧模块已删除
- [ ] 代码已提交到版本控制
## 十、回滚方案
如果重构出现问题需要回滚:
1. 使用 git 恢复到重构前的提交
2. 或者保留旧模块,在 app.module.ts 中切换回旧模块
3. 前端恢复旧的 API 路径
建议:在开始重构前创建一个 git 分支,确保可以随时回滚。