【AI 编程实战】第 3 篇:后端小白也能写 API:AI 带我 1 小时搭完 Next.js 服务

后端 API 开发是很多前端开发者的"心理阴影"------数据库设计、ORM 操作、JWT 认证、错误处理......每一步都是坑。但有了 AI,这一切都变得简单起来。这是《AI 编程实战:TRAE SOLO 全栈开发指南》专栏的第三篇文章,带你用 AI 快速搭建 Next.js 15 后端 API。

一、开篇:前端开发者的后端焦虑

1.1 后端开发的"噩梦"

还记得小何吗?完成前端架构搭建后,他面临一个更大的挑战:后端 API 开发

作为一个前端开发者,小何对后端并不陌生,但每次写后端代码时,总有一种"如履薄冰"的感觉:

txt 复制代码
数据库表怎么设计?字段类型怎么选?
ORM 怎么用?Prisma 和 TypeORM 有什么区别?
JWT 怎么生成?Token 过期怎么处理?
接口怎么设计才符合 RESTful 规范?
错误处理怎么统一?日志怎么记录?

传统方式下,小何需要:

任务 预计时间
学习 Next.js API 2 小时
数据库表设计 3 小时
Prisma 配置和学习 4 小时
JWT 认证实现 3 小时
登录注册 API 4 小时
响应格式统一 2 小时
错误处理和日志 2 小时
总计 20 小时

实际上,因为各种问题,可能需要 3-4 天

1.2 AI 辅助的后端开发

有了 TRAE SOLO,小何的体验完全不同:

txt 复制代码
小何:帮我搭建 Next.js 15 后端 API,需要:
      1. MySQL 数据库,使用 Prisma ORM
      2. JWT 认证
      3. 微信小程序登录
      4. 统一的响应格式
      5. 日志记录

AI:好的,我来帮你搭建完整的后端架构:

    ✓ 分析项目结构,确定 Pages Router 模式
    ✓ 设计数据库表结构(用户、会员、账单等)
    ✓ 配置 Prisma ORM 连接 MySQL
    ✓ 实现 JWT 生成和验证
    ✓ 封装微信登录流程
    ✓ 统一响应格式(responseHelper)
    ✓ 配置 Winston 日志系统

    后端架构已生成,请查看...

时间对比

任务 传统方式 AI 辅助 提升倍数
数据库设计 3 小时 15 分钟 12x
Prisma 配置 4 小时 10 分钟 24x
JWT 认证 3 小时 10 分钟 18x
登录 API 4 小时 20 分钟 12x
响应格式统一 2 小时 5 分钟 24x
总计 ~20 小时 ~1 小时 ~20x

二、技术选型:为什么选择 Next.js 15 + Prisma

2.1 Next.js 作为后端的优势

txt 复制代码
小何:为什么后端要用 Next.js?不是应该用 Express 或 Koa 吗?

AI:Next.js 作为后端有独特优势:

    1. 全栈一体化
       - 前后端可以共享代码和类型
       - 部署更简单,一个服务搞定

    2. API Routes 开箱即用
       - 文件即路由,无需额外配置
       - 支持中间件、动态路由

    3. TypeScript 原生支持
       - 类型安全,减少运行时错误
       - 与 Prisma 完美配合

    4. 生态完善
       - 丰富的中间件和插件
       - 社区活跃,问题容易解决

2.2 Prisma vs TypeORM

txt 复制代码
小何:Prisma 和 TypeORM 怎么选?

AI:(通过 MCP 查阅最新文档)

    ┌────────────────┬────────────────┬────────────────┐
    │     维度       │     Prisma     │    TypeORM     │
    ├────────────────┼────────────────┼────────────────┤
    │ 类型安全       │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐⭐       │
    │ 学习曲线       │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐         │
    │ 迁移管理       │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐⭐       │
    │ 查询直观性     │ ⭐⭐⭐⭐⭐     │ ⭐⭐⭐         │
    │ 性能           │ ⭐⭐⭐⭐       │ ⭐⭐⭐⭐       │
    │ 生态系统       │ ⭐⭐⭐⭐       │ ⭐⭐⭐⭐⭐     │
    └────────────────┴────────────────┴────────────────┘

    推荐:Prisma

    理由:
    1. Schema 定义直观,一目了然
    2. 自动生成类型,类型安全
    3. 迁移管理简单,prisma migrate 一键搞定
    4. 查询 API 简洁,链式调用友好

