在医疗健康与AI深度融合的浪潮下,如何利用多模态大模型快速搭建一套疾病初筛与护理建议系统?本文基于SpringBoot与Vue,结合通义千问多模态能力,从架构设计、数据库建模到核心代码实现,完整拆解这一全栈项目的落地路径,为前端与后端开发者提供可复用的工程方案。
一、项目定位与技术选型
本系统的核心链路非常清晰:用户上传症状图片并辅以文字描述,服务端调用多模态AI模型完成初筛推理,最终返回结构化的风险等级、护理建议、下一步行动指引及免责声明。同时,系统还需支撑问诊历史分页检索、TXT/PDF报告导出,以及管理员运营看板等功能。
在技术选型上,后端采用SpringBoot快速构建REST API,配合MyBatis实现SQL精细控制,MySQL负责结构化数据存储。报告生成选用iTextPDF,支持中文字体嵌入与医院风格模板。AI能力层接入通义千问多模态模型,实现图片与文本的联合理解。
Spring Boot 3.3.5MyBatis 3.0.4MySQL 8.xRestTemplate(调用千问接口)iTextPDF(生成PDF报告)Jackson(JSON解析/序列化)
对于前端开发者而言,虽然本项目以Vue为前端框架,但其设计思想同样适用于React或Angular。理解后端接口契约与数据流,是UI开发与前端工具链高效协作的基础。
[AFFILIATE_SLOT_1]二、系统架构与角色用例
系统整体分为五层:表现层(Controller,负责请求接收与鉴权)、业务层(Service,核心业务编排)、数据访问层(Mapper+XML,SQL执行与聚合统计)、数据层(MySQL+本地文件目录)、外部能力层(Qwen API多模态推理)。
角色方面,系统定义了普通用户、管理员与外部AI服务三类。普通用户可完成注册登录、提交问诊、查看记录、导出报告及个人统计;管理员则聚焦运营看板,查看用户量、问诊量、趋势与风险分布。
非功能性需求同样关键:接口响应统一格式、异常可区分、图片安全落盘防路径穿越、AI返回不稳定时有兜底结构化结果、报告支持中文字体跨平台可读。这些约束直接影响了后续的代码实现策略。
三、数据库设计与核心表结构 ✅
数据库设计围绕三张核心表展开:用户表(user_account)、会话表(user_session)与问诊记录表(consultation_record)。
user_accountid:主键username:登录名(唯一)password_hash:密码哈希nickname:昵称role:角色(USER/ADMIN)created_at:创建时间user_sessionid:主键user_id:用户IDtoken:登录token(唯一)expire_at:过期时间created_at:创建时间consultation_recordid:主键user_id:用户IDnickname:本次问诊昵称question_text:问题描述image_url:图片访问路径preliminary_assessment:初筛结论risk_level:风险等级(LOW/MEDIUM/HIGH)nursing_advice:护理建议(JSON数组字符串)next_step:下一步建议disclaimer:免责声明raw_answer:AI原始响应文本created_at:创建时间
ER关系上,用户与会话为一对多,用户与问诊记录同样为一对多。这种设计既保证了数据隔离,又便于后续统计聚合。
四、核心业务流程拆解(泳道图+时序图)
系统包含四条核心流程,每条流程都涉及前端、后端、数据库与外部服务的协作。
流程一:注册/登录/会话鉴权。用户输入账号密码后,后端校验格式(用户名4-20位字母数字下划线,密码6-32位),使用SHA-256 + salt生成密码摘要。登录成功后生成超长token(两个UUID去横杠拼接),写入user_session。每次鉴权先清理过期会话,再校验token与角色。

