直接说重点:这是一个真正能跑起来、能拿来写的课设/毕设级别项目,技术栈以 SpringBoot 为主、通过接口接入 GPT 大模型能力、实现个人健康数据的管理与分析。我在本地完整把它搭过一遍,整体分成后端服务、接口对接、前端可视化和系统文档四层结构,下面这篇就完整拆给你看:为什么这么设计、数据表怎么建、GPT 接口怎么接、哪些地方特别容易踩坑,以及那些网上教程不会明说的细节。
1. 项目整体设计与技术选型思路
1.1 个人健康管理系统到底要解决什么问题
健康管理不是只记一个体重数字,它是一连串动作的闭环:记录健康数据 → 统计分析变化趋势 → 生成健康建议 → 指导用户调整生活习惯。市面上的智能手表、体脂秤都会给你原始数据,但很少告诉你"这些数据加起来意味着什么"。这个系统的核心价值,就是把散落的健康指标收拢起来,再用大量模型能力把它们翻译成人话。
我从需求设计初就把系统拆成六大模块:用户模块、健康档案模块、数据记录模块、统计分析模块、AI 建议模块、系统管理模块。其中 AI 建议是整个系统的灵魂,没有 GPT 接入,它只是一个普通的数据录入工具;接入了以后,它就变成一个能跟你对话的私人健康助理。
另一个关键设计决定是前后端分离。虽然标题里没有明确说前端用什么,但既然 SpringBoot 只承担后端职责,前端采用 Vue3 + Element Plus 就很自然。有人可能问,为什么不用 Thymeleaf 直接做服务端渲染?我的理由是:健康管理场景天然需要图表和动态交互,分离式架构对可视化、异步加载、后续扩展都友好得多。
1.2 技术栈选型的深度分析
SpringBoot 3.x 现在很流行,但我实际开发时依然锁定 SpringBoot 2.7.x。原因很简单:GPT 相关的 Java SDK 生态、MyBatis-Plus 对 SpringBoot 3 的适配虽然已经跟进,但很多第三方 starter 还停留在 2.x 时代,比如一些图形验证码、日志增强组件。用 2.7.x 版本,兼容性和稳定性是最稳的,这个判断在实际开发中帮我避免了不少麻烦。
整套系统核心依赖如下:
- SpringBoot 2.7.18:应用主框架
- MyBatis-Plus 3.5.x:数据持久层,避免写一堆 JDBC 模板代码
- MySQL 8.0:主数据库
- Redis 2.6.x 对应版本:管理 Token 会话和热点数据缓存
- SpringDoc / knife4j:自动生成接口文档
- Hutool 工具库:处理日期、加密、HTTP 请求
- ECharts 5.x(前端):健康趋势图表
对于 GPT 接入这一环,我最后没有选择任何封装好的 Java SDK,而是直接用 Hutool 的 HttpUtil 调用老模型的 Chat Completions 接口。这么做的原因是:SDK 版本迭代太快,经常出现接口签名变化导致编译报错;而纯 HTTP 调用只依赖一个稳定的 REST API,出问题也好排查。核心逻辑只有几十行代码,可控性强,后续想换其他模型也只需要改配置。
分批去裁。项目里的健康状况评分、运动建议、饮食建议、指标异常分析,都是通过构造特定 prompt 模板实现的,这一点在后面我会给出具体模板。
2. 核心数据模型与健康指标设计拆解
2.1 数据库表结构设计,这 7 张表缺一不可
健康管理系统最怕的就是数据建模混乱。我当时把整个数据模型画了三遍才定下来,核心原则是"用户中心 + 指标维度 + 建议结果"三向分离。
第一张表是用户表。它跟标准用户表不太一样的地方是多加了几个体质相关字段:身高、基础体重、出生日期(用于计算年龄)、性别,这些是后续大模型生成建议的基础参数,单独建表字段比每次都传要好。
第二张表是核心业务表——健康指标记录表。我设计它时采用了纵向存储方案:每一条记录包含指标类型(如血压、血糖、心率、睡眠时长)、指标数值、单位、记录时间,外加用户的备注。这种纵向表结构对动态增加新指标特别友好,不用因为以后要加一个"血氧"字段而改表结构。
第三张表是饮食记录表,字段包括食物名称、估算热量(kcal)、碳水/蛋白质/脂肪克数、餐次类型(早/午/晚/加餐)、记录日期。食物营养成分的数据,前期可以先写死后端常量库,不必接第三方食物数据库,避免增加项目复杂度。
第四张表是运动记录表,记录运动类型(跑步/骑行/力量训练等)、时长(分钟)、消耗估算、运动日期。消耗热量可以用基础代谢和运动当量的简化公式计算,在服务端完成。
第五张表是健康建议表,存 GPT 返回的文本内容、建议类型、关联的健康指标批次号、创建时间。有个易错点:一条建议可能要关联多个指标记录,所以我单独用一个 batch_id 字段标识是哪一批数据触发的建议,避免多对多关联表过于复杂。
第六张表是提醒配置表,存提醒类型、提醒时间(用 cron 表达式)、是否开启。第七张表是系统日志表,记录关键操作审计日志。
数据库字符集统一使用 utf8mb4,排序规则 utf8mb4_unicode_ci。这是中文内容存储的基础配置,我看到过很多项目忘记设置,结果存汉字变成问号。表名统一预防针加t_前缀,比如t_user、t_health_record,这个习惯在多人协作时能省不少沟通成本。
2.2 健康指标的计算逻辑
健康指标里最基础的 BMI 计算反而要注意细节。公式很简单:体重(kg)除以身高(m)的平方。但如果你不做任何校验,用户把身高误填成 172cm(而不是 1.72m),算出来的 BMI 会是 0.58,系统直接给出"严重偏瘦"的可笑结论。所以服务端必须做单位归一化,身高统一存厘米,计算时再转米。
代谢当量和基础代谢的计算我采用的是经典 Mifflin-St Jeor 公式:
男性基础代谢 = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 + 5 女性基础代谢 = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 - 161
这个公式比旧版 Harris-Benedict 公式更贴近现代人群,在很多健康 App 中使用。年龄直接根据birthday字段动态计算,避免用户每年要手动改年龄,这也是一个容易被忽略但体验感很强的细节。
对于血压数据,系统需要做等级判定。比如收缩压 ≥ 140mmHg 或舒张压 ≥ 90mmHg 时处于偏高水平;心率 60~100 次/分为正常区间。这些阈值判断代码很简单,但跟 GPT 建议结合就有价值了:系统先做规则判断给出"风险等级",再把这个等级和原始数据一起传给大模型,大模型就能在一个明确的语境下生成建议,应答质量明显更高。
2.3 接口设计上的几个核心约定
我设计了统一返回结构Result<T>,包含code、message、data三个字段。code 为 200 表示成功,401 表示未认证,500 表示服务端错误。有人会觉得没必要,但当前端要频繁处理错误状态时,统一结构能减少很多 if-else 判断。
接口路径按照 RESTful 风格设计,例如:
POST /api/auth/register用户注册POST /api/auth/login用户登录GET /api/health/record/list分页查询健康记录POST /api/health/record新增健康记录GET /api/statistics/trend获取多项指标趋势POST /api/ai/advice获取 AI 健康建议GET /api/reminder/list查询提醒配置
认证方案用的是 JWT 而不是 Session。JWT 天然适合前后端分离场景,后端只需要在拦截器里校验 Token 合法性,不必管理 Session 存储。为了避免 Token 被泄露后长期有效,我设置了 24 小时过期时间,同时 Redis 里存一份黑名单:用户修改密码或主动退出时加入黑名单,立即失效。
3. 核心功能模块的实操实现细节
3.1 用户注册登录与 JWT 认证落地方案
注册接口看起来简单,但业务细节不少。密码不能明文存储,我用 BCrypt 加密。BCrypt 每次加密生成的哈希值都不同,但校验时不要求哈希一致——它会把 salt 一起编码进结果里,需要校验时自动提取。这一点经常有新手困惑:为什么两次注册同一密码存的是不同字符串?那是正常的,不用改逻辑。
注册时还需要做唯一性校验,用户名和手机号都不可重复。这个判断放在 Service 层做,先用 lambda 查询判断count > 0,有就直接抛出业务异常,由全局异常处理器统一捕获并返回友好提示。
JWT 生成我采用的是 jjwt 库。生成 Token 时把用户 ID、用户名塞进 claims,设置签发时间和过期时间,最后用 HS256 算法签名,签名密钥从application.yml读取。需要注意:密钥长度必须 ≥ 32 字节,否则某些版本会报弱密钥错误,这也是网上资料很少提的细节点。
拦截器是登录态校验的关键,我创建一个JwtInterceptor,实现HandlerInterceptor接口,在preHandle里从请求头Authorization取出 Bearer Token,解析成功就把用户信息放入 ThreadLocal 上下文,后续业务代码用UserContext.getUserId()就能拿到当前登录人。
3.2 健康记录模块的增删改查与参数校验
新增健康记录时,前端提交的数据包括指标类型、数值、记录时间等。后端除了做常规非空校验,还做了数值范围校验。比如心率范围如果不在 20~250 之间,说明是误录入,直接拒绝。这里用 JSR 303 注解,比如@NotNull、@DecimalMin、@DecimalMax,配一个全局@Valid校验,几行注解就能完成入口防护。
查询列表需要支持分页。MyBatis-Plus 提供了一个分页插件,配置一个MybatisPlusInterceptor,注册PaginationInnerInterceptor即可。使用时调用Page<UserHealthRecord>作为第一个参数,插件自动生成 count 查询和 limit 查询,十分方便。
列表接口还支持多种筛选条件:按指标类型、按时间范围、按关键字。MyBatis-Plus 的LambdaQueryWrapper可以链式拼接条件,例如:
LambdaQueryWrapper<HealthRecord> wrapper = Wrappers.lambdaQuery(); wrapper.eq(HealthRecord::getType, type) .ge(startTime != null, HealthRecord::getRecordTime, startTime) .le(endTime != null, HealthRecord::getRecordTime, endTime) .orderByDesc(HealthRecord::getRecordTime);注意.ge和.le前面的条件判断,当参数为空时自动跳过,避免"查询时间范围时传空导致查不到数据"的问题。
修改和删除接口要实现数据归属校验:记录的主键对应的userId必须等于当前登录用户 ID,否则返回 403。我见过不少项目在删除操作上只校验记录存在性不校验归属,导致用户能删别人的数据,这是严重的越权漏洞。
3.3 统计分析模块:ECharts 多指标趋势图
统计接口返回的数据结构直接影响前端图表渲染。我设计的趋势接口返回格式是一个数组,每个元素包含日期、指标类型、对应数值。前端拿到后按指标类型分组,分别传给 ECharts 的 series。
指标趋势查询实现的关键是时间段聚合。MySQL 的DATE_FORMAT(record_time, '%Y-%m-%d')可以按天格式化时间。如果想按周聚合,可以用YEARWEEK(record_time, 1),需要注意这个函数默认周一还是周日作为一周开始,不同场景要求不同。
为了统计模块的查询性能,我给(user_id, type, record_time)加了组合索引,这在数据量几百条时看不出差别,但到了几万条就会明显感觉到差异。顺手还可以加一个指标占比统计接口,比如近 30 天各类运动时长占比,前端用饼图展示,这部分工作量不大,但能让系统的完整度上一个台阶。
3.4 定时提醒模块:Quartz 与 cron 表达式的坑
提醒模块我采用的是 Spring 自带的@Scheduled注解,没有引入 Quartz,因为任务简单、不涉及分布式调度。关键点在于 cron 表达式写法,比如每天 8 点提醒喝水,写成0 0 8 * * ?;每周一、三、五晚上 7 点半提醒运动,写成0 30 19 ? * MON,WED,FRI。
这里有个实际经验:@Scheduled注解默认是单线程串行执行的。如果任务执行时间较长,多个任务会互相阻塞。所以我在启动类里加了一个TaskScheduler配置,设置线程池大小为 5:
@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); scheduler.setThreadNamePrefix("schedule-task-"); return scheduler; }提醒任务的核心逻辑是:每天固定时间扫描提醒配置表中开启状态的记录,把到期的提醒消息封装后通过 WebSocket 或站内信推送给用户。WebSocket 做实时推送体验最好,但需要一个额外配置类;如果时间紧,改成用户登录后拉取未读提醒列表也是一种性价比更高的方案。
4. GPT 能力集成:从 API 调用到提示词工程
4.1 SpringBoot 调用大模型接口的实际实现
GPT 能力是这个项目最有技术含量的部分,也是答辩时最容易被追问的部分。我从接口调用到提示词设计完整讲一遍。
我选择直接通过 HTTP 调用大模型标准的 Chat Completions 接口。请求协议是 POST,请求体是 JSON,包含model、messages、temperature等参数。用 Hutool 的HttpUtil封装请求非常简单:
public String chatCompletion(String systemPrompt, String userContent) { String url = "https://api.openai.com/v1/chat/completions"; Map<String, Object> body = new HashMap<>(); body.put("model", "gpt-3.5-turbo"); List<Map<String, String>> messages = new ArrayList<>(); messages.add(Map.of("role", "system", "content", systemPrompt)); messages.add(Map.of("role", "user", "content", userContent)); body.put("messages", messages); body.put("temperature", 0.7); String json = JSONUtil.toJsonStr(body); HttpResponse response = HttpRequest.post(url) .header("Authorization", "Bearer " + apiKey) .body(json) .timeout(30000) .execute(); // 解析返回的 choices[0].message.content }注意几个点:超时时间至少设置为 30 秒,因为大模型接口响应通常需要 3~10 秒;如果设置 5 秒超时,大概率会频繁失败。接口的temperature参数是控制随机性的,健康建议场景我设置为 0.7,既能保证回答稳定又不至于太死板。还有要求调用频率限制的问题,QPS 不要超过 3,否则容易返回 429 限流错误。
调用前做幂等设计也很重要:用户在页面上点一次"获取建议",可能因为网络抖动重复提交。我在前端做按钮 loading 禁用,在后端用 Redis 做 5 秒内重复请求拦截,双保险。
4.2 提示词工程:健康建议质量的分水岭
很多人接入大模型后就只是简单拼一句"请给我健康建议",效果当然非常差。真正有工程价值的做法是把用户健康数据组装成结构化上下文,再配合一个角色设定提示词。
我的系统提示词基本是这样写的:
你是一名资深健康管理师,拥有临床营养学和运动医学背景。请基于用户的健康数据,给出专业、具体、可执行的生活建议。要求: 1. 分析当前数据的异常项,指出潜在健康风险 2. 针对饮食、运动、睡眠三个维度分别给出建议 3. 建议必须具体可执行,不要泛泛而谈(比如写明具体食物、运动时长、频率) 4. 如果数据全部正常,也请给出维持健康的建议 5. 语气亲和专业,篇幅控制在200字左右用户消息则把数据全部序列化:
用户基本信息:男性,28岁,身高175cm,体重80kg 近期健康数据:BMI 26.1(超重);连续7天平均睡眠6.2小时;血压测量值 132/85mmHg;静息心率 78次/分 请基于以上数据分析健康风险并给出建议。这样组合出来的回答质量,跟随便丢一句话进去的结果完全是两个水平。我实际测试下来,模型的回答已经能够识别出"BMI 超重且睡眠不足可能增加心血管负担"这样的关联分析,这在传统规则引擎里需要写几十条 if-else 才能实现。
提示词工程还有一个进阶技巧:在一次请求里让模型返回 JSON 结构,而不是纯文本。比如要求"以 JSON 格式返回,包含风险等级、饮食建议、运动建议、睡眠建议四个字段"。这样后端解析结果就不再依赖正则匹配或者人工判断,可以直接结构化入库。实现方式是系统提示词里写明 JSON 格式样例,并把response_format参数设为{"type": "json_object"},代码里用JSONUtil.parseObj(content)解析。
4.3 接入大模型的降级与容错机制
大模型接口不能保证 100% 可用。网络抖动、限流、服务端过载都会导致调用失败。所以我在 AI 建议服务里设计了三级降级策略:
第一级,调用失败自动重试一次,重试时间间隔 1 秒。这是最简单有效的策略,很多瞬时错误一次重试就能解决。
第二级,重试仍失败则走本地规则引擎。我把健康指标判定规则用 Java 实现了,比如血压偏高则提示"注意低盐饮食、规律监测"等模板化文本。这部分是保底方案,保证功能始终可用。
第三级,规则引擎仍无法覆盖的场景(比如异常组合),返回一个通用提示:"暂时无法生成个性化建议,请咨询专业医生",并记录失败日志,后续人工检查。
我在实际开发中把RestTemplate的请求日志单独拉了一份,记录了每次调用的请求参数和响应耗时。分析后发现,响应耗时在 4~12 秒之间波动,所以前端请求超时时间也相应设置为 30 秒,UI 层会有一个"AI 正在思考中"的等待状态,体验上避免用户以为系统卡死了。
5. 安全加固、性能优化与文档整合实战
5.1 接口安全性:越权防护、SQL 注入与 XSS 过滤
安全这块如果只在答辩 PPT 里提一句"用了 JWT"是远远不够的。一个健康系统涉及用户体重、血压、既往病史等隐私数据,必须认真防护。
越权防护方面,我设计了数据权限注解@RequireOwner,在修改、删除、查询详情的接口加上该注解后,切面自动校验资源归属权。这种做法比在每个业务方法里手动判断userId要统一得多,也不容易遗漏。
SQL 注入方面,MyBatis-Plus 的LambdaQueryWrapper本身使用预编译参数,自带防注入能力。容易出问题的是手写的 SQL,比如统计报表里的复杂查询,我用${ew.customSqlSegment}时一定确保使用的是参数占位符#{}而不是${},这个细节在使用@Select注解时要格外小心。
XSS 过滤我用了一个XssFilter,对请求 body 里的<script>标签和javascript:协议进行转义。实现方式很简单:注册一个 Filter,通过XssRequestWrapper重写getParameter和getInputStream方法,将所有输入内容过一遍白名单清洗函数。个人健康管理系统的用户输入字段不少,比如饮食记录里的备注、健康建议里的自定义问题,不做过滤容易被存储型 XSS 钉上。
5.2 性能优化:从查询到缓存的实操细节
几百个用户量级的系统没必要上分库分表,但基本的性能习惯要有。
第一个优化点:健康记录列表查询接口。原始实现直接从 MySQL 查询全量再内存分页,效率很低。优化后先用 MyBatis-Plus 分页插件,配合(user_id, type, record_time)索引,单次查询量级控制在 20 条以内,响应时间稳定在 50ms 以内。
第二个优化点:首页仪表盘需要显示多项汇总数据(总记录数、近 7 天趋势、最新指标值)。这些数据都属于"读多写少"类型,我加了 Redis 缓存。用户修改健康记录时,用"先更新数据库、再删除对应缓存键"的策略,避免缓存与数据库不一致的问题。
关于缓存还有一个容易踩的坑:Redis 的 key 设计不要直接用裸 ID,要加业务前缀,比如health:user:123:recent。否则后续项目里 Redis 键多了以后,很难一眼看出这个键是干什么的,排障非常痛苦。
5.3 项目文档与 PPT 的结构设计
标题里提到的"含文档+PPT+源码",说明这类项目的交付物不只是代码,还有配套材料。文档部分我分成了三份:需求分析说明书、数据库设计文档、系统部署手册。毕业论文场景里这三份基本就是核心交付物。
文档最大的问题是"写不够细"。需求说明书只写"用户可以记录健康数据",这种写法等于没写。有分量的写法是给出用户用例、业务流程、数据字典,甚至包含原型图。数据库设计文档里每个表都附上字段说明、类型、约束和设计理由,让答辩老师看出数据建模的思考过程。
PPT 的核心逻辑是"问题导入 → 方案设计 → 技术实现 → 成果演示→ 总结展望",页数控制在 15~20 页。技术实现部分要放系统架构图、数据库 ER 图、核心代码片段和效果截图。演示环节建议提前录制一段 2 分钟的视频嵌入 PPT,因为现场演示经常出幺蛾子(比如网络波动导致 GPT 调用失败),有备用视频会从容很多。
5.4 本地部署与 Docker 打包的完整步骤
本地部署其实很简单,前提是环境齐全。我当时用的开发环境是 JDK 1.8、Maven 3.8、MySQL 8.0、Redis。项目里提供一个sql/init.sql脚本,初始化所有表结构并插入一个测试账号,新建用户后第一步应该是登录,健康数据可以先通过页面手动录入,测试 AI 建议等核心链路。
后台部署的话,用 Docker 会更顺手。我写了一个Dockerfile,从maven:3.8-jdk8构建到openjdk:8-jre运行,多阶段构建可以把最终镜像控制在 200MB 以内。另外用docker-compose.yml把 MySQL、Redis 和 Java 应用编排在一起,启动命令就一条:
docker-compose up -d有两点经验值得分享:第一,Java 应用容器里时区默认是 UTC,健康记录的时间显示会差 8 小时,必须在启动参数里加-Duser.timezone=Asia/Shanghai或者环境变量TZ=Asia/Shanghai。第二,大模型 API Key 不要写死在application.yml里,通过环境变量传递,防止代码提交到公开仓库后密钥泄露,这个习惯从项目开始就要养成。
6. 常见问题与排查技巧实录
6.1 SpringBoot 项目启动失败问题
很多人在本地跑这种项目遇到的第一个报错是Failed to configure a DataSource。这个错误的核心含义是应用启动时自动配置去寻找数据源,但没找到连接信息。常规解法是检查application.yml里 spring.datasource 配置是否正确:url、username、password 是否齐全,MySQL 服务是否启动,库名和账号是否存在。还有一个坑:如果你的.yml文件里中文注释没有转 UTF-8 编码,启动时会报Caused by: java.nio.charset.MalformedInputException,这类问题是编码问题,不是语法问题。
另外一个启动阶段常见的问题是高版本 JDK 编译报错。如果你机器上是 JDK 17,用 SpringBoot 2.7 加 JDK 1.8 目标编译会有版本冲突。解法是统一用 JDK 8 跑,或者在 pom.xml 里改java.version为 17,并升级相关依赖版本。在学校机房的机器上遇到过好几次这种环境问题,建议直接把 JDK 8 配置写进 README,能省很多人力。
6.2 GPT 接口调用失败的完整排查记录
这是整个项目里让我花时间最多的问题。症状是:接口偶尔成功偶尔超时,返回 500。我对失败日志做了一段时间的统计,发现 80% 的失败集中在两种状况:请求超时和限流。
超时问题的根源是上面提到的 HTTP 客户端默认超时太短,设置到 30 秒后基本解决。限流问题则麻烦一些,需要看响应体里的错误码。429 表示请求过于频繁,需要增加指数退避策略;如果出现 401 则检查 API Key 是否失效或账户余额是否不足,这一步经常被忽略,尤其是用到一些中转服务时特别容易出问题。
我还遇到过一种特殊状况:返回 200,但choices数组为空。这种情况通常是因为敏感词过滤或内容策略拦截。排查方法是打印完整响应 JSON,如果finish_reason是content_filter,说明内容触发了策略,需要调整提示词措辞。
6.3 数据统计接口常见的问题与慢查询优化细节
统计模块在数据量过千条后,出现了一次明显的响应变慢,从最初的 30ms 涨到了 1.2 秒。用EXPLAIN看了执行计划,发现全表扫描、没有走索引。原因是我建的复合索引顺序是(type, user_id),但查询条件里最常用的是user_id和时间范围,所以索引没有命中。调整顺序为(user_id, type, record_time)后,响应恢复到 40ms 以内。
这个案例的通用经验是:复合索引的字段顺序要按查询条件的选择性来排。选择性最高的字段放最左,MySQL 里user_id唯一值多、选择性高,放左侧能最大化索引命中率。
另一个统计接口易错点是动态查询条件拼接时,排序字段若来自前端传入的字符串,不能直接拼接进ORDER BY,会造成 SQL 注入。我做了白名单校验:排序字段只允许在白名单数组内取值,比如包含record_time、type、create_time,其余一律走默认排序。
6.4 前端调用后端接口的典型问题
我在联调过程中遇到最多的是跨域问题。SpringBoot 需要在配置类中注册跨域映射:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns是 SpringBoot 2.4 之后的写法,以前用allowedOrigins("*")在携带凭证时会有兼容性问题。allowCredentials 为 true 时,allowedOrigins 不能用*,这是浏览器规范限制,用 pattern 可以解决。
另外一个联调问题是日期格式。Java 后端默认返回的 LocalDateTime 格式是2025-01-15T10:30:00,前端如果直接展示,会有个 T 字母。我在项目里配置了全局 Jackson 日期格式化:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这样前端拿到的就是正常的中文时间格式,省去前端转换的麻烦。
7. 实测体验与总结思考
我完整跑通这个项目大概花了一个完整开发周的时间,其中真正写代码的时间其实只占了一半,另一半全花在跟 GPT 接口打交道和各种环境问题上。把这个过程回头梳理,有几点强烈建议给准备做类似主题的人。
第一,接大模型的系统不超过早接。先把功能验证做通了,再去完善界面和文档,避免最后发现模型调用链路的坑要改底层设计。我是先写了 20 行代码直接调接口拿到返回,确认可行后才开始搭提示词模板和降级逻辑。
第二,提示词的迭代要建立记录。我在本地建了一个 prompt 记录文档,每次改动都记下来:改了哪个要求、模型回答质量是否提升、失败案例是什么。这个方法虽然土,但对回答质量的提升极其显著,远比凭感觉瞎调有效。
第三,答辩时重点讲设计取舍。比如为什么用 JWT 不用 Session(前后端分离)、为什么用 HTTP 直接调用而不用 SDK(可控性和排障)、为什么提示词要结构化输出(方便入库)——这些"为什么"正是普通项目报告里缺失的东西,也是拉开评分差距的地方。
最后说一句实际经验:这个项目做下来,收获最大的不是学会了 SpringBoot 的某个注解或 GPT 的调用方法,而是完整走了一遍"需求梳理 → 数据建模 → 接口设计 → 集成第三方能力 → 部署上线"的全流程。如果你们在做类似的选题,遇到某个环节卡住了,按我前面写到的流程逐步排查,大部分问题都能在半小时内定位。祝顺利。