news 2026/10/17 7:41:07

SpringBoot+Vue前后端分离信息管理系统开发实战:从表设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue前后端分离信息管理系统开发实战:从表设计到部署

1. 项目整体认知与选型思路

1.1 这类系统到底要解决什么问题

先别急着看代码,我先把这类“信息管理系统”的业务逻辑捋清楚。说白了,疫情信息管理系统在业务上要回答三个问题:人员健康情况怎么收集、异常信息怎么上报和跟踪、管理端怎么看到全局数据。

听起来简单,但真做起来你会发现坑不少。比如“每日健康上报”听起来就是一个表单提交,实际上涉及用户身份校验、重复提交拦截、数据合法性校验、上报记录可追溯、异常状态自动标记等多个环节。而“管理员查看统计”更是要从几千条甚至几万条上报记录里按日期、按部门、按状态维度聚合,没有设计好表结构和查询语句,页面加载能把人急死。

我见过很多同学一上来就建一张大宽表,所有字段堆一起,结果统计查询慢、扩展困难、逻辑混乱。这个项目的价值恰恰在于用一套标准的SpringBoot+Vue全栈方案,把这类业务做得清晰、可扩展、能答辩。

这个项目适合谁?正在做毕业设计或课程设计的计算机相关专业学生,想从零跑通一个前后端分离项目的初级开发者,以及需要一套可直接改造成其他“信息管理系统”模板的人。你把这套骨架吃透了,换成设备管理、图书管理、社区管理,核心套路完全一样。

1.2 技术栈为什么是这套组合

SpringBoot + Vue + MyBatis + MySQL这个组合,已经是JavaWeb方向上当之无愧的“毕业设计标准答案”。但标准答案不等于平庸,我讲一下每层选型的理由,你答辩被问到也有话说。

后端用SpringBoot,是因为它把Spring的复杂配置压缩到了“启动类+注解”的极致。你用SpringMVC写一个REST接口要配一堆XML,SpringBoot里一个@RestController一个@RequestMapping就搞定。内嵌Tomcat更是福音,本地开发不用单独装容器,打包成jar直接跑,部署成本被压到最低。对于信息管理系统这种典型CRUD项目,SpringBoot就是“零废话”的框架。

持久层选了MyBatis而不是JPA,我的个人经验是:信息管理系统这类项目查询条件往往动态多变。你今天要“按日期查”,明天要“按状态+部门+模糊姓名组合查”,JPA的派生查询写起来很别扭,而MyBatis的动态SQL能把你从拼字符串的泥潭里救出来。更重要的是,很多导师在答辩时爱问“你SQL怎么写的”“这个统计是怎么查出来的”,你用MyBatis的XML文件能讲得非常具体,这是很大的加分点。

前端Vue没什么好争议的。Vue的双向绑定让表单交互写起来极其顺手,组件化拆分让页面结构清晰,配合Element UI组件库,你甚至不需要自己写CSS就能做出像样的后台界面。Vue2还是Vue3?如果是毕业设计,我建议用Vue2+Element UI,资料多、坑少、查问题快,你需要的是稳定产出而不是追新。Vue3的生态现在也成熟了,但Element Plus部分组件的API有变动,万一卡住了排查成本会高一些。

MySQL就更不必多说了,开源、稳定、教程铺天盖地。信息管理系统这类数据量级撑死几十万条,MySQL完全够用,事务、索引、分组查询这些特性也足够覆盖所有业务场景。

1.3 前后端分离架构的边界划分

这套系统采用前后端分离,核心思想是:后端只做数据接口,前端只做页面渲染和交互。两者通过JSON格式的HTTP请求通信,互不干扰。

后端工程的职责边界是:接收前端请求,校验参数,调用Service层处理业务逻辑,通过Mapper操作数据库,最后把结果包成统一格式返回。一句话,后端不关心数据长什么样显示在页面上,只关心数据和业务规则。

前端工程的职责边界是:处理用户操作,向后端发请求拿数据,把数据绑定到页面上,处理样式、交互、弹窗、路由跳转。前端也不关心数据存在哪个表里,只知道接口返回什么字段。

