后端 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")
}
设计要点解析:
- UUID 主键 :使用
@default(uuid())而非自增 ID,分布式友好,自己玩的小项目推荐自增 ID - 软删除 :
deleted_status+deleted_time,数据可恢复 - 来源追踪 :
register_source/channel和latest_source/channel,用于数据分析 - 免费额度 :
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
分层职责:
- pages/api:接收请求、参数验证、调用 Service
- service:业务逻辑处理、调用多个 DB 方法
- db:纯数据操作、Prisma 查询封装
- helper:通用辅助功能(日志、响应格式化)
- 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 帮小何设计了完整的登录流程:
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 编程的强大!
有任何问题,欢迎在评论区留言,我们一起讨论。