我是一个做了一年多全栈开发的人,去年一整年基本都在跟 Spring Boot + Vue 这套组合打交道。老实说,刚开始用这套技术栈时走了一堆弯路,很多问题不是功能难度大,而是前后端之间那层"隐形墙"——项目结构没理清、接口规范没统一、跨域配置来回折腾。这篇实战指南算是我把这一年多踩过的坑、验证过的方案、沉淀下来的习惯统一梳理一遍,从工程搭建到联调部署,尽量把那些网上搜不到完整答案的细节补齐。无论你是刚学完 Spring Boot/Vue 基础的学生,还是准备把个人项目从单体 JSP 升级成前后端分离的开发者,这篇文章的路线应该都能省下你不少排查时间。
1. 为什么是 Spring Boot + Vue:技术选型的现实考量
你让我推荐全栈入门技术栈,我大概率会首推 Spring Boot + Vue,不是因为它最新潮,而是因为它最"稳"。
1.1 后端生态与市场真实需求的匹配
Java 后端在国内的存量市场实在太大了,从传统企业信息系统到各类电商、管理系统,Spring Boot 几乎是无处不在。它的优势不光是生态成熟,更在于上手之后的横向扩展能力强——你今天能用 Spring Boot 写接口,明天接 Spring Cloud、Spring Security、消息队列都不需要换语言换框架。对全栈开发者来说,后端语言选 Java 的另一个好处是:遇到难以排查的问题,能搜到的资料量级是其他语言没法比的。
Spring Boot 2.3.x、2.6.x 和 3.x 这几个版本,我实际用下来的感受是:如果公司存量项目多,2.6.x 是最稳的选择,稳定、资料全、各种 starter 兼容性好;如果是 2024 年以后新起的项目,直接上 3.x,它要求 JDK 17,长期来看是趋势。我在项目里用 Spring Boot 3 + Vue 3 的组合跑了半年,除了个别老依赖需要替换,没遇到什么大坑。
1.2 前端选 Vue 而不是其他框架的真实体验
Vue 最大的特点是渐进式——你可以只在一个页面里引入它当 jQuery 用,也可以配合 Vite、Vue Router、Pinia 搭完整工程。这个特性对全栈开发者特别友好,因为你前端可能不是天天写,隔一段时间再来,Vue 那种"模板语法 + 响应式数据"的心智模型捡起来特别快。
对比 React 需要理解 JSX、Hooks 的渲染逻辑,Vue 的 template 更接近传统的 HTML 思维;对比 Nuxt 这种重框架,Vue 本身做中后台系统完全够用,不必额外承担 SSR 的学习成本。我见过好几个后端转全栈的同事,Vue 是唯一一个他们看了两周就能写业务页面的前端框架。
1.3 什么场景最适合这套组合
用了这么久,我的判断是:Spring Boot + Vue 最适合数据驱动型业务系统,比如后台管理系统、商品管理、订单处理、内容发布平台。这类系统的特点是:后端要处理复杂的业务逻辑和数据库事务,前端主要做表单、表格、状态展示,对 SEO 没有要求,对首屏加载速度的容忍度也比 C 端产品高。
如果你要做的是强交互的 C 端产品(比如抖音那种信息流),或者对性能极致敏感,那这套组合不一定最优。但在绝大多数中小型公司里,Spring Boot + Vue 就是那个"不会错"的答案,招聘市场上需求量大,外包接单也好找人协助。
2. 从零搭建前后端工程:目录结构与初始化细节
很多新手全栈的第一个坎不是写代码,而是不知道项目文件该往哪放。我在代码审查里最常见的毛病就是:所有 Controller 堆在一个包里,所有接口都在一个类里,前端页面全部写在 App.vue。这套习惯在个人项目里还能忍,一旦进入团队协作就是灾难。下面是我目前用下来最顺手的工程组织方式。
2.1 后端项目结构与分包思路
后端我用 Maven 构建,Java 包名根据功能横向划分,而不是按技术类型划分。对比一下两种分包方式:
- 按技术分包:controller / service / mapper / entity,每层一个包
- 按功能分包:user / order / product,每个功能域内部再分层
小项目按技术分包没问题,项目一旦超过二十个接口,按功能分包查代码效率高得多。我现在习惯的包结构是这样:
com.example.project ├── config # 配置类(跨域、拦截器、Swagger) ├── controller # 接口层 ├── service # 业务层 │ └── impl ├── mapper # 数据访问层(MyBatis-Plus 用 mapper 接口) ├── entity # 数据库实体 ├── dto # 请求参数对象 ├── vo # 返回视图对象 └── common # 统一返回体、异常处理、常量其中 dto 和 vo 很多新手容易忽略,我实际写的规范是:接口接收参数用 dto,返回参数用 vo,entity 只在 service 和 mapper 之间传递。这样做的核心价值是:数据库字段变动时,接口的出入参结构不会被牵连,前后端联调不会因为一个字段改名就崩。
2.2 前端工程的目录规划与组件化意识
前端这块,新项目我建议直接用 Vite 创建 Vue 3 工程,命令是npm create vite@latest project-name -- --template vue。Vue CLI 目前处于维护模式,新项目没必要再用它,但如果你需要在老项目里维护 Vue 2,那 webpack 和 Vue CLI 还是绕不开。
Vite 生成的项目里,src目录我习惯这样组织:
src ├── api # 接口请求定义(按模块拆文件) ├── assets # 静态资源 ├── components # 通用组件(按需放) ├── router # 路由配置 ├── store # Pinia 状态管理 ├── views # 页面级组件 ├── utils # 工具函数、axios 实例 └── App.vue关于组件化,我特别想多说一句:不是所有东西都要封装成组件。如果你发现一个组件只在某个页面用一次,放 views 目录下当局部组件即可,不必为了"组件化"而强行抽到 components。我在项目里看到过不少反面案例:封装了十几层的通用表格组件,结果每次需求变更都要改组件源码,比直接写页面还累。组件的价值和复用次数成正比,一个组件至少有三个地方在用才值得抽象。
2.3 单仓库还是双仓库:一个现实的取舍
如果你是自己做个人项目,或者团队只有两三个人,我建议用一个 Git 仓库,里面建backend和frontend两个平级目录。这样提交一个 PR 就能同时看到前后端改动,回滚也方便。如果团队规模已经超过五个人,前后端分别建仓库更合适,因为这时候接口文档就是契约,前后端各自独立发版,需要能单独回滚。
3. 开发环境配置:最容易耽误进度的几个坑
我在帮初学者看环境问题时,发现大部分报错不是代码问题,而是环境没配对。下面这几个坑,我几乎每个月都会遇到一次。
3.1 JDK、Maven、Node 的版本搭配
先说结论,再解释原因:
- Spring Boot 2.x → JDK 8 或 JDK 11,Maven 3.6+ 都能跑
- Spring Boot 3.x → 必须 JDK 17
- Vue 3 + Vite → Node 16 以上,18/20 都挺稳
我遇到的比较头疼的问题是 Maven 依赖下载慢和下载失败。解决办法很简单,在~/.m2/settings.xml里配置阿里云镜像:
<mirrors> <mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors>前端同理,用 npm 的镜像源:
npm config set registry https://registry.npmmirror.com3.2 IDEA 社区版跑 Spring Boot 的完整方案
很多人用 IntelliJ IDEA Community Edition 问得最多的问题就是:社区版怎么用 Spring Boot?其实社区版完全能跑 Spring Boot 项目,区别只是没有 Spring Initializr 这个项目初始化向导。
我的做法是两步:先在浏览器打开start.spring.io,勾选需要的依赖(Spring Web、MyBatis、Lombok、MySQL Driver 等),生成一个 zip 包;然后在 IDEA 里直接 Open 解压后的文件夹,Maven 会自动导入依赖。跑 Spring Boot 主类的 main 方法,社区版的调试功能完全够用。
补充一个增强体验的小技巧:社区版的应用商店搜 Spring Boot Helper 插件,装完后可以像旗舰版一样一键启动和重启 Spring Boot 应用,免费,很香。
3.3 开发环境的前后端启动顺序
我推荐的启动顺序是:先启动后端,再启动前端。原因是前端开发服务器启动时会检查代理配置,如果后端没起,登录接口直接报错,你分不清是前端问题还是后端问题;后端先启动,可以用 Postman 或浏览器直接访问 Swagger 接口文档,确认服务正常再搞前端。
后端启动成功后,我一般会在浏览器先访问一下接口文档路径(Spring Boot 3 用http://localhost:8080/swagger-ui/index.html,Spring Boot 2 用http://localhost:8080/swagger-ui.html),看到接口列表再去启动前端,减少了 90% 的"怎么连不上"问题。
4. 前后端联调的核心:接口规范、跨域与 axios 封装
前后端分离项目的联调阶段才是真正的主战场。如果你想开发过程中不互相扯皮,接口规范必须在开工前定好。这一块我把自己沉淀下来的规定完整写出来。
4.1 统一返回体:所有接口的输出格式
我见过不少团队的口头禅是"接口返回格式随便定,前端自己处理",这句话成了无数混乱的根源。毫不过分地说,如果一家公司所有接口都能保证返回格式一致,前后端联调的效率至少提升一半。
我习惯的统一返回体长这样:
{ "code": 200, "message": "success", "data": {} }code 为 200 表示成功,非 200 表示业务失败或异常。前端 axios 拦截器只需要判断 code 是否为 200,统一弹错误提示,所有接口写起来都干干净净:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }4.2 接口路径规划:资源命名与第三方接口边界
RESTful 风格说了很多年,我实际落地时的规则很朴素:用名词加 HTTP 方法表达动作。比如获取用户列表是GET /api/users,创建用户是POST /api/users,更新用户是PUT /api/users/{id}。不要出现GET /getUserByType这种动词式路径,一个资源一个 Controller,路径统一加/api前缀,方便后面做代理和权限拦截。
热词里有一个很典型的问题:"Spring Boot 对外提供的接口(给第三方)应该放在哪里?是单独的服务还是放在对应的服务里?" 我的经验是:如果只是两三个接口,放一个独立的thirdparty包,独立 Controller,独立鉴权逻辑;如果接口数量多了或者责任边界必须清晰,拆成单独服务。原因是给第三方的接口和内部接口在安全要求上完全不同——第三方接口需要签名验签、限流、独立的 QPS 控制,跟内部接口混在一起很容易出安全事故。我见过一个项目把第三方回调接口写在用户 Controller 里,结果某次接口权限调整直接把回调搞挂了,排查了很久。
4.3 跨域问题的三种解法:从开发到生产
跨域是前后端分离绕不开的话题,后端和前端各有一种"最省事"的写法,但真正生产环境用的通常是第三种。
开发阶段最简单的办法是在后端加@CrossOrigin注解,或者在配置类实现全局 CORS:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*"); } }前端的"最省事"办法是在 Vite 配置里配 proxy,让前端请求/api时自动转发到http://localhost:8080:
// vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })如果你用了 proxy,开发环境是不需要后端配置 CORS 的,因为浏览器看到的请求是同源的(都来自前端开发服务器)。但生产环境用 nginx 反向代理的话,nginx 就把跨域解决了。CORS 只在"前端和后端分别用不同域名/端口直接访问"时才真正需要。我自己现在的习惯是:前后端各自都做一层代理配置,开发期用前端 proxy,生产期用 nginx,后端基本不再写 CORS。
4.4 axios 封装:为什么不能每个页面直接请求
axios 封装的意义不是"写着好看",而是解决三个实际问题:
- 统一注入 token:每次请求都自动把 JWT 加到 Authorization 头,不用每个接口手动写
- 统一拦截错误:code 不为 200、HTTP 401、网络异常都能自动提示
- 统一处理加载状态:可以在拦截器里集中控制进度条或 loading 遮罩
我项目里最简单但完整的 axios 封装大致是这样:
// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } else { ElMessage.error(error.response?.data?.message || '网络异常') } return Promise.reject(error) } ) export default request用的时候每个 api 模块就是干净的接口定义:
// src/api/user.js import request from '@/utils/request' export function getUserList(params) { return request.get('/users', { params }) } export function createUser(data) { return request.post('/users', data) }5. 登录鉴权与动态路由:从后端发令牌到前端守卫
中后台系统几乎都逃不掉登录和权限控制。这套逻辑前端和后端各占一半,我拆开来说。
5.1 后端签发与校验 JWT 的完整链路
我用的方案是 Spring Boot + JWT + 拦截器,不引入 Spring Security(如果是简单的角色权限,Security 的配置成本反而偏高;等业务复杂到需要方法级权限控制时,再引入也不迟)。
后端主要做三件事。第一件事是登录取用户:账号密码查到用户后,用 BCrypt 校验密码(密码永远不能明文存储,注册时BCryptPasswordEncoder.encode(),登录时matches()对比)。
第二件事是签发 JWT,我用 jjwt 库:
@Configuration public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire-days}") private int expireDays; public String generateToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expireDays * 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }第三件事是拦截器校验,写一个AuthInterceptor实现HandlerInterceptor,在preHandle里从 Authorization 头取出 token 并解析,解析失败直接返回 401。然后注册拦截器时排除登录接口和 Swagger 路径。
5.2 前端登录态管理与路由守卫
前端拿到 token 后,我习惯存localStorage,因为页面刷新后Pinia状态会丢失,而 token 需要持久化。请求拦截器会自动带上 token,后端校验通过就能访问数据。
路由守卫的核心逻辑是:没有 token 时,任何页面跳转都重定向到 /login:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })5.3 动态路由:按权限加载菜单和页面
热搜里提到的"vue 动态路由"是一个进阶需求。最常见的场景是:不同角色的用户登录后看到的菜单不同,路由表不能在前端静态写死,否则用户直接在地址栏输入 URL 就能越权访问未授权页面。
我的实现思路分四步:
- 登录成功后,调用
GET /api/auth/menus获取当前用户的权限编码和菜单列表 - 把后端返回的菜单数据递归生成符合 Vue Router 规范的
RouteRecordRaw数组 - 用
router.addRoute()逐条动态添加 - 全局守卫里判断:如果 store 中没有动态路由,则先拉取菜单并添加路由再放行
这样处理之后,前端渲染的菜单、后端接口的权限、路由的访问控制三方统一,权限变更只需要改数据库,不用重新发前端包。
5.4 按钮级权限和 Vue 插槽的实际使用场景
菜单级权限解决后,热词里"vue 插槽"对应的是按钮级权限的常见实现——我给你举一个实际例子。在表格操作列,不同角色能看到的操作按钮不同,我封了一个AuthButton组件,内部用自定义指令判断权限编码来决定是否渲染:
<template> <el-button v-if="hasPermission(authCode)" v-bind="$attrs"> <slot /> </el-button> </template>这里的<slot />就是插槽的应用,调用方往里传入按钮文字:
<AuthButton auth-code="user:delete">删除</AuthButton>如果你想让组件更灵活,还可以用具名插槽、作用域插槽等。核心思想是:插槽是组件提供的一个"可插入内容的占位符",让父组件决定子组件中间的显示内容。理解到这一层,Vue 插槽就算真正入门了。
6. 常见业务需求落地:文件上传、视频播放与 PDF 展示
中后台系统做到一定程度,文件相关功能基本躲不开。我把高频的三个需求一起说完。
6.1 图片视频文件上传:后端接收与前端组件
后端接收文件非常简单,Spring Boot 的MultipartFile一顿操作:
@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { // 1. 校验文件大小和类型 // 2. 生成存储路径,用 UUID 重命名(避免中文名、乱码、覆盖) // 3. 保存到本地磁盘,比如 D:/upload/2025/01/xxx.jpg // 4. 返回可访问的 URL }注意三点:文件不能直接存数据库(除非极小文件);保存路径不能是项目内部目录(重新部署会丢失);返回 URL 要通过 nginx 或静态资源映射暴露出去。
前端多文件上传用 Element Plus 的el-upload,设置action属性指向http://localhost:8080/api/upload,或者自定义http-request用 axios 带上 token 上传。注意el-upload默认收到的返回值格式和后端统一下发格式不一致,需要在on-success回调里做一次数据格式转换。
6.2 播放 m3u8 视频:前端免插件方案
"vue 播放 m3u8 免安装"是热词里的高频问题。m3u8 是 HTTP Live Streaming 的播放列表格式,浏览器原生不能直接播放,需要引入hls.js。这个库会把 m3u8 解析成浏览器能播放的片段,实现如下:
<template> <video ref="videoRef" controls autoplay style="width: 100%"></video> </template> <script setup> import Hls from 'hls.js' import { onMounted, ref } from 'vue' const videoRef = ref(null) onMounted(() => { const video = videoRef.value const videoUrl = 'https://example.com/video/index.m3u8' if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(videoUrl) hls.attachMedia(video) } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari 原生支持 HLS video.src = videoUrl } }) </script>至于 m3u8 文件怎么来?一般是后端用 ffmpeg 把视频转码切片出来:
ffmpeg -i input.mp4 -c:v h264 -c:a aac -f hls -hls_time 10 -hls_list_size 0 output/index.m3u86.3 Vue 里显示 PDF:别再照 img 直接怼
"vue image 能显示 pdf 吗"这个热词,答案是:<img>标签不能直接显示 PDF,浏览器对 PDF 的处理是直接打开新页面或下载,而不是渲染进图片框。要在页面内嵌 PDF 预览,我有两种方案:
- 简单方案:用
<iframe :src="pdfUrl" />或<embed :src="pdfUrl" type="application/pdf" />,浏览器自带 PDF 阅读器就能预览。缺点是不同浏览器样式不一致,移动端支持差。 - 进阶方案:用
pdfjs-dist,Mozilla 出品的 PDF.js 库,可以按页渲染到 canvas,能自定义翻页、缩放、水印等功能。缺点是代码量多不少,而且大文件渲染慢,需要做分页加载优化。
我个人的建议是:非核心场景用 iframe,三分钟搞定;如果对交互有要求,再考虑 pdfjs-dist,别一上来就上重方案。
7. 打包部署与项目交付:Vue 进 Spring Boot 的两种姿势
全栈项目做完,最后一定绕不开部署。前后端分离项目的部署方式直接决定了你维护成本的高低。
7.1 姿势一:Vue 打包进 Spring Boot 的 static 目录
热词里"vue 打包放进 springboot中"是这个问题的经典描述。这种姿势适合个人项目、小型 demo、内网工具系统。操作流程:
- 在前端项目执行
npm run build,生成dist目录 - 把
dist里的所有文件复制到后端的src/main/resources/static/ - 用 Maven 打包
mvn clean package,生成一个包含前后端的 jar - 启动 jar,浏览器访问
http://localhost:8080就能看到整个全栈应用
这个方案的核心坑有两个。第一个是Vue Router 的 history 模式问题:你直接访问http://localhost:8080/user/list会 404,因为后端没有这个路由。Spring Boot 需要加一个转发规则,让所有非 API 路径都回到index.html:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{spring:\\w+}") .setViewName("forward:/index.html"); registry.addViewController("/{spring:\\w+}/**") .setViewName("forward:/index.html"); } }或者更省事:构建时直接用 hash 模式,路由变成/#/user/list,刷新就不会 404,代价是 URL 难看一点。
第二个坑是打包体积和更新效率:如果前端经常改,每次都要重新打整个 jar,传到服务器几百 MB 很浪费时间。所以这个姿势我更倾向于"纯演示或一次性交付"场景。
7.2 姿势二:前后端分离部署,nginx 做反向代理
稍微正式一点的团队项目都应该用这种方案。流程是:
- 前端
dist部署到 nginx 的静态目录,比如/usr/share/nginx/html - 后端 jar 用
java -jar app.jar启动在 8080 - nginx 配置把
/api/开头的请求转发到后端:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; # 前端 history 路由回退 location / { try_files $uri $uri/ /index.html; } # 接口代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这种方式的维护优势是:前端更新只需要把新的 dist 传上去,替换静态文件后nginx -s reload就完事,全程不需要停后端。后端接口更新重启 jar 时,前端用户无感知。我负责任地说,只要你的项目要持续迭代,用姿势二。
7.3 项目源码交付时的注意事项
热词里"vue 项目源码怎么发给别人",这个看似简单的问题,实际交付时经常出乱子。我的标准操作是:
- 检查
.gitignore是否配置正确,node_modules、dist、target绝对不能发 - 提供
.env.example文件,把VITE_API_BASE_URL这类环境变量写清楚,让对方自己复制成.env.development - 后端数据库脚本单独放在
sql/目录,并写明 MySQL 版本 - README 里写清楚 JDK/Node/Maven 版本要求,加启动步骤(先启动后端再启动前端的说明不能省)
8. 上线后的监控与排查:Spring Boot Admin 与日志体系
项目一旦上了生产,开发期那套"看控制台输出"的办法就不够用了。这个问题热词里也提到了"spring boot 实现监控,都有哪些需求和功能",我用自己的落地实践来回答。
8.1 Spring Boot Admin:十分钟搭一个轻量监控面板
Spring Boot Admin 不需要写什么业务代码,它是通过 actuator 暴露的端点来收集应用信息,然后在前端面板上可视化展示。搭建分两步。
第一步,新建一个独立的 Spring Boot 工程作为监控服务端,引入依赖后主类加@EnableAdminServer:
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>3.2.0</version> </dependency>第二步,被监控的应用引入 client 依赖,并在配置里指定服务端地址:
spring: application: name: business-service boot: admin: client: url: http://localhost:9000启动两个服务后,访问监控服务端的 9000 端口,就能看到业务服务的状态。我对这套面板最常用的功能有三个:一是健康检查,看服务是否活着、数据库连接是否正常;二是内存和 GC 曲线,排查内存溢出和频繁 Full GC;三是在线查看日志,不用 SSH 上服务器翻文件。
8.2 日志规范:线上排查的第一只手
监控面板只能发现问题,定位问题还得靠日志。我的日志规范是从一次线上事故里学来的教训——那次系统半夜报了接口超时,因为之前的日志没有记录请求参数和耗时,完全无从下手。
现在我的后端在每个 Controller 方法上都加了一个简单的切面,统一记录请求路径、参数、响应时间:
@Aspect @Component @Slf4j public class ApiLogAspect { @Around("execution(* com.example.project.controller.*.*(..))") public Object logAround(ProceedingJoinPoint pjp) throws Throwable { String method = pjp.getSignature().toShortString(); long start = System.currentTimeMillis(); Object result = pjp.proceed(); long cost = System.currentTimeMillis() - start; log.info("接口调用 {},耗时 {}ms", method, cost); return result; } }logback 配置里我习惯按天切分日志文件,保留 15 天:rollingPolicy设置fileNamePattern为logs/app-%d{yyyy-MM-dd}.log,maxHistory设为 15。
这是我踩过很多坑之后沉淀下来的整套 Spring Boot + Vue 实战路线:技术选型想清楚为什么、工程结构从第一天就立好规范、联调靠统一接口体系和 axios 封装、权限用 JWT 加动态路由闭环、部署用 nginx 分离方式、监控靠 Spring Boot Admin 保底。每次带新人或者自己接手新项目,我都会按这条路径先理顺环境,再动手写业务。最后提一个很多人忽视的建议:前后端接口文档一定要跟着代码同步更新,我现在是把 Swagger/Knife4j 集成到 CI 流程里自动发布的,接口变动自动生成新文档,人工维护文档这件事本身就不靠谱。