这个边界的价值在于:你可以并行开发,也可以分别测试。开发阶段用Vue的proxy代理解决跨域,部署阶段用Nginx把前端静态资源和后端接口统一在一个端口下,避免跨域问题。这是现在企业里最主流的前后端协作模式,你毕业设计用了这套,面试时聊项目也有话可说。

2. 数据库设计与核心表结构

2.1 合理的表怎么划分

数据库设计是整个项目的根基。表设计不合理,后期写代码会痛不欲生。我按自己的实践给你一套可直接用的表结构方案,共五张核心表,每张表都围绕一个明确的业务对象。

用户表(sys_user):存储系统所有账号,包含登录凭证和基础信息。

字段名类型说明
idbigint主键,自增
usernamevarchar(50)登录用户名,唯一索引
passwordvarchar(100)加密后的密码,BCrypt加密
real_namevarchar(50)真实姓名
phonevarchar(20)手机号
rolevarchar(20)角色,ADMIN或USER
departmentvarchar(100)所属部门/班级/社区
create_timedatetime创建时间

每日健康上报表(health_report):一条记录就是一次上报行为,记录用户当天填报的健康信息。

字段名类型说明
idbigint主键
user_idbigint上报人ID
report_datedate上报日期
temperaturedecimal(4,2)体温,保留两位小数
is_coughtinyint是否咳嗽,0否1是
is_fatiguetinyint是否乏力
is_contact_risktinyint是否有风险地区接触史
health_codevarchar(10)健康码状态:绿码/黄码/红码
locationvarchar(200)当前所在地
remarkvarchar(500)备注
create_timedatetime提交时间

这两张表就构成了系统的两个核心对象:人和人的健康记录。剩下三张表围绕业务延伸。

打卡登记表(check_in_record):记录用户在某时间点的进出场所记录,配合体温监测。

公告通知表(notice):管理员发布的通知公告,用户端可见。

异常信息跟踪表(abnormal_record):当上报数据出现异常(体温偏高、黄码红码、接触风险),系统自动生成一条异常记录,管理员跟踪处理状态。

2.2 字段设计的关键细节

讲几个容易忽略但极其重要的细节。

第一个是密码加密。很多野路子教程里密码直接明文存储,这是极其糟糕的做法。数据库一旦泄露,所有账号裸奔。我强烈建议用Spring Security自带的BCryptPasswordEncoder加密,你不需要深入了解BCrypt原理,只要知道它是加盐哈希、不可逆、每次加密结果都不同就够了。登录时用matches方法校验,代码简单又安全。

第二个是状态字段的默认值。所有业务字段都要有默认值,不能让数据库出现NULL。比如is_cough、is_fatigue这些tinyint字段默认0,health_code默认“绿码”。前端拿到的数据就干净,不会出现“数据显示空白”“判断报空指针”的尴尬。这是新手最容易犯的错。

第三个是唯一索引。每日上报的业务规则是一个用户一天只能上报一次。这个规则用代码判断可以,但并发下容易出Bug。最稳妥的做法是在health_report表加上(user_id, report_date)的联合唯一索引,数据库层面严格拦截重复上报。你代码里先查询判断一次,再插入,数据库唯一索引再做第二道防线,双保险。

第四个小时字段。create_time统一用datetime类型,不要用字符串。order by和日期范围查询,datetime比字符串可靠得多。还有一个坑:MySQL的datetime默认不支持时区,如果服务器和客户端时区不一致,查出来的时间可能差8小时。在JDBC URL里加上serverTimezone=Asia/Shanghai,或者直接在MySQL连接参数里指定,能省掉很多莫名其妙的时间错位问题。

2.3 表关系怎么理

表之间的关系其实很简单,一句就能讲明白:用户和健康上报是一对多,用户和打卡记录是一对多,管理员和公告是一对多,异常记录与用户是一对多。

报表统计的时候怎么关联?核心思路是“尽量单表查询,必要时再关联”。比如按日统计上报人数,直接在health_report表group by report_date就行,根本不用join用户表。但如果要按部门维度统计上报情况,就需要join sys_user取department字段。