2.3 最终技术栈

txt 复制代码
后端技术栈:
├── 框架:Next.js 15 (Pages Router)
├── 数据库:MySQL
├── ORM:Prisma 4.11
├── 认证:JWT (jsonwebtoken)
├── 加密:bcryptjs
├── 日志:Winston + daily-rotate-file
├── AI 集成:OpenAI SDK
├── 支付:微信支付 + 支付宝
├── 存储:腾讯云 COS
└── 类型共享:shared-types (workspace)

三、数据库设计:Prisma Schema

3.1 用户表设计

数据库设计是后端开发的第一步,也是最重要的一步。

提示词

txt 复制代码
帮我设计用户表,需要支持:
1. 微信小程序登录(openid)
2. 手机号登录
3. 性别、年龄段等用户属性
4. 免费使用次数限制
5. 注册来源和渠道追踪

AI 生成的 Prisma Schema

prisma 复制代码
// prisma/schema.prisma

generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "darwin", "darwin-arm64", "linux-musl-openssl-3.0.x"]
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  user_id               String    @id @default(uuid())
  username              String
  email                 String?   @unique
  phone_number          String?
  openid                String?   // 微信 openid
  hashed_password       String
  email_verified        Boolean   @default(false)
  phone_verified        Boolean   @default(false)

  // 地理信息
  country               String?
  province              String?
  city                  String?
  isp                   String?

  // 用户属性
  verification_code     String?
  avatar                String?
  gender                Int?      // 1: 男, 2: 女
  age_group             Int?      // 1: 00后, 2: 05后, 3: 90后, 4: 80后, 5: 70后

  // 状态管理
  disabled_status       Int?      @default(0)
  disabled_time         DateTime?

  // 免费额度
  free_reply_total      Int       @default(3)
  free_reply_used       Int       @default(0)
  free_reply_reset_time DateTime?

  // 来源追踪
  register_source       Int       @default(1)  // 注册来源
  register_channel      Int       @default(1)  // 注册渠道
  latest_source         Int       @default(1)  // 最近来源
  latest_channel        Int       @default(1)  // 最近渠道

  // 时间戳
  code_send_time        DateTime?
  create_time           DateTime? @default(now())
  update_time           DateTime  @default(now()) @updatedAt
  operate_time          DateTime?

  // 索引
  @@unique([phone_number, deleted_status])
  @@index([register_source], name: "idx_users_register_source")
  @@index([latest_source], name: "idx_users_latest_source")
  @@map("users")
}

设计要点解析

  1. UUID 主键 :使用 @default(uuid()) 而非自增 ID,分布式友好,自己玩的小项目推荐自增 ID
  2. 软删除deleted_status + deleted_time,数据可恢复
  3. 来源追踪register_source/channellatest_source/channel,用于数据分析
  4. 免费额度free_reply_total/used/reset_time,实现每日免费次数限制

3.2 会员表设计

prisma 复制代码
// 会员类型模型
model MembershipType {
  id              Int       @id @default(autoincrement())
  name            String    // 会员名称:月度会员、季度会员、年度会员
  price           Decimal   @db.Decimal(10, 2)
  duration_days   Int       // 会员时长(天)
  description     String?
  is_active       Boolean   @default(true)
  sort_order      Int       @default(0)
  create_time     DateTime  @default(now())
  update_time     DateTime  @default(now()) @updatedAt

  @@map("membership_types")
}

// 用户会员记录
model UserMembership {
  id              Int       @id @default(autoincrement())
  user_id         String
  membership_type Int       // 关联会员类型
  start_time      DateTime
  end_time        DateTime
  is_active       Boolean   @default(true)
  source          String    @default("purchase") // purchase/gift/invite
  order_id        String?   // 关联订单
  create_time     DateTime  @default(now())
  update_time     DateTime  @default(now()) @updatedAt

  @@index([user_id], name: "idx_user_membership_user_id")
  @@index([end_time], name: "idx_user_membership_end_time")
  @@map("user_memberships")
}

3.3 Prisma 初始化和迁移

