| 这个作业属于哪个课程 | 软件工程实践W班 |
|---|---|
| 这个作业要求在哪里 | 作业要求链接 |
| 这个作业的目标 | 回顾本学期软件工程实践课的学习与项目开发全过程,反思需求到发布各阶段的收获,总结个人技术成长,并结合项目思考软件开发模式的选择 |
| 其他参考文献 | 《构建之法》 |
卫生评估功能技术复盘:从小程序上传到后端转发,再到"可解释"报告
1. 技术概述
卫生评估用于宿舍卫生检查的"前后对比":用户在小程序上传打扫前/后的两张图片,后端 Spring Boot 接收后转发给评估服务,再将返回的清洁度分数与指标解析为结构化结果或可读报告供前端展示。该技术适用于需要减少人工检查主观性、提高检查效率的场景,难点集中在跨端图片上传协议、评估结果的可解释性、以及外部评估服务的可配置与稳定联调。
写这篇技术复盘时,我刻意把重点放在"工程链路"而不是"算法玄学"上:我们真正踩坑的往往不是模型/特征怎么提,而是图片到底有没有传上来、Python 服务到底能不能连上、返回字段能不能稳定渲染、出了问题能不能快速定位。
2. 需求拆解:什么叫"可用的评估"
在团队项目里,"卫生评估"如果只给一个总分,往往会遇到两类问题:
-
不可信:用户/宿舍成员不知道为什么扣分,容易引发争议
-
不可迭代:开发者无法从结果反推算法哪里出了问题(阈值?权重?噪声?)
因此我们把"可用的评估"拆成三层输出:
-
总分/核心指标 :快速告诉"好/一般/差"(例如
cleanliness_score) -
差异指标 :告诉"变化在哪里/变化有多大"(例如
change_area_ratio) -
可读结论 :给人看的解释(例如
assessment_result,或由后端生成的评估文本报告)
这三层在我们的代码里都有落点(见第 4、5 节)。
从"产品体验"的角度,我给自己定了一个验收口径:
-
用户不需要懂算法,也能理解结果(至少知道"哪里没做好/下次怎么改")
-
结果可追溯(同一组图片重复评估,输出结构一致;必要时能通过日志定位是哪一步出错)
-
跨端闭环顺滑(小程序上传---后端处理---结果回显,整个流程对用户来说是连贯的)
3. 系统架构与模块定位(基于仓库真实结构)
3.1 小程序端入口
-
页面:
guichao/guichao/pages/main/clean/clean.* -
关键展示字段:
-
details.cleanliness_score -
details.change_area_ratio -
details.assessment_result
-
3.2 后端端入口(Spring Boot)
后端卫生评估相关代码集中在包:
-
cn.edu.fzu.sosd.guicao.cv.controller-
ImageAnalysisController:POST /api/analyze(返回结构化 JSON) -
EvaluationController:POST /api/eval/upload(返回可读文本报告)
-
-
cn.edu.fzu.sosd.guicao.cv.dtoAnalysisResult、EvaluationResult、EvaluationDetails
-
cn.edu.fzu.sosd.guicao.cv.EvaluationReportGenerator- 将细分指标映射为自然语言结论
4. 端到端数据流:从两张照片到前端展示
4.1 总体流程(结构化返回链路)
Python评估服务(/evaluate) Spring Boot(/api/analyze) 微信小程序 Python评估服务(/evaluate) Spring Boot(/api/analyze) 微信小程序 选择before/after图片 上传两张图片(beforeImage, afterImage) 转换为临时文件 multipart转发两张图片 JSON结果(cleanliness_score等) 返回AnalysisResult 渲染报告表格
我在这里做 Token 透传的原因很现实:
-
评估接口很容易被滥用(图片上传本身就是资源消耗),因此从工程角度它应当是"登录后才能用"的能力。
-
即便后端一开始没有强制校验 token,前端把 token 传上去,后续加鉴权改动会小很多。
4.2 小程序页面:交互与展示
clean.wxml 显示了完整的交互闭环:
-
用户上传两张图片(打扫前/打扫后)
-
页面展示"AI卫生报告"表格
-
若没有数据则显示"暂无AI生成报告"
关键渲染字段(节选自 clean.wxml):
-
地面清洁度:
{``{details.cleanliness_score}} -
变化比例:
{``{details.change_area_ratio}} -
结论:
{``{details.assessment_result}}
这意味着后端必须返回一份结构稳定的 JSON,否则前端渲染会空/错。
我在联调时会额外关注一个细节:前端对"空数据"的判定逻辑是:
details.cleanliness_score === 0 && details.assessment_result === '' && details.change_area_ratio === 0
所以后端如果返回 null、或者字段名不匹配,页面就会一直显示"暂无AI生成报告"。这类问题看起来像"算法没跑",实际往往是"字段没对齐"。
4.3 小程序上传实现与 Token 透传
clean.js 里包含两块关键逻辑:
4.3.1 Token 透传(为鉴权预留)
js
getTokenData() {
const tokenData = wx.getStorageSync('auth_token');
if (tokenData && Date.now() < tokenData.expireTime) {
return tokenData;
}
return null;
},
getRequestHeader() {
const tokenData = this.getTokenData();
const headers = { 'Content-Type': 'application/json' };
if (tokenData) {
headers[tokenData.tokenName] = tokenData.tokenValue;
}
return headers;
}
4.3.2 两图上传触发
当 beforeImgSrc 与 afterImgSrc 都有值时触发上传:
js
if (that.data.beforeImgSrc && that.data.afterImgSrc) {
that.uploadBothImages();
}
4.4 后端:/api/analyze 的职责(协议适配 + 转发)
ImageAnalysisController 的定位非常"工程化":它不做算法,只做适配与转发,并负责临时文件清理。
关键事实:
-
接口:
POST /api/analyze -
参数:
beforeImage、afterImage(Multipart) -
转发地址:
PYTHON_SERVICE_URL = "http://localhost:5000/evaluate" -
返回:
AnalysisResult(结构化 JSON)
AnalysisResult 在 Java 中定义为:
-
cleanliness_score: double -
assessment_result: String -
change_area_ratio: double
这个 DTO 结构与小程序渲染字段一一对应。
为了让接口文档更可直接使用,我把 /api/analyze 的"工程契约"补充成更明确的形式:
4.4.1 请求协议(约定)
-
Method :
POST -
Path :
/api/analyze -
Content-Type :
multipart/form-data -
Form 字段:
-
beforeImage:打扫前图片 -
afterImage:打扫后图片
-
4.4.2 响应协议(由 DTO 推导)
AnalysisResult 在后端代码里定义了 3 个字段,因此响应 JSON 至少应包含:
json
{
"cleanliness_score": 0,
"assessment_result": "",
"change_area_ratio": 0
}
字段含义(按前端展示理解):
-
cleanliness_score:清洁度评分(数值越大越好) -
change_area_ratio:变化区域比例(用于描述前后变化幅度) -
assessment_result:评语/结论
注意:上面 JSON 是"结构示意",具体数值由 Python 服务返回。
4.5 另一条链路:/api/eval/upload 生成"解释性文本报告"
EvaluationController 走的是"Base64 + JSON"协议,并在后端使用 EvaluationReportGenerator 生成可读报告:
-
输入:
beforeImage、afterImage(Multipart) -
处理:转 Base64、拼 JSON (
before,after) 转发 Python -
输出:字符串报告(
String)
报告生成中使用的细分指标来自:
-
EvaluationResult.cleanliness_score -
EvaluationDetails.sharpness_change -
EvaluationDetails.blob_decrease -
EvaluationDetails.texture_simplified -
EvaluationDetails.color_similarity
EvaluationReportGenerator 的逻辑是典型的"阈值解释器":
-
score < 50输出"较差" -
50<=score<70输出"一般" -
>=70输出"良好"
并对每个细分指标给出建议。
这条链路的价值是:用最小成本把算法结果解释给人。即便算法复杂度提升,解释层也可以稳定输出"人能看懂"的结论。
我在项目里最终保留两条链路,并不是"写重复代码",而是因为它们各自解决不同问题:
-
/api/analyze:偏"产品化输出"(结构稳定,前端容易渲染与做数据分析) -
/api/eval/upload:偏"解释性输出"(文本更友好,适合直接给用户/宿舍长查看)
如果只保留其中一个,我更倾向保留 /api/analyze,并把解释逻辑作为可选字段附加在结构化返回里(但这需要进一步迭代)。
5. "算法可解释性"落地方式:从 DTO 到报告生成
在不直接改 Python 算法的前提下,我们仍然能在系统层面提升可解释性:
-
结构化字段:保证每次评估都有稳定字段可分析
-
解释层:将细分指标映射到自然语言,降低沟通成本
在代码层面:
-
DTO(
EvaluationDetails)提供"解释所需的最小证据" -
EvaluationReportGenerator把证据翻译成面向用户的语言
这种"解释层与算法层分离"的好处:
-
算法迭代不必推翻前端展示逻辑
-
解释逻辑可版本化、可 A/B
-
可逐步引入配置化(阈值、权重、文案)
站在"团队协作"的角度,这个分离还有一个额外价值:
-
算法同学(或 Python 服务维护者)只要保证返回字段存在且语义稳定
-
后端同学就能独立把这些字段组织成"能被用户理解"的报告
-
前端同学也能稳定渲染,不会因为算法内部重构就被迫跟着改页面
这让我在项目里越来越相信一句话:对外契约稳定,比内部实现漂亮更重要。
6. 工程化难点与踩坑:从现有代码出发做诊断
你提出"旧版本完全不可用"的反馈非常关键。下面我严格基于当前仓库代码指出可能导致不可用/不稳定的点,并给出可落地的修复路径(便于你写博客时体现"问题---分析---解决")。
6.1 踩坑 1:小程序端 Multipart 上传实现不严谨
在 clean.js 的 uploadBothImages() 中:
-
代码读取了
beforeBuffer/afterBuffer -
但
wx.request的data实际只传了formData字符串 -
并未把二进制 buffer 真正拼入请求体
这会导致典型现象:
-
后端拿不到文件
-
后端报参数为空
-
或 Python 端收到空文件
建议修复路线:
-
优先使用
wx.uploadFile(小程序更推荐) -
若必须多文件一次上传,使用可用的 multipart 工具库,确保 body 中包含二进制部分,并设置正确
Content-Type: multipart/form-data; boundary=...
在技术博客里,你可以把它写成"我如何通过抓包/日志定位上传体不完整"的排查过程。
我当时的排查路径大概是:
-
先看后端 Controller 是否进入(若没进入,多半是 URL/网关/证书问题)
-
进入后看
MultipartFile是否为空(为空就回头查前端 body) -
MultipartFile不为空,再看转发到 Python 的请求是否成功(HTTP 状态码、响应体是否能解析)
这条路径能把问题迅速定位到"前端上传/后端解析/转发调用/响应解析"四段中的哪一段。
6.3 踩坑 3:临时文件名固定,存在并发覆盖风险
ImageAnalysisController 使用固定文件名:
-
before.jpg -
after.jpg
并发请求时可能:
-
A 请求写入 before.jpg,B 请求覆盖
-
A 请求转发给 Python 的其实是 B 的图片
建议修复路线:
-
用
File.createTempFile生成唯一文件名 -
或改成内存流转发(权衡内存压力)
这一点尤其适合写进"我如何从现象推导到根因"的复盘:如果评估结果偶尔串图、偶尔离谱,除了怀疑算法,更应该先怀疑并发与共享资源。
6.4 踩坑 4:返回字段命名风格不统一导致前端取值混乱
仓库中存在两套返回结构:
-
AnalysisResult:字段名是cleanliness_score(下划线) -
EvaluationResult:用@JsonProperty("cleanliness_score")映射到cleanlinessScore(驼峰)
这类差异如果在前端混用,很容易"字段拿不到"。
建议:统一对外 JSON 的字段命名约定,并在前端只对接一种结构。
我在团队协作里会把它提升为"接口规范"层面的约束:
-
对外 JSON 统一使用下划线还是驼峰
-
DTO 与 JSON 的映射是否都用
@JsonProperty明确标注 -
前端只允许从一个"接口适配层"拿字段,避免页面里散落一堆字段兼容逻辑
7. 参数配置化与 A/B 测试:如何把"算法迭代"做成工程能力
你在描述中提到"参数配置化方案、A/B 测试环境"。目前我在仓库里还没定位到对应实现文件,因此这里先给出可直接落在现有架构上的工程方案,你提供代码位置后我可以改成"完全基于你们实现"的版本:
7.1 参数配置化
可配置项包括:
-
评分阈值(良好/一般/较差)
-
各子指标权重
-
文案模板
-
是否启用某个子项(例如颜色一致性在某些场景不可靠)
落地方式(推荐顺序):
-
application.yml+@ConfigurationProperties -
数据库表(可动态调整)
-
配置中心(若项目长期运行)
7.2 A/B 测试
目标:让新算法/新阈值在小范围验证,而不是一次性全量替换。
典型实现:
-
按宿舍/用户 hash 分流(A 组用旧阈值,B 组用新阈值)
-
同时记录两套结果,比较分布与投诉率
与你们现有系统的结合点:
-
A/B 可先做在"解释层"(
EvaluationReportGenerator的阈值与文案) -
再逐步下沉到"算法层"(Python 评分逻辑)
我补充一下我对 A/B 的工程理解:它不是为了"做科研",而是为了把算法迭代的风险可控化。
-
目标不是证明新算法更强,而是证明"新算法上线不会把系统搞崩/不会引发明显争议"。
-
只要我们能把流量限制在小范围、能回滚、能比较分布,就已经达成课程项目里最关键的工程价值。
8. 总结
卫生评估功能本质是一个跨端、跨语言、跨部署环境的系统:
-
小程序负责交互与上传
-
Spring Boot 负责协议适配、鉴权与稳定输出
-
Python 负责评估算法
从工程角度看,真正决定"可用性"的不是算法是否高级,而是:
-
上传协议是否可靠
-
依赖服务是否可配置、可部署
-
返回结构是否稳定
-
是否有解释层降低争议
如果让我用一句话概括这次实现的核心经验:
卫生评估是一个"系统工程问题",算法只是其中一段;把接口、依赖、返回结构、可解释性与排查路径做好,功能才能真正被用起来。
9. 参考资料
-
《构建之法》(邹欣)
-
微信小程序官方文档:
wx.request、wx.uploadFile -
Spring Boot 官方文档:Multipart、配置管理