还有一个原则是“冗余字段优先”。比如health_report表里冗余了department字段(在用户表里也有),这样统计部门维度时不用join,查询效率高很多。有人可能觉得冗余违反范式,但在这种报表统计场景里,冗余是提高性能的常用手段,你答辩的时候能说出这个理由,导师会觉得你有工程意识。

3. 后端工程结构与核心实现

3.1 后端代码怎么分层

SpringBoot后端的包结构,我推荐遵循主流的Controller-Service-Mapper三层架构,再加一些辅助包。给你看一下我实际用的目录结构:

com.example.epidemic ├── controller // 接口层,接收请求,返回结果 ├── service // 业务逻辑层 │ └── impl // 业务实现类 ├── mapper // MyBatis数据访问层 ├── entity // 数据库实体类 ├── common // 公共类 │ ├── Result.java // 统一返回结果 │ ├── ResultCode.java // 返回状态码枚举 │ └── exception // 全局异常处理 ├── config // 配置类(跨域、拦截器等) ├── interceptor // 登录拦截器 └── util // 工具类(JWT工具等)

分层的价值在于职责单一。Controller只负责接收参数和调用Service,不写任何业务逻辑;Service只负责业务规则,不写SQL;Mapper只负责数据库操作,不写业务。有人会觉得“项目简单,不分层写更快”,但信息管理系统后续会不停加需求,今天加个公告模块,明天加个数据导出,不分层的话改一处崩三处,到时候哭都来不及。

3.2 统一返回结果与全局异常处理

前后端分离的关键约定之一就是接口返回格式统一。我自己的习惯是设计一个Result类,结构固定为code、message、data三个字段。

public class Result { private Integer code; private String message; private Object data; public static Result success(Object data) { Result result = new Result(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static Result error(Integer code, String message) { Result result = new Result(); result.setCode(code); result.setMessage(message); return result; } }

前端拿到响应后,统一判断code字段决定是正常渲染还是弹错误提示,不需要每个接口单独处理。这个统一性非常重要,也是专业项目的标志。

全局异常处理用@RestControllerAdvice注解。Controller代码里不需要到处try-catch,业务代码抛出自定义异常,全局处理器统一捕获后转成Result对象返回。

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } }

这样做的意义是:代码里干净利落,出错信息统一由后端把控。前端接到的数据结构永远是code/message/data,处理逻辑唯一,用户体验也一致。

3.3 登录鉴权怎么设计

登录鉴权这个模块,是几乎所有信息管理系统的门面。我的方案是JWT + 拦截器,这是SpringBoot项目里最常见也最好解释的鉴权方案。

用户登录成功后,后端生成一个JWT Token,包含用户ID、用户名、角色,设置有效期(我一般设置12小时或者24小时),返回给前端。前端把Token存到localStorage,后续每次请求在Axios请求头里带上Authorization字段。后端写一个拦截器,拦截所有需要登录才能访问的接口,取出Token校验签名和有效期,通过就放行,失败就返回401。

拦截器里有两个细节要注意。第一个是密码的校验放行路径,比如/login接口本身不需要登录,拦截器要配置excludePathPatterns放过。第二个是管理员接口和普通用户接口要区分权限,我一般在Controller方法上用自定义注解@RequireRole("ADMIN"),拦截器里再校验一次角色,这样就不会出现普通用户调用管理员接口的越权漏洞。

MySQL里的密码是BCrypt加密的字符串,登录校验就用BCryptPasswordEncoder的matches方法比对明文密码和密文,比对成功签发Token。答辩时把JWT的原理讲到“服务端无状态、客户端携带凭证”这个层面就够了。

3.4 MyBatis动态SQL与统计查询

信息管理系统的报表模块,是MyBatis发挥真正优势的地方。拿“按日期统计每日上报人数”这个需求举例:

<select id="countDailyReports" resultType="map"> SELECT report_date AS date, COUNT(*) AS total FROM health_report WHERE report_date BETWEEN #{startDate} AND #{endDate} GROUP BY report_date ORDER BY report_date </select>