bash 复制代码
# 安装 Prisma
pnpm --filter xingdong-server add prisma @prisma/client

# 初始化 Prisma
pnpm --filter xingdong-server prisma init

# 生成 Prisma Client
pnpm --filter xingdong-server prisma generate

# 创建迁移
pnpm --filter xingdong-server prisma migrate dev --name init

# 查看数据库
pnpm --filter xingdong-server prisma studio

四、项目结构:分层架构设计

4.1 目录结构

AI 帮小何设计了清晰的分层架构:

txt 复制代码
apps/xindong-server/
├── prisma/
│   └── schema.prisma           # 数据库模型定义
├── src/
│   ├── constants/              # 常量定义
│   │   └── index.ts
│   ├── db/                     # 数据访问层 (DAL)
│   │   ├── user.ts             # 用户数据操作
│   │   ├── chat.ts             # 聊天数据操作
│   │   ├── membership.ts       # 会员数据操作
│   │   └── ...
│   ├── helper/                 # 辅助工具
│   │   ├── logger.ts           # 日志工具
│   │   └── responseHelper.ts   # 响应格式化
│   ├── pages/
│   │   └── api/                # API 路由
│   │       ├── auth/           # 认证相关
│   │       │   ├── login.ts
│   │       │   ├── wx-login.ts
│   │       │   └── register.ts
│   │       ├── chat/           # 聊天相关
│   │       ├── membership/     # 会员相关
│   │       └── upload/         # 文件上传
│   ├── service/                # 业务逻辑层
│   │   ├── auth.ts             # 认证服务
│   │   ├── chatService.ts      # 聊天服务
│   │   ├── membershipService.ts
│   │   └── ...
│   ├── type/                   # 类型定义
│   └── utils/                  # 工具函数
│       ├── auth/
│       │   └── auth.ts         # JWT 工具
│       ├── prismaProxy.ts      # Prisma 代理
│       └── logRequest.ts       # 请求日志
├── .env                        # 环境变量
└── package.json

分层职责

  1. pages/api:接收请求、参数验证、调用 Service
  2. service:业务逻辑处理、调用多个 DB 方法
  3. db:纯数据操作、Prisma 查询封装
  4. helper:通用辅助功能(日志、响应格式化)
  5. utils:工具函数(加密、JWT、请求处理)

4.2 Prisma 代理封装

AI 生成了一个 Prisma 代理,自动记录所有数据库操作日志:

typescript 复制代码
// src/utils/prismaProxy.ts
import logger from '@/helper/logger';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

function handlePrismaError(error: Error) {
  logger.error(`Prisma error: ${error.message}`, { error });
}

const handler: ProxyHandler<PrismaClient> = {
  get(target, propKey) {
    const origMethod = target[propKey as keyof PrismaClient];

    if (typeof origMethod === 'function') {
      return function (this: PrismaClient, ...args: unknown[]) {
        const boundMethod = (origMethod as Function).bind(this);
        try {
          // 记录请求日志
          logger.info(`Prisma request: ${propKey.toString()} with args: ${JSON.stringify(args)}`);

          const result = boundMethod(...args);
          if (result instanceof Promise) {
            return result
              .then((res) => {
                // 如果响应包含 count 属性,则记录所影响的行数
                if (res && typeof res.count === 'number') {
                  logger.info(`Prisma response: ${propKey.toString()} affected rows: ${res.count}`);
                }
                return res;
              })
              .catch(handlePrismaError);
          }
          return result;
        } catch (error) {
          return handlePrismaError(error as Error);
        }
      };
    }

    return target[propKey as keyof PrismaClient];
  },
};

const prismaProxy = new Proxy(prisma, handler);
export default prismaProxy;

使用方式

typescript 复制代码
// 在 db 层使用代理后的 prisma
import prisma from '@/utils/prismaProxy';

// 所有操作自动记录日志
const user = await prisma.user.findUnique({
  where: { user_id: userId },
});

五、JWT 认证:从生成到验证

5.1 JWT 工具封装

提示词

txt 复制代码
帮我封装 JWT 工具,需要:
1. 生成 Token(带过期时间)
2. 验证 Token
3. 密码哈希和验证
4. 使用 bcryptjs 加密

AI 生成的代码

typescript 复制代码
// src/utils/auth/auth.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { TOKEN_EXPIRE_IN } from '@/constants';

