最近在帮人调试一个疾病防控综合系统,前端Vue3+Element Plus,后端SpringBoot2+MyBatis-Plus,数据库MySQL8.0,前后端分离。这套组合几乎是目前毕业设计和中小型项目里的"标准答案":SpringBoot2负责业务接口,MyBatis-Plus把繁琐的SQL简化到令人舒适,Vue3的组合式API让页面状态管理比Vue2时代清爽得多,MySQL8.0则在JSON支持、窗口函数和稳定性上都比5.7有明显进步。
这个系统本质上是给疾病预防控制中心、社区卫生服务站这类场景用的信息管理平台。核心围绕传染病登记、病例随访、密接管理、疫苗接种记录、健康申报统计这几条业务线展开。你拿到源码后会发现,它不复杂,但涉及的技术面很全,非常合适用来串一遍Java全栈知识,也能直接作为毕业设计的底子改造。这篇就按我实际调试和二次开发的经验,把整个系统的架构、表设计、核心代码思路、部署过程和踩过的坑一次讲清楚。
1. 项目拆解:疾控业务如何落到功能模块上
1.1 需求拆解:从业务场景到功能清单
我拿到这个项目第一件事不是看代码,是先对照需求理清业务边界。疾病防控系统听起来大,落到实际场景里就是这几件事:
第一,基础信息管理。系统得有用户体系,但用户的角色不是单一的。疾控中心管理员、医院填报医生、社区卫生站的随访人员、普通居民,这些角色看到的页面和可操作的范围完全不同。所以权限模型不能只做一个用户表加密码,必须带角色和菜单权限。
第二,传染病登记与上报。医生发现疑似或确诊传染病病例后,需要在系统里建卡登记,内容包括患者基本信息、发病日期、诊断类型、报告单位、报告时间。这个动作在实际疾控工作中叫"报卡",是整个系统的核心操作,必须支持新增、修改、审核、查询列表。
第三,病例随访与密接管理。登记不是终点,确诊后需要持续跟进患者健康状况,每天记录体温、症状、用药情况。同时要登记密切接触者,做隔离提醒和健康监测。这对应到功能上就是随访记录表和密接管理表,两张表都跟病例表关联。
第四,疫苗接种记录管理。疫苗接种是防控中最前置、最有效的手段,系统里要维护接种点信息、疫苗批次、接种人记录、接种剂次。这块业务方便后面做覆盖率统计。
第五,统计与可视化。管理员最关心的不是单条数据,而是整体趋势,比如每日新增病例数、各区域感染分布、疫苗接种覆盖率。系统需要提供统计接口,前端配合可视化图表展示。
把业务梳理完,功能模块就自然出来了:系统管理(用户、角色、菜单)、病例信息管理、案例随访管理、密接管理、疫苗管理、统计仪表盘。这也是项目里数据表和前后端页面的划分依据。
1.2 整体架构设计:前后端分离是怎么落地的
这个项目采用的是标准的Vue3 + SpringBoot2前后端分离架构。前端项目独立运行在Nginx或者Node开发的Dev Server上,后端跑在Tomcat内嵌容器里,双方通过HTTP协议下的RESTful API通信。
前后端分离带来的好处在开发阶段和运维阶段都能感受到。开发阶段,前后端可以并行开发,前端用Mock数据先调页面,后端用Postman测接口,两边互不阻塞。部署阶段,前端打包成静态文件扔到Nginx里,后端单独跑一个Java进程,哪个挂了单独重启,不至于整站不可用。
数据流向是这样的:Vue3页面里用户点击按钮,触发API请求,Axios把请求发到后端的Controller层,Controller做参数校验和权限校验,然后调用Service层处理业务逻辑,Service通过MyBatis-Plus提供的Mapper接口操作MySQL8.0里的数据表,结果再逐层返回。说句实在话,这套链路本身没什么新奇的地方,但这个项目好就好在链路完整,从登录鉴权到增删改查再到统计报表,该有的环节一个没少,很适合用来建立全局观。
1.3 技术选型:为什么是这四个组件
先说SpringBoot2。选它不选SpringBoot3的原因很现实,社区生态和资料数量差太多。SpringBoot2.7.x是目前最稳定的版本,网上遇到问题基本都有现成答案,同时它也完全够用,腾讯云、阿里云的Java应用里大量还在跑2.x。这个项目的依赖管理走的是Maven,SpringBoot2.7父级POM进来自动管理版本,不用自己操心依赖冲突。
Vue3相比Vue2最大的变化是组合式API。以前Vue2是data、methods、computed分开写,一个页面逻辑复杂后代码会"散"得到处都是。Vue3的setup语法把数据、方法、生命周期钩子聚在一起,同一个业务的一块逻辑可以封装成自定义hook,复用起来非常舒服。加上Teleport、Fragment、Suspense这些新特性,写复杂表单和弹窗组件时会感觉明显顺手。
MyBatis-Plus真的是把MyBatis的缺点补上了。传统MyBatis几乎每张表都要手写XML和SQL,单表增删改查的机械劳动量很大。MyBatis-Plus内置了BaseMapper,提供了selectById、insert、updateById、selectPage这些通用方法,单表操作基本不用写SQL。它的条件构造器LambdaQueryWrapper也很有意思,查询条件用Java链式调用,比如lambdaQuery().eq(User::getStatus, 1).list(),不用拼接字符串,从根本上避免了一大票SQL注入风险。
MySQL8.0在5.7基础上增强了JSON类型和窗口函数等能力,而且默认的utf8mb4字符集让我这种对中文乱码有心理阴影的人很安心。8.0的安装和账户权限管理也比5.7完善,本地开发用Docker起一个8.0实例,几分钟就搞定。
2. 数据库设计与表结构,这些坑一定要避开
2.1 核心表设计:从用户到健康申报的全链路
这个项目的数据库设计是典型的业务驱动型。我把它拆成四条线来看:用户权限线、病例业务线、疫苗业务线、申报统计线。
用户权限线包含三张表:sys_user、sys_role、sys_user_role。sys_user里除了username和password,还存了real_name、phone、status字段。password存储不是明文,而是用了BCrypt加密的哈希串,后端登录时用BCrypt.matches进行校验,这也是SpringSecurity生态里默认推荐的密码加密方案。sys_role表只有几个预设角色,用户和角色通过中间表关联,方便后面做RBAC权限控制。
病例业务线是系统的核心,主要有disease_case、case_follow_up、close_contact三张表。disease_case是最重要的一张表,字段包括patient_name、id_card、gender、age、phone、address、onset_date、diagnosis_date、disease_type、report_unit、report_user、case_status。case_status这个字段承载了业务状态流转,比如待审核、已确诊、已排除、已治愈,不同状态对应不同列表页签和操作按钮。
case_follow_up表记录每次随访的内容,字段有case_id、follow_date、body_temp、symptom_desc、health_status、follow_up_doctor。close_contact表记录密接人员,字段包括case_id、contact_name、contact_phone、contact_address、contact_type、isolation_status、health_status。
疫苗业务线包含vaccine_info、vaccination_record两张表。vaccine_info存疫苗批次、生产厂家、有效期,vaccination_record存居民接种明细,字段有person_name、id_card、vaccine_id、dose_number、vaccinate_date、vaccinate_site。
健康申报表health_report是给居民日常上报体温和健康状况用的,字段有report_date、body_temp、symptom_desc、contact_history、report_user_id。这张表和统计接口配合,可以直接查出每天的申报人数和异常人数。
2.2 表关系与设计要点
表关系其实不复杂。disease_case是一切的源头,case_follow_up通过case_id关联它,close_contact也通过case_id关联它,形成一对多关系。vaccination_record通过vaccine_id关联疫苗信息表。sys_user_role是用户和角色的多对多中间表。health_report通过report_user_id关联sys_user,记录是谁汇报的。
几点设计经验分享下。第一,所有业务表都要有id主键、create_time、update_time这些公共字段。这个项目统一了字段命名,然后配合MyBatis-Plus的MetaObjectHandler做字段自动填充,插入和更新时不用手动给这几个字段赋值,省不少事。第二,状态字段一定要用数字或者短字符串,不要用中文。比如case_status用'0'代表待审核、'1'代表已确诊,前端用枚举映射成中文标签。存中文在数据库里,后续改状态逻辑和统计都会很难受。第三,身份证号、手机号这类查询频繁的字段要建索引,否则数据量上来后,按身份证查一个人的接种记录会全表扫描,性能会很糟糕。
这个项目数据库还有一个值得一提的点:初始化脚本里预置了完整的菜单表数据和几个测试账号。我拿到源码后直接导入SQL,启动后端,连上数据库就有数据可看,不用自己手动构造。对于做毕设的同学来说,这种细节真的能节省大量时间。
2.3 MySQL8.0的连接配置与常见参数
用MySQL8.0的时候,JDBC连接串跟5.7时代有点不一样。首先是驱动类变成了com.mysql.cj.jdbc.Driver,其次连接串需要带serverTimezone=Asia/Shanghai参数,否则会报"Server returns invalid timezone"的错误。8.0默认的认证插件是caching_sha2_password,老版本的驱动连不上,所以pom里的mysql-connector-java版本不能太低,至少8.0.x。
我实际使用的连接串长这样:
spring.datasource.url=jdbc:mysql://localhost:3306/disease_control?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true spring.datasource.username=root spring.datasource.password=你的密码 spring.datasource.driver-class-name=com.mysql.cj.jdbc.DriveruseSSL=false是本地开发必备,不然每次连接都会弹出SSL握手警告。allowPublicKeyRetrieval=true是为了解决8.0的RSA公钥明文传输问题,否则第一次连接可能报"Public Key Retrieval is not allowed"。
MySQL8.0安装方面,我推荐用Docker容器跑,避免本机卸载不干净导致的后患:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=disease_control \ -e TZ=Asia/Shanghai \ -v /data/mysql:/var/lib/mysql \ mysql:8.0.36启动后用docker exec -it mysql8 mysql -uroot -p进入容器,把项目里的sql/disease_control.sql导入进去,数据库这步就算完成了。注意Docker容器里的MySQL默认字符集可能不是utf8mb4,如果发现导入后中文乱码,可以在启动参数里加--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci。
3. 后端实现:SpringBoot2 + MyBatis-Plus 的核心代码
3.1 项目骨架与Maven依赖配置
后端项目结构是标准的Maven单模块布局:controller、service、mapper、entity、config、common几个包。启动类就一个DiseaseControlApplication,标了@SpringBootApplication,没做多余的花活。
pom.xml里有几个关键依赖值得看下。SpringBoot父POM用的是2.7.18版本,MyBatis-Plus用的是3.5.3.x,代码生成器用的是mybatis-plus-generator。此外还有fastjson2或Jackson做JSON序列化,lombok简化实体类,jwt做登录令牌。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>配置文件的命名我也建议延续SpringBoot的风格,用application.yml。端口、数据源、MyBatis-Plus逻辑删除配置、日志级别都放在里面。MyBatis-Plus的配置要注意map-underscore-to-camel-case默认就是true,所以数据库的create_time能自动映射到实体的createTime字段,不用在XML里费劲写resultMap。
3.2 MyBatis-Plus快速开发:实体、Mapper到条件构造器
实体类用lombok写非常简洁。以disease_case表为例:
@Data @TableName("disease_case") public class DiseaseCase { @TableId(type = IdType.AUTO) private Long id; private String patientName; private String idCard; private Integer gender; private Integer age; private String phone; private String address; private LocalDate onsetDate; private LocalDate diagnosisDate; private String diseaseType; private String reportUnit; private String reportUser; private String caseStatus; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }@TableName指定表名,@TableId标记主键并指定自增,@TableField用来描述字段填充策略。实体写好后,Mapper接口就三行:
public interface DiseaseCaseMapper extends BaseMapper<DiseaseCase> { }ServiceImpl继承ServiceImpl<DiseaseCaseMapper, DiseaseCase>,再顺手实现一个自定义的接口。这样一来,单表的增删改查、分页查询全部白送。比如列表页要按病种和状态筛选,用LambdaQueryWrapper写起来非常直观:
LambdaQueryWrapper<DiseaseCase> wrapper = Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(keyword), DiseaseCase::getPatientName, keyword) .eq(StringUtils.hasText(status), DiseaseCase::getCaseStatus, status) .orderByDesc(DiseaseCase::getCreateTime); Page<DiseaseCase> page = diseaseCaseService.page(new Page<>(pageNum, pageSize), wrapper);注意page方法会触发分页插件,分页插件需要在配置类里注册一下,否则分页不会生效:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }3.3 业务核心:病例上报与统计接口的实现逻辑
病例上报接口不是简单的save,还包含了一套状态流转控制。新增病例时默认状态是"0"(待审核),只有管理员可以改成"1"(已确诊)或者"2"(排除)。这个权限规则我用自定义注解加拦截器实现,在Controller方法上标@RequireRole("admin"),拦截器里从JWT解析出当前用户角色,不合符直接返回403。
统计接口是另一个重点。比如按天统计新增病例数,SQL层面用到了MySQL8.0的DATE_FORMAT函数:
@Override public List<Map<String, Object>> countCaseByDate(LocalDate startDate, LocalDate endDate) { QueryWrapper<DiseaseCase> wrapper = new QueryWrapper<>(); wrapper.select("DATE_FORMAT(create_time, '%Y-%m-%d') as report_date", "count(*) as total") .between("create_time", startDate.atStartOfDay(), endDate.plusDays(1).atStartOfDay()) .groupBy("report_date") .orderByAsc("report_date"); return diseaseCaseMapper.selectMaps(wrapper); }selectMaps返回List<Map>,其中key就是report_date和total,前端拿这个数组直接渲染柱状图和折线图,字段名都不用改。这里有个细节,统计涨跌趋势时最好用report_date这种语义明确的别名,别用mysql关键字当列名,不然前端很容易踩坑。
业务层和Controller层还有个约定:接口统一返回Result对象,它包含code、message、data三个字段。code为200表示成功,401表示未登录,403表示无权限,500表示服务端异常。所有Controller方法都返回Result.success(data)或Result.error("提示信息"),前端Axios响应拦截器里判断code,统一做Message提示,避免了每个页面各自写错误处理的重复劳动。
3.4 认证与权限:JWT方案的一次完整梳理
登录流程是:前端把用户名密码提交到/api/auth/login,后端拿用户名查出用户,用BCrypt校验密码,然后生成JWT返回给前端。JWT的载荷里放了userId、username、role三个关键信息,后端用io.jsonwebtoken库签发生成,密钥在application.yml里配置。
前端拿到token后存到localStorage,每次请求在Axios请求拦截器里塞进Authorization: Bearer <token>头。后端写了一个拦截器,统一从请求头里取token并解析,解析成功就把用户信息放到ThreadLocal里供Controller使用,解析失败直接返回401。除了登录接口和几个公开的查询接口,其他所有接口都受保护。
这个方案省掉了SpringSecurity那一大套配置,对毕设和中型项目来说是够用的。如果后续想引入权限菜单,可以根据用户角色动态返回菜单列表,前端用v-if或路由守卫来控制按钮和页面的显示,比在后端代码里写死权限判断要优雅很多。项目里我建议直接用自定义注解的方式去搞,这样后端权限点清晰,前端也方便配合。
4. 前端实现:Vue3组合式API的工程化落地
4.1 Vue3项目初始化与基础配置
前端用的是Vite作为构建工具,创建命令很简单:
npm create vite@latest disease-control-web -- --template vue创建完成后安装依赖,注意要装的包有这么几个:vue-router(路由)、pinia(状态管理)、element-plus(UI组件库)、axios(请求库)、echarts(图表)。
Vite相比Webpack最直观的优势就是启动快,Dev Server秒开。但Vite也有一些配置上的讲究,比如需要手动配置@别名指向src目录,在vite.config.js里加:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': path.resolve(__dirname, 'src') } }, server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })Dev模式下通过proxy解决跨域,打包后通过Nginx的location配置也可以解决跨域。这里要说明一下,本地开发时不要用axios.defaults.baseURL = 'http://localhost:8080'这种硬编码,因为打包后部署到服务器就要换地址,很麻烦。建议配置多个环境文件,比如.env.development和.env.production,这样切换环境时只需要改环境变量,代码不用动。
4.2 登录页、路由守卫和用户状态管理
登录页是典型的Element Plus表单:验证码可以不要,但基本的用户名、密码、回车提交、表单校验最好都有,不然答辩时会被问"连个前端校验都没有"就尴尬了。登录成功后路由跳转用router.push('/dashboard'),同时把用户信息和token存入Pinia和localStorage。
路由守卫是前端权限的第一道关卡。在router/index.js里给每个路由标meta: { requiresAuth: true },然后全局前置守卫里判断:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })这一步能挡住未登录用户直接URL访问页面,但别指望它做真正的安全控制,后端接口鉴权才是关键。前端路由守卫只是提升体验,让人觉得这是套正经系统。
Pinia在这套系统里的角色很简单,就是保存当前登录用户信息和动态菜单列表。Vue3里用Pinia比Vuex清爽的地方在于不需要写那么多Mutation和Action模板,直接改state就行,很适合中小型项目。
4.3 数据统计大屏与列表页的写法要点
统计页面是整项目的门面,答辩时先展示这个页面就很加分。echarts在Vue3里一般封装成一个公共组件,接收option作为props,内部负责初始化和监听变化。效果图展示每日新增病例的折线图、各病种占比的饼图、各区县病例分布的柱状图。
列表页的写法有几个习惯建议保持一致。第一,所有列表接口都用分页参数,页面用el-pagination组件,切页时重新请求接口。第二,表单组件统一用el-dialog包裹,提交成功后关闭弹窗并刷新当前页数据。第三,删除操作必须弹MessageBox确认,否则误删数据无法恢复。这几点看着不起眼,但直接决定了一个前端项目的成熟度。
细节上再补充一句,Vue3的ref和reactive一定要分清楚。列表数据用ref([])来声明,表单对象用ref({})来声明,别随便用reactive嵌套数组。reactive在修改深层属性时可能不会触发视图更新,ref在取值时要写.value,虽然刚开始觉得烦,但这是Vue3响应式机制的底层逻辑,多写两遍就习惯了。
5. 项目运行与部署:从本地到服务器的完整流程
5.1 本地运行环境准备
本地跑这个项目,最少需要三个软件:JDK1.8或JDK11、Node.js16+、MySQL8.0。很多同学的坑不是代码,而是环境版本对不上。SpringBoot2.7用JDK17也能跑,但没必要,用JDK8最稳,跟绝大多数教程保持一致。
后端启动前,先改application.yml里的数据库连接信息。启动方式可以直接在IDEA里运行启动类,也可以用maven打包后跑jar包。第一种方式适合开发和调试,第二种方式适合验证打包产物。IDEA里运行前需要先安装Lombok插件,不然实体类的getter和setter编译不过,这个坑新人经常踩。
前端启动前,先npm install装依赖。国内网络建议把npm源切到淘宝镜像:
npm config set registry https://registry.npmmirror.com然后npm run dev启动开发服务器,浏览器访问http://localhost:5173。登录时用预置的admin账号,密码是admin123之类的,具体以源码里的SQL初始数据为准。
5.2 打包构建与环境分离
前端打包命令是npm run build,产物在dist目录。dist里全是静态文件,部署到Nginx只需要两步:把dist目录的文件拷贝到Nginx的html目录里,然后在nginx.conf里加一个location配置:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files那行是关键,Vue路由用了history模式后,刷新页面时如果Nginx没配置这个,会直接404。/api的代理转发让前端页面和后端接口挂在同一个域名下,解决了生产环境的跨域问题。
后端打包用:
mvn clean package -DskipTests打完包在target目录下有一个disease-control-0.0.1-SNAPSHOT.jar,上传到服务器后执行:
nohup java -jar disease-control-0.0.1-SNAPSHOT.jar > app.log 2>&1 &查看日志用tail -f app.log,确认出了"Started DiseaseControlApplication"字样就说明启动成功了。
5.3 Docker Compose一键部署的补充方案
如果说本地跑是第一步,那线上部署还有更省心的方式。很多人喜欢用Docker Compose把前端、后端、数据库三个服务编排在一起。大致结构是mysql一个容器、后端一个容器、前端Nginx一个容器,前端容器里打包好的dist文件直接挂载进Nginx镜像。三个容器通过docker网络内网互通,端口只暴露80和3306。
这种做法的好处是迁移部署时一条docker compose up -d就能拉起来整套环境,对于后面要部署到云服务器、参加演示的场景挺方便的。但前提是服务器上得装好Docker和Compose插件,同时数据库数据要挂载volume持久化,不然容器重启数据全丢,这个教训我印象深刻。
6. 常见问题与排查技巧实录
6.1 MyBatis-Plus分页失效:版本迁移的坑
项目里Controller层写的是:
return Result.success(diseaseCaseService.page(new Page<>(pageNum, pageSize), queryWrapper));如果查询返回的分页数据里total永远是0,records却是全量数据,八成是MyBatis-Plus分页插件没注册。3.4版本之后PaginationInnerInterceptor必须要手动注册Bean,不像老版本配置好依赖就自动生效。我调试时踩过这个坑,加上配置类后问题迎刃而解。
另外,如果代码生成器生成的分页语句里出现了LIMIT 0,10这种奇怪参数,检查一下分页插件版本和MyBatis-Plus core版本是否一致。版本不一致导致的SQL拼接错误是MyBatis-Plus的经典问题,统一版本后基本不会再出现。
6.2 Vue3响应式丢更新:ref和reactive用错了
列表页里我一开始用reactive({ list: [] })存接口数据,然后res.data.list.forEach(item => list.push(item)),结果列表渲染出来了但勾选状态无法同步更新。排查半天发现是reactive数组的深层响应式在嵌套对象上有时候不触发视图更新。
我的建议是列表数据一律用ref,用list.value = res.data.records整体赋值。表单对象也一样,用ref({})整体替换。Vue3的响应式确实比Vue2强,但不是完全等价,遇到视图不更新的情况,先检查是不是用了错误的声明方式。
6.3 MySQL8.0的时区和认证问题
启动后端时如果报错"The server time zone value 'CST' is unrecognized",就是数据库时区没配。解决方式:连接串后面加serverTimezone=Asia/Shanghai,或者启动Docker容器时加-e TZ=Asia/Shanghai。如果项目里大量用了LocalDateTime,建议数据库连接串里同时加useLegacyDatetimeCode=false,避免时区被转换出错。
如果连接报"Public Key Retrieval is not allowed",则是在MySQL8.0的caching_sha2_password认证机制下,客户端第一次连接需要加密传输公钥,加allowPublicKeyRetrieval=true解决。这个报错在本地第一次连接时出现的概率很高,但是配置里写死这个参数对生产环境其实不太安全,生产环境建议用SSL连接替代。
6.4 跨域问题:本地和线上分别怎么处理
本地开发时,前端在5173端口、后端在8080端口,直接发起请求会触发跨域。我前面提过用Vite proxy解决,这个方案最优雅,浏览器里根本没有跨域这回事,因为请求是Vite Dev Server转发出去的。
线上部署时,前后端如果不在同一域名,就需要在后端写CORS配置,或者用Nginx反向代理。更推荐规划好域名,让前端页面和后端API共用域名,这样在Nginx层就解决了,后端不用暴露给公网,安全性和调试便利性都好很多。如果后端接口本身没做认证就把CORS开得很大,那大概率会被扫描工具盯上,这点在公网部署时一定要留意。
6.5 文档与答辩准备:给你的最后一条建议
这个项目带了完整文档,包含需求分析、数据库设计、接口文档、部署说明。你拿到手不要直接交,重要的是自己走一遍流程,把每一步记在笔记里。答辩时老师会随机追问,比如"这个字段为什么这么设计""某个接口怎么实现权限控制""如果并发量大了怎么优化",这些都需要你真正跑过代码后才能答得上来。
文档里的SQL初始数据也建议自己重新核对一遍,特别是表名和字段名,因为有些生成的SQL脚本跟实体类不完全对应。我的习惯是删库重建一次,通过前端页面完整操作一遍功能流程,确认所有页面都有数据展示,再去写答辩PPT。这个习惯能帮你避开"现场演示页面空白"这种大型尴尬现场。
我个人在实际操作中的体会是,这类全栈项目最忌讳"源码到手就算完成"。真正把它跑通、改一个自己感兴趣的功能点、亲手踩几次坑再爬出来,收获比读十篇教程都大。如果时间充裕,试着把统计大屏改成ECharts的Map图、把Excel导出做成异步任务,或者把登录换成验证码登录,任何一个方向的二次开发都能让你在做项目陈述时更有底气。