一条SQL搞定,简单直接。如果要按部门和日期两个维度统计,再加一个字段和一层分组就行:

<select id="countByDepartmentAndDate" resultType="map"> SELECT department, report_date AS date, COUNT(*) AS total FROM health_report WHERE report_date BETWEEN #{startDate} AND #{endDate} GROUP BY department, report_date ORDER BY department, report_date </select>

另一个高频场景是条件查询,比如用户列表需要支持用户名模糊查、角色筛选、部门筛选、时间范围筛选,组合情况非常多。手写if判断会疯掉,MyBatis的where标签加if标签完美解决:

<select id="selectUserList" resultType="com.example.epidemic.entity.User"> SELECT * FROM sys_user <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <if test="role != null and role != ''"> AND role = #{role} </if> <if test="department != null and department != ''"> AND department = #{department} </if> </where> ORDER BY create_time DESC </select>

动态SQL用好了,一个Mapper方法就能覆盖十几个查询组合,不用为每种条件写一个方法。这是MyBatis框架最核心的实用价值,也是我推荐它的最重要理由。

4. 前端Vue实现要点

4.1 Vue工程与组件划分

前端工程我推荐用Vue CLI或者Vite创建,组件库用Element UI。整个前端目录的核心结构是这样的:

src ├── api // 接口请求模块,按业务模块拆文件 │ ├── user.js │ ├── report.js │ └── notice.js ├── router // 路由配置 ├── store // 全局状态(Vuex或Pinia) ├── views // 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── ReportList.vue │ ├── UserManage.vue │ └── Statistics.vue ├── components // 公共组件 └── utils // 工具类(Axios封装等)

views目录下每个文件就是系统的一个页面。Login是登录页,Dashboard是登录后的首页,展示核心统计数据看板,ReportList是健康上报记录列表,UserManage是管理员用户管理页,Statistics是报表统计分析页。

组件划分的原则是:一个页面就是一个视图文件,页面里重复出现的区块抽成公共组件。比如用户列表的搜索表单、表格列配置,如果在多个页面复用,就抽成组件;如果只用在一个页面,就直接写在页面文件里。过度拆组件的坏处是找代码要跨好几个文件,收益却不大,这个度要把握好。

4.2 Axios封装与路由守卫

Axios封装是Vue项目的“水电工程”,每个请求都要走它。我提供一个经过验证的封装思路:

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const request = axios.create({ baseURL: '/api', // 开发环境走Vue代理,生产环境走Nginx timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } else { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error('网络异常,请稍后重试') return Promise.reject(error) } ) export default request

响应拦截器里统一处理业务错误和401跳转,全世界的前端页面都不需要再单独写错误弹窗。这一个封装能省掉几十处重复代码,绝对值得花时间做。

路由守卫的作用是控制页面访问权限。未登录用户只能访问登录页,已登录用户不能回登录页,管理员路由要额外校验角色。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else if (to.path === '/login' && token) { next('/') } else { next() } })

4.3 Element UI表格与表单的实操细节

Element UI的el-table和el-form是使用频率最高的两个组件,这里有一个性能大坑:el-table一次性渲染几千条数据时会明显卡顿。信息管理系统的列表动辄几千上万条,必须配合后端分页接口。el-table加el-pagination组合,前端只展示当前页数据,翻页时重新请求接口。

表单交互上,el-form的rules校验规则建议做两层。第一层是前端校验,比如必填、手机号格式、邮箱格式,即时反馈用户体验;第二层是后端校验,防止绕过前端直接调接口的恶意请求。两层的校验规则需要保持逻辑一致,不然会出现“前端说成功、后端报错误”的割裂体验。

还有个细节是日期组件。上报记录查询通常需要“开始日期”和“结束日期”两个条件,用el-date-picker的daterange模式,一个组件搞定两个值,传给后端时注意格式化。这个格式化用dayjs或者moment都行,但项目里统一用dayjs就够了,moment体积大,没必要。

5. 关键业务逻辑与实操晋级

5.1 每日上报的防重逻辑