// 密码哈希
export const hashPassword = async (password: string): Promise<string> => {
  const salt = await bcrypt.genSalt(10);
  return bcrypt.hash(password, salt);
};

// 密码验证
export const verifyPassword = async (
  password: string,
  hashedPassword: string,
): Promise<boolean> => {
  return bcrypt.compare(password, hashedPassword);
};

// 生成 JWT
export const generateJWT = (userId: string): string => {
  return jwt.sign({ userId }, process.env.JWT_SECRET as string, {
    expiresIn: TOKEN_EXPIRE_IN, // 例如 '7d'
  });
};

// 验证 JWT
export const verifyJWT = (token: string): Promise<any> => {
  return new Promise((resolve, reject) => {
    jwt.verify(token, process.env.JWT_SECRET as string, (err, decoded) => {
      if (err) {
        reject(err);
      } else {
        resolve(decoded);
      }
    });
  });
};

5.2 认证中间件

typescript 复制代码
// src/utils/auth/authMiddleware.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { verifyJWT } from './auth';
import { sendUnauthorizedResponse } from '@/helper/responseHelper';
import { getUserById } from '@/db/user';

export interface AuthenticatedRequest extends NextApiRequest {
  userId: string;
  user: any;
}

export const withAuth = (
  handler: (req: AuthenticatedRequest, res: NextApiResponse) => Promise<void>,
) => {
  return async (req: NextApiRequest, res: NextApiResponse) => {
    try {
      // 从 Header 获取 Token
      const authHeader = req.headers.authorization;
      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return sendUnauthorizedResponse(res, '请先登录');
      }

      const token = authHeader.substring(7);

      // 验证 Token
      const decoded = await verifyJWT(token);
      if (!decoded || !decoded.userId) {
        return sendUnauthorizedResponse(res, 'Token 无效');
      }

      // 获取用户信息
      const user = await getUserById(decoded.userId);
      if (!user) {
        return sendUnauthorizedResponse(res, '用户不存在');
      }

      // 注入用户信息
      (req as AuthenticatedRequest).userId = decoded.userId;
      (req as AuthenticatedRequest).user = user;

      // 调用原始处理函数
      return handler(req as AuthenticatedRequest, res);
    } catch (error) {
      return sendUnauthorizedResponse(res, '认证失败,请重新登录');
    }
  };
};

使用方式

typescript 复制代码
// pages/api/user/profile.ts
import { withAuth, AuthenticatedRequest } from '@/utils/auth/authMiddleware';
import { sendSuccessResponse } from '@/helper/responseHelper';
import { NextApiResponse } from 'next';

const handler = async (req: AuthenticatedRequest, res: NextApiResponse) => {
  // req.userId 和 req.user 已经可用
  sendSuccessResponse(res, '获取成功', {
    user_id: req.userId,
    username: req.user.username,
  });
};

export default withAuth(handler);

六、统一响应格式

6.1 响应格式设计

统一的响应格式是 API 开发的基本要求。AI 帮小何设计了清晰的响应结构:

typescript 复制代码
// src/helper/responseHelper.ts
import { NextApiResponse } from 'next';
import logger from './logger';
import { UNAUTHORIZED_TIPS } from '@/constants';

interface JsonResponse {
  code: number;
  message: string;
  data?: any;
}

// 通用响应
export function sendResponse(res: NextApiResponse, code: number, message: string, data: any = null): void {
  const jsonResponse: JsonResponse = {
    code,
    message,
    data,
  };
  res.status(code).json(jsonResponse);
}

// 成功响应
export function sendSuccessResponse(
  res: NextApiResponse,
  message: string = '数据获取成功',
  data: any = null,
): void {
  sendResponse(res, 200, message, data);
}

// 错误响应(服务器错误)
export function sendErrorResponse(res: NextApiResponse, message: string, data: any = null): void {
  sendResponse(res, 500, message, data);
  logger.error(message);
  logger.error(data);
}

// 警告响应(业务错误)
export function sendWarnningResponse(res: NextApiResponse, message: string, data: any = null): void {
  sendResponse(res, 503, message, data);
}

// 未授权响应
export function sendUnauthorizedResponse(
  res: NextApiResponse,
  message: string = UNAUTHORIZED_TIPS,
): void {
  sendResponse(res, 401, message);
}