流程二:提交问诊并生成初筛结果。接收multipart表单(question + image (+ nickname)),校验问题描述长度≤500、图片类型为image/*。图片落盘uploads,生成publicUrl(前端展示)与dataUrl(AI传图)。构造多模态提示词调用Qwen,解析返回JSON;若解析失败则走fallback(默认中风险+默认护理建议),完整记录落库并返回详情。

流程三:导出报告(TXT+PDF)。按consultationId + userId查记录防止越权。TXT拼接结构化文本;PDF包含封面页、内容页、问诊图片嵌入、头脚部(报告编号、分页、生成时间)及中文字体嵌入。

流程四:管理员看板统计。指标口径包括:totalUsers(总用户数)、totalConsultations(总问诊数)、recent7DaysConsultations(近7天问诊量)、activeUsers(近30天活跃用户)、riskDistribution(风险等级分布)、dailyTrends(按日趋势,空白天补0)、topUsers(问诊量TOP用户)。


五、关键代码实现与API设计
以下代码均来自本项目后端,按模块截取关键片段。
在密码安全方面,系统不保存明文密码,比较时比较hash值:
public String hash(String password) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String raw = password + "#" + appProperties.getAuth().getPasswordSalt();
byte[] bytes = digest.digest(raw.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(bytes);
}登录鉴权核心逻辑中,每次鉴权会先做过期会话清理,减轻脏数据积累:
public UserAccount requireUser(String authorization) {
String token = extractToken(authorization);
LocalDateTime now = LocalDateTime.now();
userSessionMapper.deleteExpired(now);
UserSession session = userSessionMapper.selectByToken(token);
if (session == null || session.getExpireAt() == null || !session.getExpireAt().isAfter(now)) {
throw new UnauthorizedException("登录状态已失效,请重新登录");
}
UserAccount userAccount = userAccountMapper.selectById(session.getUserId());
if (userAccount == null) {
throw new UnauthorizedException("用户不存在,请重新登录");
}
return userAccount;
}图片存储与安全处理方面,同一份图片输出两个地址,publicUrl给前端展示,dataUrl供AI调用:
public StoredImage store(MultipartFile imageFile) {
String contentType = imageFile.getContentType() == null ? "" : imageFile.getContentType().toLowerCase(Locale.ROOT);
if (!contentType.startsWith("image/")) {
throw new BusinessException("仅支持图片文件");
}
byte[] imageBytes = imageFile.getBytes();
String filename = FORMATTER.format(LocalDateTime.now()) + "-" + UUID.randomUUID().toString().replace("-", "") + extension;
Path target = uploadPath.resolve(filename);
Files.write(target, imageBytes, StandardOpenOption.CREATE_NEW);
String dataUrl = "data:" + contentType + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
String publicUrl = "/uploads/" + filename;
return new StoredImage(publicUrl, dataUrl);
}多模态AI调用时,提示词强约束JSON格式,便于后端结构化入库:
String prompt = "你是医学初筛与护理建议助手。请结合图片和问题做初步分析,不要做确诊。"
+ "请严格返回 JSON,字段如下:"
+ "{\"preliminaryAssessment\":\"\",\"riskLevel\":\"LOW|MEDIUM|HIGH\",\"nursingAdvice\":[\"\"],\"nextStep\":\"\",\"disclaimer\":\"\"}"
+ "。nursingAdvice 至少给 3 条,语言用简体中文。";AI返回解析与兜底逻辑确保即使AI输出偏离预期,也能返回可用结果而不是直接失败:
private AiStructuredResult buildStructuredResult(String contentText) {
String cleaned = stripCodeFence(contentText);
String jsonSegment = extractJsonSegment(cleaned);
if (!StringUtils.hasText(jsonSegment)) {
return buildFallback(contentText);
}
try {
JsonNode jsonNode = objectMapper.readTree(jsonSegment);
// ...读取字段并标准化 riskLevel
} catch (Exception ex) {
return buildFallback(contentText);
}
}创建问诊主流程代码如下:
public ConsultationDetailResponse createConsultation(Long userId, String nickname, String question, MultipartFile image) {
ImageStorageService.StoredImage storedImage = imageStorageService.store(image);
AiStructuredResult aiResult = qwenAiService.analyze(question.trim(), storedImage.getDataUrl());
ConsultationRecord record = new ConsultationRecord();
record.setUserId(userId);
record.setQuestionText(question.trim());
record.setImageUrl(storedImage.getPublicUrl());
record.setPreliminaryAssessment(aiResult.getPreliminaryAssessment());
record.setRiskLevel(aiResult.getRiskLevel());
record.setNursingAdvice(toJson(aiResult.getNursingAdvice()));
record.setRawAnswer(aiResult.getRawText());
consultationRecordMapper.insert(record);
ConsultationRecord saved = consultationRecordMapper.selectById(record.getId(), userId);
return toResponse(saved);
}统计SQL涵盖分页检索、风险分布与趋势查询:
<select id="selectPage" resultMap="consultationRecordMap">
SELECT id, user_id, nickname, question_text, image_url, preliminary_assessment,
risk_level, nursing_advice, next_step, disclaimer, raw_answer, created_at
FROM consultation_record
<where>
user_id = #{userId}
<if test="keyword != null and keyword != ''">
AND (
question_text LIKE CONCAT('%', #{keyword}, '%')
OR preliminary_assessment LIKE CONCAT('%', #{keyword}, '%')
)
</if>
</where>
ORDER BY created_at DESC
LIMIT #{size} OFFSET #{offset}
</select><select id="dailyTrend" resultType="com.medical.screening.dto.DailyTrendItem">
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') AS day, COUNT(1) AS count
FROM consultation_record
WHERE created_at >= CONCAT(#{startDay}, ' 00:00:00')
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d')
ORDER BY day ASC
</select>全局异常统一返回,前端只需按code做分支处理:
@ExceptionHandler(UnauthorizedException.class)
public ApiResponse<Void> handleUnauthorizedException(UnauthorizedException ex) {
return ApiResponse.fail(4010, ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException ex) {
return ApiResponse.fail(4001, ex.getMessage());
}API设计上,统一返回格式如下:
{
"code": 0,
"message": "ok",
"data": {},
"timestamp": "2026-02-19T11:00:00"
}鉴权接口包括注册、登录、获取当前用户与退出登录:
POST /api/auth/register
{
"username": "test_user",
"password": "Test123456",
"nickname": "测试用户"
}POST /api/auth/login
{
"username": "test_user",
"password": "Test123456"
}GET /api/auth/me
Authorization: Bearer <token>
POST /api/auth/logout
Authorization: Bearer <token>
问诊接口涵盖创建、分页、详情、统计与报告导出:
POST /api/consultations
multipart/form-data
question:必填
image:必填
nickname:选填
GET /api/consultations?page=1&size=10&keyword=咳嗽
GET /api/consultations/{id}
GET /api/consultations/statistics
GET /api/consultations/{id}/report
GET /api/consultations/{id}/report/pdf
首页与管理端接口:
GET /api/home/overview
GET /api/home/highlights
GET /api/admin/dashboard?days=14(需ADMIN)
错误码约定:4000(参数错误)、4001(业务异常)、4010(未登录/登录失效)、4030(无权限)、5000(系统异常)。
前端页面截图与替换对应表:
| 编号 | 章节位置 | 页面实际名称(建议) | 建议文件名 |
|---|---|---|---|
| S01 | 5.1.3 | 用户端-登录页 | S01-用户端-登录页.png |
| S02 | 5.1.3 | 用户端-注册页 | S02-用户端-注册页.png |
| S03 | 5.2.4 | 用户端-智能问诊页-症状输入 | S03-用户端-智能问诊页-症状输入.png |
| S04 | 5.2.4 | 用户端-智能问诊页-图片上传预览 | S04-用户端-智能问诊页-图片上传预览.png |
| S05 | 5.2.4 | 用户端-问诊结果页-风险与护理建议 | S05-用户端-问诊结果页-风险与护理建议.png |
| S06 | 5.3.3 | 用户端-问诊详情页-导出入口 | S06-用户端-问诊详情页-导出入口.png |
| S07 | 5.3.3 | 用户端-PDF报告预览页 | S07-用户端-PDF报告预览页.png |
| S08 | 5.4.3 | 管理端-运营看板页-核心指标卡片 | S08-管理端-运营看板页-核心指标卡片.png |
| S09 | 5.4.3 | 管理端-运营看板页-趋势与风险分布 | S09-管理端-运营看板页-趋势与风险分布.png |
| S10 | 7.7 | 用户端-首页概览页 | S10-用户端-首页概览页.png |
| S11 | 7.7 | 用户端-问诊记录列表页 | S11-用户端-问诊记录列表页.png |
| S12 | 7.7 | 用户端-问诊记录详情页 | S12-用户端-问诊记录详情页.png |
| S13 | 7.7 | 用户端-个人中心页 | S13-用户端-个人中心页.png |
| S14 | 7.7 | 用户端-个人统计页-风险分布图 | S14-用户端-个人统计页-风险分布图.png |
| S15 | 7.6 | 接口调试页-注册登录与问诊接口 | S15-接口调试页-注册登录与问诊接口.png |
| S16 | 7.6 | 接口返回示例页-问诊详情JSON | S16-接口返回示例页-问诊详情JSON.png |
| S17 | 8.5 | 测试验证页-Postman集合 | S17-测试验证页-Postman集合.png |
| S18 | 8.5 | 测试验证页-MySQL数据校验 | S18-测试验证页-MySQL数据校验.png |
| S19 | 9.6 | 部署架构页-前后端与MySQL | S19-部署架构页-前后端与MySQL.png |
| S20 | 9.6 | 运行验证页-后端服务日志 | S20-运行验证页-后端服务日志.png |
六、测试、部署与安全优化
测试用例设计覆盖鉴权、问诊、报告导出与管理端四大场景:
| 用例ID | 场景 | 输入 | 预期 |
|---|---|---|---|
| AUTH-01 | 注册成功 | 合法用户名密码 | 返回 token + 用户信息 |
| AUTH-02 | 重复用户名 | 同一 username 二次注册 | code=4001 |
| AUTH-03 | 密码太短 | 5位密码 | code=4001 |
| AUTH-04 | 未登录访问 | 无 Authorization | code=4010 |
| AUTH-05 | 普通用户访问管理员接口 | USER token 调用 dashboard | code=4030 |
| 用例ID | 场景 | 输入 | 预期 |
|---|---|---|---|
| CON-01 | 正常提交 | 合法图片+问题 | 创建成功并落库 |
| CON-02 | 非图片文件 | txt 文件 | code=4001 |
| CON-03 | 问题过长 | >500 字 | code=4001 |
| CON-04 | AI返回异常结构 | 模拟无JSON输出 | fallback 返回中风险建议 |
| CON-05 | 越权访问记录 | A用户访问B记录id | code=4001/记录不存在 |
| 用例ID | 场景 | 输入 | 预期 |
|---|---|---|---|
| REP-01 | TXT导出 | 合法id | 返回 fileName + content |
| REP-02 | PDF导出 | 合法id | 返回 fileName + base64 |
| REP-03 | 图片丢失 | image_url 不存在 | PDF 文字正常,图片提示跳过 |
| 用例ID | 场景 | 输入 | 预期 |
|---|---|---|---|
| ADM-01 | days 下限 | days=1 | 实际按7天 |
| ADM-02 | days 上限 | days=100 | 实际按30天 |
| ADM-03 | 趋势补零 | 某些天无记录 | dailyTrends 仍连续 |
部署环境需JDK 17、MySQL 8.x、Maven 3.8+。初始化数据库执行:
source src/main/resources/db/schema.sql;配置建议使用环境变量,不要把真实密钥写入仓库:
export DASHSCOPE_API_KEY=your_real_key
export MYSQL_HOST=localhost
export MYSQL_PORT=3306
export MYSQL_DB=medical_screening
export MYSQL_USER=root
export MYSQL_PASSWORD=xxxxxx启动方式:
mvn clean package -DskipTests
java -jar target/screening-backend-1.0.0.jar安全方面,已实现密码加盐哈希、token会话过期、USER/ADMIN角色控制、上传文件仅允许image/*、读取图片路径规范化与存在性校验、异常统一处理避免堆栈泄露。
建议继续优化:将token会话迁移至Redis支持多实例、引入JWT+刷新令牌、上传文件增加内容签名与病毒扫描、接口限流(如Bucket4j)、关键审计日志落库、补充自动化测试、AI调用增加重试超时降级与熔断、对application.yml中敏感信息彻底脱敏。
总结:本文完整拆解了基于SpringBoot+Vue的多模态疾病初筛系统,从技术选型、架构分层、数据库设计到四条核心流程与关键代码,覆盖了AI集成、文件安全、报告生成与运营统计等工程要点。对于前端开发者而言,理解后端契约与数据流是高效UI开发的前提;对于全栈团队,这套方案可直接复用并持续优化。
浙公网安备 33010602011771号