业务上“一个用户一天只能上报一次”,这个逻辑是这类系统最核心的规则之一。我刚才提过数据库层面用联合唯一索引兜底,但代码层面也应该有一次判断,原因有二:第一,友好的提示信息需要代码判断来生成,你不能让用户看到数据库的报错;第二,统计“是否已上报”这个状态需要查库。

Service层伪代码如下:

public void submitReport(ReportVO vo, Long userId) { String today = LocalDate.now().toString(); HealthReport existing = reportMapper.selectByUserIdAndDate(userId, today); if (existing != null) { throw new BusinessException(500, "今日已经上报,请勿重复操作"); } HealthReport report = new HealthReport(); BeanUtils.copyProperties(vo, report); report.setUserId(userId); report.setReportDate(LocalDate.now()); reportMapper.insert(report); }

再说一个容易踩的坑:判断“今天”时,前后端都要注意时区问题。前端获取本地日期,后端获取服务器日期,如果服务器时间不准或时区不对,就会出现在前端看起来是今天,后端判断成昨天的情况。解决方案是后端统一从服务端取时间,前端只做展示不做判断,并且数据库和JDBC连接时区都统一成Asia/Shanghai。

5.2 异常数据自动标记逻辑

这个系统比一般CRUD项目高级的地方在于业务规则:当用户上报的体温高于37.3、或者健康状态为黄码/红码、或者有风险接触史时,系统要自动在异常记录表里生成一条异常信息,通知管理员跟进。

Service层的实现思路:

public void handleAbnormalReport(HealthReport report) { boolean abnormal = false; String reason = ""; if (report.getTemperature().compareTo(new BigDecimal("37.3")) > 0) { abnormal = true; reason = "体温异常"; } if ("黄码".equals(report.getHealthCode()) || "红码".equals(report.getHealthCode())) { abnormal = true; reason = reason.isEmpty() ? "健康码异常" : reason + "、健康码异常"; } if (report.getIsContactRisk() == 1) { abnormal = true; reason = reason.isEmpty() ? "风险地区接触" : reason + "、风险地区接触"; } if (abnormal) { AbnormalRecord record = new AbnormalRecord(); record.setUserId(report.getUserId()); record.setReportId(report.getId()); record.setReason(reason); record.setStatus("待处理"); abnormalRecordMapper.insert(record); } }

这种业务逻辑写起来不难,但体现的是“需求分析能力”。答辩时你能讲清楚“上报表和异常表是两个对象,异常记录由业务规则自动生成”,比你只会写CRUD高一个档次。

5.3 统计报表怎么呈现

统计报表是信息管理系统的“门面”,也是最能直观展示你做了什么的功能模块。我推荐用ECharts图表库,在Vue项目里用vue-echarts组件封装,引入ECharts后做两到三个图表就足够撑起看板页面。

首屏看板推荐三个图表:第一个是折线图,显示最近14天的上报人数趋势,数据来源于按日期分组统计的SQL;第二个是饼图,显示全部用户健康状态占比,绿色/黄色/红色比例;第三个是柱状图,显示各部门上报覆盖率,覆盖率等于实际上报人数除以部门总人数。

这些数据的来源都是后端接口,前端只负责调用接口拿数据,再传给ECharts的option配置。代码层面注意一点:ECharts在图表容器隐藏时渲染会出现宽高为0的问题,如果你的统计页是Tab切换,初始化图表要放在Tab切换完成之后,或者在图表容器可见后再调用resize方法。

5.4 前后端联调与数据格式约定

前后端联调是最容易出问题的环节,大部分问题都出在“数据格式约定”上。我的经验是:开发阶段必须先定义接口文档,至少要在代码里对字段命名达成一致。Java后端习惯用驼峰命名(reportDate、healthCode),前端也直接用驼峰,避免出现JSON里的字段对不上的尴尬。

还有一个常见的格式坑:日期字段。Java后端返回的LocalDate默认序列化出来是“2025-01-15”这种字符串,但LocalDateTime序列化出来是一大串带T的格式。为了前端统一好处理,我建议在配置里统一日期格式:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=Asia/Shanghai