// 方法不允许
export function sendMethodNotAllowedResponse(
  res: NextApiResponse,
  message: string = 'Method Not Allowed',
): void {
  sendResponse(res, 405, message);
}

6.2 响应码设计

状态码 含义 使用场景
200 成功 请求成功处理
401 未授权 Token 无效或过期
405 方法不允许 GET 接口收到 POST 请求
500 服务器错误 代码异常、数据库错误
503 业务警告 参数错误、业务规则不满足

前端统一处理

typescript 复制代码
// 前端 HTTP 拦截器
const handleResponse = (response: any) => {
  const { code, message, data } = response;

  switch (code) {
    case 200:
      return data;
    case 401:
      // 跳转登录
      uni.reLaunch({ url: '/pages/login/login' });
      throw new Error(message);
    case 503:
      // 业务警告,显示 Toast
      uni.showToast({ title: message, icon: 'none' });
      throw new Error(message);
    case 500:
      // 服务器错误
      uni.showToast({ title: '服务器开小差了', icon: 'none' });
      throw new Error(message);
    default:
      throw new Error(message || '未知错误');
  }
};

七、微信小程序登录实现

7.1 登录流程设计

微信小程序登录是"心动恋聊"的核心功能。AI 帮小何设计了完整的登录流程:

sequenceDiagram participant Client as 小程序端 participant Server as 后端服务 participant WX as 微信服务器 Client->>Client: 1. wx.login() Client->>Client: 2. 返回 code Client->>Server: 3. POST /api/auth/wx-login Note right of Client: { code, gender, age_group } Server->>WX: 4. jscode2session WX-->>Server: 5. 返回 openid Server->>Server: 6. 查找/创建用户 Server->>Server: 7. 生成 JWT Server-->>Client: 8. 返回 token + userInfo

7.2 登录 API 实现

提示词

txt 复制代码
帮我实现微信小程序登录 API,需要:
1. 接收 wx.login 的 code
2. 调用微信服务器获取 openid
3. 新用户需要收集性别和年龄段
4. 老用户直接返回 token
5. 支持多来源(小程序、App)
6. 记录用户来源和渠道

AI 生成的代码

typescript 复制代码
// src/pages/api/auth/wx-login.ts
import { sendErrorResponse, sendSuccessResponse } from '@/helper/responseHelper';
import { generateJWT } from '@/utils/auth/auth';
import { getUserByOpenid, createUserByOpenid } from '@/db/user';
import { NextApiRequest, NextApiResponse } from 'next';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method !== 'POST') {
    return sendErrorResponse(res, 'Method Not Allowed');
  }

  try {
    const { code, gender, age_group } = req.body;

    // 1. 调用微信服务器获取 openid
    const wxLoginUrl = `https://api.weixin.qq.com/sns/jscode2session?appid=${process.env.WX_APPID}&secret=${process.env.WX_SECRET}&js_code=${code}&grant_type=authorization_code`;
    const wxRes = await fetch(wxLoginUrl);
    const { openid } = await wxRes.json();

    // 2. 查找或创建用户
    let user = await getUserByOpenid(openid);
    let isNewUser = false;

    if (!user) {
      // 新用户需要提供性别和年龄
      if (!gender || !age_group) {
        return sendSuccessResponse(res, '需要完善信息', { needsRegistration: true, openid });
      }
      user = await createUserByOpenid(openid, gender, age_group);
      isNewUser = true;
    }

    // 3. 生成 JWT Token
    const token = generateJWT(user.user_id);

    // 4. 返回登录结果
    sendSuccessResponse(res, '登录成功', { token, ...user, isNewUser });
  } catch (error: any) {
    sendErrorResponse(res, '登录失败', error.message);
  }
};

export default handler;

7.3 登录服务层

手机号登录的服务层封装,支持密码和验证码两种方式:

typescript 复制代码
// src/service/auth.ts
import { getUserByPhoneNumber } from '@/db/user';
import { checkCodeValid } from '@/service/checkCodeValid';
import { generateJWT, verifyPassword } from '@/utils/auth/auth';
import { LoginCredentials, LoginResult } from 'shared-types';

