19 KiB
后端模块重构实施指南
概述
本指南提供了完整的后端模块重构步骤和代码示例。重构涉及:
- 创建 50+ 个新文件
- 修改所有 Controller 的路由前缀
- 更新前端 API 调用路径
- 创建新的模块组织结构
一、快速开始
方案选择
方案A:手动逐步执行(推荐,风险低)
- 按照本文档逐个模块迁移
- 每完成一个端就测试验证
- 适合希望完全掌控过程的团队
方案B:使用自动化脚本(快速,需要验证)
- 使用提供的 Node.js 脚本批量迁移
- 完成后统一测试和修复
- 适合有经验的团队
方案C:AI辅助完成(平衡)
- 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
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
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
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
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
// 旧代码
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
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
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
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
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
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:创建目录结构
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:复制并修改文件
对于每个模块:
- 复制原文件到新位置
- 修改 Controller 的
@Controller()装饰器路由前缀 - 如果是 Module 文件,重命名类名避免冲突(如
AuthModule→UserAuthModule) - 复制 Service 和 DTO 文件(通常不需要修改)
步骤3:创建端总模块
为每个端创建总模块文件(user.module.ts, merchant.module.ts, admin.module.ts等)
步骤4:更新 app.module.ts
导入新的四个端模块,删除旧模块导入
步骤5:创建官网模块
- 创建 WebsiteInfo 实体
- 运行数据库迁移
- 创建 Service、Controller
- 创建管理接口
步骤6:更新前端 API 路径
使用 VS Code 全局搜索替换,按照第五节的规则批量替换
步骤7:测试验证
- 启动后端服务
- 检查 Swagger 文档
- 测试各端的核心接口
- 启动三个前端应用测试
步骤8:清理旧模块
确认新模块工作正常后,删除旧的模块文件
八、常见问题
Q1: 模块导入路径错误
问题: Service 或 Entity 导入路径找不到
解决: 使用绝对路径 @/entities/... 或 @/modules/...
Q2: 模块名称冲突
问题: 多个模块都叫 AuthModule
解决: 重命名为 UserAuthModule, MerchantAuthModule, AdminAuthModule
Q3: 前端接口404
问题: 前端调用新接口返回404 解决: 检查是否遗漏了某些路径的替换,特别注意动态路径
Q4: Guards 不生效
问题: 认证守卫没有正确应用 解决: 确保 JwtModule 在对应的 Auth Module 中正确导出
九、验证清单
- 后端服务启动无错误
- Swagger 文档显示所有新接口
- 用户端接口测试通过(登录、下单、支付等)
- 商家端接口测试通过(登录、房源管理、订单处理等)
- 管理端接口测试通过(登录、审核、财务管理等)
- 公共接口测试通过(房源列表、商家列表等)
- 官网接口测试通过(获取网站信息)
- 小程序正常运行
- 商家后台正常运行
- 平台后台正常运行
- 旧模块已删除
- 代码已提交到版本控制
十、回滚方案
如果重构出现问题需要回滚:
- 使用 git 恢复到重构前的提交
- 或者保留旧模块,在 app.module.ts 中切换回旧模块
- 前端恢复旧的 API 路径
建议:在开始重构前创建一个 git 分支,确保可以随时回滚。