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

19 KiB
Raw Blame History

后端模块重构实施指南

概述

本指南提供了完整的后端模块重构步骤和代码示例。重构涉及:

  • 创建 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

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:复制并修改文件

对于每个模块:

  1. 复制原文件到新位置
  2. 修改 Controller 的 @Controller() 装饰器路由前缀
  3. 如果是 Module 文件,重命名类名避免冲突(如 AuthModuleUserAuthModule
  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 分支,确保可以随时回滚。