export class AuthServiceError extends Error {
  constructor(
    message: string,
    public code: string,
  ) {
    super(message);
    this.name = 'AuthServiceError';
  }
}

export async function loginUser(credentials: LoginCredentials): Promise<LoginResult> {
  const { phone_number, password, verification_code } = credentials;

  // 从数据库获取用户
  const user = await getUserByPhoneNumber(phone_number!);
  if (!user) {
    throw new AuthServiceError('该用户不存在,请先注册', 'USER_NOT_FOUND');
  }

  if (password) {
    // 密码登录
    const decodedPassword = Buffer.from(password, 'base64').toString();
    const isPasswordValid = await verifyPassword(decodedPassword, user.hashed_password);
    if (!isPasswordValid) {
      throw new AuthServiceError('手机号或密码输入有误', 'INVALID_CREDENTIALS');
    }

    if (!user.phone_verified) {
      throw new AuthServiceError(
        '手机号未验证通过,请先使用手机号+验证码的方式登录',
        'PHONE_UNVERIFIED',
      );
    }
  } else {
    // 验证码登录
    if (!verification_code) {
      throw new AuthServiceError('请输入验证码', 'MISSING_VERIFICATION_CODE');
    }
    const { valid, msg } = checkCodeValid({ user, code: verification_code });
    if (!valid) {
      throw new AuthServiceError(msg, 'INVALID_VERIFICATION_CODE');
    }

    if (!user.phone_verified) {
      throw new AuthServiceError('请先注册完成后再登录', 'USER_NOT_REGISTERED');
    }
  }

  // 生成 JWT
  const token = await generateJWT(user.user_id);

  return {
    user,
    token,
  };
}

八、前后端类型共享:shared-types

8.1 为什么需要类型共享

前后端分离开发时,最常见的问题是接口对不上

txt 复制代码
后端:{ user_id: "xxx" }
前端:{ userId: "xxx" }  // 拿不到数据

后端:{ gender: 1 }
前端:{ gender: "男" }  // 类型不匹配

解决方案:shared-types 包

8.2 类型定义

typescript 复制代码
// packages/shared-types/enums.ts

/**
 * 性别枚举
 */
export enum GenderEnum {
  MALE = 1,
  FEMALE = 2,
}

/**
 * 年龄段枚举
 */
export enum AgeGroupEnum {
  POST_00 = 1,  // 00后
  POST_05 = 2,  // 05后
  POST_90 = 3,  // 90后
  POST_80 = 4,  // 80后
  POST_70 = 5,  // 70后
}

/**
 * 年龄段枚举映射(用于显示)
 */
export const AgeGroupMap = {
  [AgeGroupEnum.POST_00]: '00后',
  [AgeGroupEnum.POST_05]: '05后',
  [AgeGroupEnum.POST_90]: '90后',
  [AgeGroupEnum.POST_80]: '80后',
  [AgeGroupEnum.POST_70]: '70后',
};

/**
 * 客户端来源枚举
 */
export enum ClientSourceEnum {
  MP_WEIXIN = 1,  // 微信小程序
  ANDROID = 2,    // 安卓 App
  HARMONY = 3,    // 鸿蒙
}

8.3 在项目中使用

后端使用

typescript 复制代码
// apps/xingdong-server/src/service/auth.ts
import { LoginCredentials, LoginResult } from 'shared-types';

export async function loginUser(credentials: LoginCredentials): Promise<LoginResult> {
  // 类型自动推导,IDE 智能提示
}

前端使用

typescript 复制代码
// apps/unibest-mp/src/api/auth.ts
import type { WxLoginParams, WxLoginResult } from 'shared-types';

export const apiWxLogin = (params: WxLoginParams): Promise<WxLoginResult> => {
  return http.post('/api/auth/wx-login', params);
};

package.json 配置

json 复制代码
{
  "dependencies": {
    "shared-types": "workspace:*"
  }
}

Next.js 配置(重要!):

javascript 复制代码
// next.config.js
const nextConfig = {
  reactStrictMode: true,
  transpilePackages: ['shared-types'], // 编译本地 TypeScript 包
};

九、日志系统:Winston 配置

9.1 日志配置

typescript 复制代码
// src/helper/logger.ts
import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
import path from 'path';

// 日志目录
const logDir = path.join(process.cwd(), 'logs');