另外,BigDecimal类型的体温字段,序列化后前端拿到的就是数字,但要注意精度。MySQL的decimal(4,2)存36.6,拿回来就是36.60,前端直接展示在“36.60”没有问题,但如果要跟临界值37.3做比较,后端做,不要在前端用浮点数比较,这是干净的业务边界。

6. 常见问题与排查思路实录

6.1 前端接口跨域报错

跨域是前后端分离项目里出现频率最高的问题。现象是浏览器Console报“Access to XMLHttpRequest has been blocked by CORS policy”,或者请求显示为CORS error。

排查思路:确认前端请求的URL是否指向了正确的接口地址。开发环境下最省事的方案是用Vue CLI的devServer代理,在vue.config.js里配置:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

这样前端请求/api/user/list会代理到http://localhost:8080/api/user/list,浏览器看到的请求是同源的,跨域问题消失。配置后一定要重启前端服务,不然不生效。这个方案比在后端加@CrossOrigin注解更干净,也更能模拟生产环境Nginx的部署方式。

6.2 Mapper接口扫描不到

报错一般是“Invalid bound statement (not found)”或者“Mapper method not found”。大部分原因是启动类上漏了@MapperScan注解。

@SpringBootApplication @MapperScan("com.example.epidemic.mapper") public class EpidemicApplication { public static void main(String[] args) { SpringApplication.run(EpidemicApplication.class, args); } }

还有一个坑是XML映射文件的路径放错。我习惯把Mapper的XML文件放在src/main/resources/mapper目录下,然后在application.yml里声明:

mybatis.mapper-locations=classpath:mapper/*.xml

如果你既用了注解写SQL,又用XML写SQL,同一个Mapper方法两边都定义会冲突,记住一个原则:一个方法要么用注解,要么用XML,别混着来。

6.3 数据库连接失败

这个错误最常见的两种报错,一种是“Access denied for user”,另一种是“Communications link failure”。前者是账号密码或权限问题,检查application.yml里的数据库用户名密码是否和MySQL里一致,确认用户的host配置是不是localhost或%匹配到了。

后者常见的原因是MySQL服务没启动,或者端口不对。检查MySQL服务是否在运行,检查application.yml里的端口是否和你本地MySQL实际端口一致,默认3306,但很多人装的是3307,这个最容易被忽略。

JDBC URL建议加上这些参数:

spring.datasource.url=jdbc:mysql://localhost:3306/epidemic_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false

其中characterEncoding=utf8解决中文乱码,serverTimezone解决时间错位,useSSL=false避免SSL握手警告。这三个参数几乎是必加的。

6.4 登录成功后访问其他接口仍返回401

这个问题的根源通常是Token传递失败,或者拦截器过滤配置错误。排查步骤按顺序来:

第一步,看前端请求头里有没有带上Authorization。打开浏览器开发者工具的Network面板,点一下某个请求,看Request Headers里有没有Authorization字段。没有的话,检查Axios拦截器里的token获取逻辑,通常是localStorage的key写错了。

第二步,确认拦截器的放行路径配置。注意/excludePathPatterns的路径写法,/login只匹配/login,不匹配/user/login等其他路径。如果你的登录接口是/auth/login,拦截器放行的却是/login,那登录接口本身也会被拦截。

第三步,检查JWT工具类的密钥是否一致。签发Token和解密Token必须用同一个secret,否则解密必然失败。我建议把secret放在配置文件里,而不是写死在代码里,这样调试的时候也方便。

6.5 前端打包后部署到服务器接口404

前端npm run build之后生成dist目录,直接扔到Nginx的html目录下,页面能打开但接口全是404。核心原因是你没有配置Nginx反向代理。

正确做法是:Nginx监听80端口,静态资源走前端目录,以/api开头的请求转发到后端服务:

server { listen 80; server_name localhost; location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/; } }

注意try_files那行,它的作用是:刷新页面时如果找不到对应的静态文件路径,就回退到index.html,由前端路由接管。不加这行,刷新某个子路由页面时会404。这个坑我见过无数人踩过,哪怕到现在也还是高频问题。

6.6 后端打包的潜规则

SpringBoot项目打包成jar的时候,有个经典坑:默认打出来的jar不包含前端静态文件。如果你的项目目标是“一个jar包双击就能跑整个系统”,需要把前端构建后的dist目录复制到SpringBoot的src/main/resources/static目录下再打包。这个步骤可以用maven插件自动化,也可以手动复制。

我个人的建议是:毕业设计层面,分开部署更省事,前端扔Nginx,后端跑jar。但在演示给导师看的时候,一个jar包能启动整个系统会方便很多。两个方案各有利弊,你自己权衡。

最后说几点实在的

这套系统跑通并不难,真正花时间的是把一个一个小细节做对。我自己的体会是,信息管理系统类的毕设项目,数据库设计决定了项目上限,你花两天时间把表结构和业务规则打磨清楚,后面编码的时间能省下三分之一。进入编码阶段,先搭好统一返回结果、全局异常、登录鉴权这套“基础骨架”,再往上填业务功能,你会发现每个新功能都只是在重复同一套模式。

如果还想在答辩和简历上再加分,有几个低成本高收益的扩展方向:数据导出功能,把列表数据和统计结果导出成Excel;定时统计任务,用Spring的@Scheduled注解每天零点自动汇总前一天的报表数据;通知功能,用户登录后在站内看到管理员发布的最新公告。这三个功能都不复杂,但能把项目从一个“教学作业”提升到“工程应用”的观感。就把整套代码跑通、把业务逻辑吃透,这已经足够让你在答辩时对答如流了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 7:40:35

手撕List:从动态数组到双向链表,Java集合底层原理与面试实战

面试场上最让人措手不及的一句话&#xff0c;往往不是连环八股问答&#xff0c;而是面试官云淡风轻地来一句&#xff1a;“来&#xff0c;手撕一个List。”手撕这个词&#xff0c;在程序员圈子里含义很明确&#xff1a;不给IDE提示、不给参考文档、不从工具类里直接import现成的…

作者头像 李华
网站建设 2026/10/17 7:40:14

大模型应用混合架构:Agent-Workflow-RAG-Skill四要素协同实战

大模型应用做到一定复杂度&#xff0c;你会发现一个卡点&#xff1a;到底该让 Agent 自由发挥&#xff0c;还是把流程写死用 Workflow 硬编&#xff1f;纯走 Agent&#xff0c;回答发散、工具调用失控、出错不可追溯&#xff1b;纯走 Workflow&#xff0c;稍微有点开放性的任务…

作者头像 李华
网站建设 2026/10/17 7:40:12

Minimax H3模型在ComfyUI中的本地部署与显存优化

1. 项目概述&#xff1a;Minimax H3 模型在 ComfyUI 中的本地化落地实践 最近两周&#xff0c;我连续帮三位做AI视频生成的朋友调试本地ComfyUI环境&#xff0c;其中两位卡在“Minimax H3模型加载失败”这一步超过48小时——不是报错显存不足&#xff0c;就是节点识别异常&…

作者头像 李华
网站建设 2026/10/17 7:40:10

专科生毕业论文AI工具测评:选题、降重到格式排版全流程实战

专科生的毕业论文&#xff0c;一直是“过关型”任务里有名的一道坎。选题要自己定&#xff0c;开题报告要写&#xff0c;一二三稿要改&#xff0c;答辩PPT要做&#xff0c;最后还得过知网查重和学校那套严苛的格式规范。说实话&#xff0c;很多专科同学不是写不出来&#xff0c…

作者头像 李华
网站建设 2026/10/17 7:37:50

NRBO-CNN-BiLSTM-Attention多变量时序预测:原理、实现与调参实战

开头这几年做多变量时序预测&#xff0c;最头疼的就是“预测结果勉强能用&#xff0c;但稍微换个场景就拉胯”——单模型学不到全局规律&#xff0c;超参数又全靠手调&#xff0c;跑一次实验大半天就没了。最近我把 24 年新出的 NRBO 优化算法和 CNN-BiLSTM-Attention 这套组合…

作者头像 李华