前言
实战项目比写demo更有意义、更有价值,更能考虑实际情况的应用,所以选用了 buildadmin
这个后台管理系统
采用 buildadmin/web
前端项目不改,直接把 php
后端重构成 nestjs
后端
初始化 nestjs
创建服务
bash
nest new basys
接口数据格式
在开发 api 之前,我们需要确定它的 api 接口数据格式
json
{
"code": 1,
"msg": "",
"time": 1753953842,
"data": {
"captcha": true
}
}
创建 nestjs 拦截器(src/core/interceptors/response.interceptor.ts)
创建 nestjs 拦截器,格式化接口输出格式
ts
// src/core/interceptors/response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const response = context.switchToHttp().getResponse();
// buildadmin headers
response.setHeader('access-control-allow-headers', 'think-lang, server, ba_user_token, ba-user-token, ba_token, ba-token, batoken, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With');
response.setHeader('access-control-allow-methods', 'GET, POST, PUT, DELETE, OPTIONS');
return next.handle().pipe(
map(data => {
return {
code: data?.code ?? 1,
msg: data?.msg ?? '',
time: Math.floor(Date.now() / 1000),
data: data?.data ?? data ?? null
};
})
);
}
}
加载拦截器(src/main.ts)
加载拦截器,顺便设置跨域问题,本地开发会出现端口跨域问题
typescript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ResponseInterceptor } from './core/interceptors/response.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 跨域
app.enableCors({
origin: 'http://localhost:1818',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
// 拦截器
app.useGlobalInterceptors(new ResponseInterceptor());
await app.listen(process.env.PORT ?? 8000);
}
bootstrap();
创建 restful api
为了保持 buildadmin
接口格式不变,分析出从登录
到进入首页
所需接口:
[GET]
/admin/index/login
登录页信息[GET]
/api/common/clickCaptcha
点击验证码[POST]
/api/common/checkClickCaptcha
校验验证码[POST]
/admin/index/login
提交登录信息(账号密码)[GET]
/admin/index/index
首页信息[GET]
/admin/dashboard/index
控制台信息
我们先实现这 6 个接口的 mock 数据,基于 MVC
和 Nestjs
设计理念,可以这样设计目录结构: /[module]/[submodule]/[controller].[action]
md
- src
- admin
- admin.module.ts
- index
- index.module.ts
- index.controller.ts # /admin/index/index
- login.controller.ts # /admin/index/login
- dashboard
- dashboard.module.ts
- index.controller.ts # /admin/dashboard/login
- api
- api.module.ts
- common
- common.module.ts
- click-captcha.controller.ts # /api/common/clickCaptcha|checkClickCaptcha
以上先忽略了 xxx.service.ts
后面打通数据库在添加
完善 module
两个主模块导入各自的子模块
src/admin/admin.module.ts
typescript
// src/admin/admin.module.ts
import { Module } from '@nestjs/common';
import { IndexModule } from './index/index.module';
import { DashboardModule } from './dashboard/dashboard.module';
@Module({
imports: [IndexModule, DashboardModule],
})
export class AdminModule {}
src/api/api.module.ts
typescript
// src/api/api.module.ts
import { Module } from '@nestjs/common';
import { CommonModule } from './common/common.module';
@Module({
imports: [CommonModule],
})
export class ApiModule {}
- 各个子模块
src/admin/index/index.module.ts
typescript
// src/admin/index/index.module.ts
import { Module } from '@nestjs/common';
import { IndexController } from './index.controller';
import { LoginController } from './login.controller';
@Module({
controllers: [IndexController, LoginController]
})
export class IndexModule {}
src/admin/dashboard/dashboard.module.ts
typescript
// src/admin/dashboard/dashboard.module.ts
import { Module } from '@nestjs/common';
import { IndexController } from './index.controller';
@Module({
controllers: [IndexController]
})
export class DashboardModule {}
src/api/common/common.module.ts
typescript
// src/api/common/common.module.ts
import { Module } from '@nestjs/common';
import { ClickCaptchaController } from './click-captcha.controller';
@Module({
controllers: [ClickCaptchaController],
})
export class CommonModule {}
完善 controller
src/admin/index/login.controller.ts
typescript
import { Controller, Get, Post } from '@nestjs/common';
@Controller('admin/index')
export class LoginController {
/**
* [GET] /admin/index/login
*/
@Get('login')
getLogin() {
return {
captcha: true
}
}
/**
* [POST] /admin/index/login
*/
@Post('login')
postLogin() {
return {
"userInfo": {
"id": 1,
"username": "admin",
"nickname": "Admin",
"avatar": "\/static\/images\/avatar.png",
"last_login_time": "2025-07-31 16:50:23",
"token": "128adae3-665e-4424-94d8-124089ddd460",
"refresh_token": ""
}
};
}
}
src/api/common/click-captcha.controller.ts
typescript
import { Controller, Get, Post, Query } from '@nestjs/common';
@Controller('/api/common')
export class ClickCaptchaController {
/**
* [GET] /api/common/clickCaptcha
*/
@Get('clickCaptcha')
getCaptcha(@Query('id') id: string) {
return {
id,
"text": [
"检",
"步"
],
"base64": "",
"width": 350,
"height": 200
};
}
/**
* [POST] /api/common/checkClickCaptcha
*/
@Post('checkClickCaptcha')
postCheckCaptcha() {
// 假装校验通过
return null;
}
}
src/admin/dashboard/index.controller.ts
typescript
import { Controller, Get } from '@nestjs/common';
@Controller('admin/dashboard')
export class IndexController {
/**
* [GET] /admin/dashboard/index
*/
@Get('index')
index() {
return {
"remark": "开源等于互助;开源需要大家一起来支持,支持的方式有很多种,比如使用、推荐、写教程、保护生态、贡献代码、回答问题、分享经验、打赏赞助等;欢迎您加入我们!"
};
}
}
src/admin/index/index.controller.ts
typescript
import { Controller, Get } from '@nestjs/common';
@Controller('admin/index')
export class IndexController {
/**
* [GET] /admin/index/index
*/
@Get('index')
index() {
return {
"adminInfo": {
"id": 1,
"username": "admin",
"nickname": "Admin",
"avatar": "\/static\/images\/avatar.png",
"last_login_time": "2025-07-31 16:50:24",
"super": true
},
"menus": [
{
"id": 1,
"pid": 0,
"type": "menu",
"title": "控制台",
"name": "dashboard",
"path": "dashboard",
"icon": "fa fa-dashboard",
"menu_type": "tab",
"url": "",
"component": "\/src\/views\/backend\/dashboard.vue",
"keepalive": "dashboard",
"extend": "none",
"children": [
{
"id": 94,
"pid": 1,
"type": "button",
"title": "查看",
"name": "dashboard\/index",
"path": "",
"icon": "",
"menu_type": null,
"url": "",
"component": "",
"keepalive": 0,
"extend": "none"
}
]
},
],
"siteConfig": {
"siteName": "BuildAdmin",
"version": "v1.0.0",
"cdnUrl": "\/\/demo.buildadmin.com",
"apiUrl": "https:\/\/buildadmin.com",
"upload": {
"maxSize": 10485760,
"saveName": "\/storage\/{topic}\/{year}{mon}{day}\/{fileName}{fileSha1}{.suffix}",
"allowedSuffixes": "jpg,png,bmp,jpeg,gif,webp,zip,rar,wav,mp4,mp3",
"allowedMimeTypes": [],
"mode": "local"
}
},
"terminal": {
"phpDevelopmentServer": false,
"npmPackageManager": "pnpm"
}
};
}
}
到这里基本完成 nestjs 的接口设计,直接 npm run start
就能跑起来服务
之后直接运行前端项目,就能看到界面了
代码放这里了 buildadmin-nestjs