// 日志格式
const logFormat = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
  winston.format.errors({ stack: true }),
  winston.format.printf(({ timestamp, level, message, ...meta }) => {
    let msg = `${timestamp} [${level.toUpperCase()}]: ${message}`;
    if (Object.keys(meta).length > 0) {
      msg += ` ${JSON.stringify(meta)}`;
    }
    return msg;
  }),
);

// 创建 logger
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: logFormat,
  transports: [
    // 控制台输出
    new winston.transports.Console({
      format: winston.format.combine(winston.format.colorize(), logFormat),
    }),
    // 按日期滚动的文件日志
    new DailyRotateFile({
      dirname: logDir,
      filename: 'app-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
    }),
    // 错误日志单独存放
    new DailyRotateFile({
      dirname: logDir,
      filename: 'error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxSize: '20m',
      maxFiles: '30d',
    }),
  ],
});

export default logger;

9.2 请求日志中间件

typescript 复制代码
// src/utils/logRequest.ts
import { NextApiRequest, NextApiResponse } from 'next';
import logger from '@/helper/logger';

const logRequest = (req: NextApiRequest, res: NextApiResponse) => {
  const { method, url, body, query } = req;

  logger.info(`API Request: ${method} ${url}`, {
    body: JSON.stringify(body),
    query: JSON.stringify(query),
    ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress,
    userAgent: req.headers['user-agent'],
  });

  // 记录响应时间
  const startTime = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    logger.info(`API Response: ${method} ${url} - ${res.statusCode} (${duration}ms)`);
  });
};

export default logRequest;

十、总结与下一步

10.1 本篇完成的工作

通过 AI 辅助,我们在 约 1 小时 内完成了:

任务 完成情况
✅ Prisma 数据库设计 用户、会员、账单等核心表
✅ 分层架构 API → Service → DB 清晰分离
✅ JWT 认证 生成、验证、中间件完整实现
✅ 微信登录 支持小程序、App 多端登录
✅ 统一响应格式 responseHelper 标准化
✅ 类型共享 shared-types 前后端类型一致
✅ 日志系统 Winston 按日期滚动、分级存储

10.2 核心提示词模板

数据库设计

txt 复制代码
帮我设计 [表名] 表,需要支持:
1. [功能点 1]
2. [功能点 2]
3. [功能点 3]
使用 Prisma Schema 语法

API 开发

txt 复制代码
帮我实现 [功能名称] API,需要:
1. [接口功能]
2. [参数要求]
3. [返回格式]
4. [错误处理]

工具封装

txt 复制代码
帮我封装 [工具名称],需要:
1. [功能点 1]
2. [功能点 2]
示例用法:[使用场景]

10.3 下一篇预告

《【AI 编程实战】第 4 篇:用 AI 打造原子化 CSS 开发体系 - UnoCSS 实战》

我们将学习:

  • UnoCSS 高级配置
  • 设计稿转代码技巧
  • 主题定制和换肤
  • 小程序端样式优化
  • 响应式布局实践

关注我,不错过每一篇实战干货!


如果这篇文章对你有帮助,请点赞、收藏、转发,让更多人了解 AI 编程的强大!

有任何问题,欢迎在评论区留言,我们一起讨论。

相关推荐
三年三月2 小时前
React 中 CSS Modules 详解
前端·css
子昕2 小时前
Claude Code更新,新增异步子代理、即时压缩、自定义会话名和使用统计四大功能
ai编程
白宇横流学长2 小时前
基于SpringBoot实现的电子发票管理系统
java·spring boot·后端
白宇横流学长2 小时前
基于SpringBoot实现的智慧就业管理系统
java·spring boot·后端
粉末的沉淀2 小时前
tauri:关闭窗口后最小化到托盘
前端·javascript·vue.js
赵庆明老师2 小时前
NET 使用SmtpClient 发送邮件
java·服务器·前端
绝世唐门三哥2 小时前
使用Intersection Observer js实现超出视口固定底部按钮
开发语言·前端·javascript
用户25542581802162 小时前
Spring AI(二):如何在使用的时候指定角色,使用模板
后端
YDS8293 小时前
SpringCloud —— 黑马商城的项目拆分和Nacos
spring boot·后端·spring cloud