软件工程实践第二次作业

Samuel099 2026-09-24 22:25:14

软件工程实践第二次作业|与 AI 结对完成 CV TrendAtlas 顶会热词分析平台

项目内容
这个作业属于哪个课程202601 福大-软件工程实践-W班
学号-姓名102400325—邱宁华
这个作业要求在哪里软件工程实践第二次作业——与 AI 结对编程(顶会热词统计)
这个作业的目标运用 NABCD 分析用户需求;使用 AI 结对完成论文采集、管理、热词分析与趋势可视化;通过 Git 版本控制和云端部署形成可验证交付
其他参考文献《构建之法》第 3、4、8 章;Google JavaScript Style Guide;Google HTML/CSS Style Guide;CVF Open Access;ECVA

目录

  • 软件工程实践第二次作业|与 AI 结对完成 CV TrendAtlas 顶会热词分析平台
  • 1. Git 仓库链接、代码规范链接和 AI 工具说明
  • 1.1 项目入口
  • 1.2 AI 编程助手与分工
  • 2. PSP 表格与时间偏差分析
  • 2.1 PSP 记录
  • 2.2 偏差反映了什么
  • 3. NABCD 需求分析
  • 3.1 N — Need:用户真正需要什么
  • 3.2 A — Approach:怎样解决问题
  • 3.3 B — Benefit:收益如何体现
  • 3.4 C — Competitors:替代方案与取舍
  • 3.5 D — Delivery:如何交付和推广
  • 4. 原型设计与原型链接
  • 4.1 工具与信息架构
  • 4.2 设计中的取舍
  • 5. 成品展示
  • 5.1 首页总览
  • 5.2 关键词图谱
  • 5.3 论文文库
  • 5.4 论文详情弹窗
  • 5.5 趋势洞察
  • 5.6 云端部署验证
  • 6. 结对(人机)讨论过程描述
  • 6.1 案例 A:从零搭建 Spring Boot 项目
  • 6.2 案例 B:爬虫 URL 与选择器的调试
  • 6.3 案例 C:服务器 JDK 版本不兼容
  • 6.4 人机协作怎样形成闭环
  • 7. 设计实现过程
  • 7.1 功能结构图
  • 7.2 项目目录
  • 7.3 数据范围与清洗
  • 7.4 关键词抽取与热度口径
  • 7.5 查询、导入与异常处理
  • 8. 关键代码说明
  • 8.1 PaperMapper 的 SQL 与 RowMapper
  • 8.2 PaperCrawlerService 的列表页解析
  • 8.3 PaperService 的去重与校验
  • 8.4 TopicExtractor 的主题规则匹配
  • 9. 测试、Git 协作与云端部署
  • 9.1 接口测试
  • 9.2 Git 过程与提交记录
  • 9.3 运行与部署说明
  • 10. 心路历程与收获
  • 11. 对 AI 结对伙伴的评价
  • 12. 扩展功能与后续改进
  • 13. 参考资料与数据来源

1. Git 仓库链接、代码规范链接和 AI 工具说明

1.1 项目入口

  • CodeArts 仓库:https://codehub-cn-south-1.devcloud.huaweicloud.com/538373125b434b55873faa4c8494ac8b/102400325.git
  • 代码规范:codestyle.md,位于仓库根目录
  • 云端访问:http://49.232.19.164:8080/
  • 健康检查:http://49.232.19.164:8080/api/papers/count

项目简述:CV TrendAtlas 面向刚接触计算机视觉的初学者,把"查找论文—阅读摘要—观察热词—比较趋势—回到原文"连接起来。项目采用 Java 17 + Spring Boot 4.1.1 + MySQL 8.0 + 原生 HTML/CSS/JavaScript + ECharts,数据来源以 CVF Open Access 与 ECVA 官方目录为主。当前云端种子文库覆盖 CVPR 2022—2025 四个会议年度,共 20 篇论文(用于演示部署效果)。

源码地址与运行地址职责不同:CodeArts 保存版本,腾讯云轻量应用服务器提供在线访问。

1.2 AI 编程助手与分工

项目说明
主要 AI 工具通义灵码(Qoder CN,DeepSeek-V4-Pro 模型)
工具版本或会话说明IDEA 插件版通义灵码,具体版本以实际会话界面为准
AI 的主要工作需求拆解、Spring Boot 项目结构、爬虫与接口初稿、统计逻辑、前端页面、错误定位、文档整理
我的主要工作提出需求、选择技术方案、组织项目结构、编写和修改代码、运行并验证程序、审查 AI 生成代码、对最终交付负责
责任边界通义灵码主要提供代码建议与问题分析,我负责理解、筛选、修改和验证。所有 AI 输出均结合实际源码、运行结果和测试结果进行判断

可解释性声明:
本项目使用了 AI 辅助生成和修改的代码,包括项目骨架、页面结构、爬虫、REST 接口、统计逻辑和前端图表配置。不能把这些内容描述为完全由我独立手写。本文按模块说明实现思路;我需要能够解释数据从哪里来、如何入库、怎样统计、哪里可能失败,以及每项修改解决了什么问题。


2. PSP 表格与时间偏差分析

2.1 PSP 记录

记录口径说明:下表为回顾估算,不是完整计时日志,仅用于讨论时间分配。

PSPPersonal Software Process Stages预估耗时/分钟实际耗时/分钟(回顾估算)
Planning计划小计4545
· Estimate· 估计任务与整理工作区4545
Development开发小计12001560
· Analysis· 需求分析、学习技术、与 AI 讨论150180
· Design Spec· 设计文档、页面与数据结构90100
· Design Review· 设计复审与交互规则检查4560
· Coding Standard· 编码规范与工程约定3040
· Design· 前端页面与交互设计180240
· Coding· Spring Boot 后端、爬虫、前端页面与图表编码420540
· Code Review· 人工与 AI 代码复审105140
· Test· 测试、修复、提交与部署验证180260
Reporting报告小计210280
· Test Report· 测试报告与功能截图6090
· Size Measurement· 工作量与提交记录统计3035
· Postmortem & Process Improvement Plan· 博客整理、复盘与改进计划120155
合计计划+开发+报告14551885

2.2 偏差反映了什么

实际投入比预估增加约 30%,主要差异集中在三处:

  1. 环境配置比预期复杂。IDEA 运行配置、JDK 版本、Maven 镜像、Lombok 注解处理器、MySQL 连接、IDEA 静态资源构建,每一步都出现过报错。尤其是"IDEA 找不到 chrome"这个报错,查了很久才定位到是运行配置问题。

  2. 真实爬虫比模拟数据复杂。CVF 列表页 URL 必须带 ?day=all 才返回论文链接,ECVA 页面结构和 CVF 不完全一致,还要处理缓存、超时、重试和摘要缺失。

  3. 云端部署比本地复杂。SSH 密码、防火墙、系统重装、JDK 版本兼容、Maven 首次编译缓慢,都需要单独排时间。

下次我会在每轮工作开始时记录时间,把"编码"、"等待网络"、"调试"、"环境配置"分开统计。


3. NABCD 需求分析

3.1 N — Need:用户真正需要什么

题目中的小刚对计算机视觉感兴趣,但不知道从哪里开始阅读。对他而言,问题不是论文不够多,而是缺少一条从总体认识走向具体阅读的路径。

核心问题可以归纳为:

  1. 近几年有哪些常见研究方向?
  2. 这些方向在不同会议和年份中有什么变化?
  3. 一个热词对应哪些真实论文?
  4. 看完热词后,下一步应该读什么?
用户使用场景需要完成的任务
计算机视觉初学者第一次了解领域从 Top 10 和主题图谱进入具体论文
课程学习者整理某一会议、某一年度的资料搜索、筛选、维护论文
研究方向探索者对比同一主题在不同会议中的表现查看年度篇数、样本占比与原文依据

3.2 A — Approach:怎样解决问题

第一步,把数据来源做实。
初始数据从 CVF Open Access 和 ECVA 官方目录采集,再访问论文详情页面获取作者、摘要及 PDF 链接。每条记录保存来源和抓取时间。爬虫没有获取到合格摘要时记录失败,不用模拟论文补齐数量。

第二步,统一论文的存储和操作。
采用 Java Spring Boot 提供 REST API,MySQL 8.0 作为持久化数据库。论文新增、修改、删除和导入都经过统一的 PaperService 校验,前端重新读取数据库后再展示结果,避免不同页面维护各自的论文副本。

第三步,让统计结果能够返回原始论文。
系统基于标题和摘要匹配明确的主题规则,按包含某主题的论文数生成榜单。用户点击关键词或趋势数据点后,进入带有对应条件的论文文库,可以继续核查摘要与原文。

第四步,把比较口径写在页面上。
趋势既支持篇数,也支持"主题匹配数/该会议该年度样本数"的占比。ICCV 偶数年和 ECCV 奇数年不被当成零热度;未举办或未收录的年份使用断点及文字说明。

第五步,以测试控制范围。
实际程序由 Spring Boot、原生 HTML/CSS/JavaScript 与 ECharts 实现。接口经过实际运行验证,数据来源可追溯到官方页面。

技术选择考虑了我的学习基础:原生前端便于直接理解 DOM、事件与数据处理,Spring Boot + MySQL 是企业级 Web 开发的主流组合。当前数据量约千篇,前端读取后筛选便于实现;如果规模继续增长,应改为后端分页、查询索引与独立统计接口。

3.3 B — Benefit:收益如何体现

用户可以沿着"方向—年份与会议—论文—官方来源"的路径完成初步探索,减少在不同页面之间手工整理信息的操作。明确的来源和统计说明帮助用户判断结果是否适合自己的问题。

这些是功能带来的预期收益,并没有经过正式用户实验验证。因此本文不使用"效率提高某个百分比"等缺乏测量依据的结论。

3.4 C — Competitors:替代方案与取舍

替代方案已有优势与本题场景的差异本项目的选择
CVF / ECVA 官方目录来源直接,原文链接可追溯按会议阅读方便,跨年度主题比较还需整理保留官方来源,增加统一主题统计与跳转
DBLP书目信息组织成熟,适合检索和文献定位摘要覆盖与本题所需的热词可视化并非同一任务作为可扩展书目检索来源,不声称替代它
Google Scholar覆盖广,检索能力强面向广泛学术搜索,难以直接控制本课程样本口径聚焦三大顶会与可解释样本分析
表格工具+手工整理自由度高,上手直接重复导入、去重、主题关联与更新需要人工维护将常用流程封装到一个可操作网页中

本项目的优势是范围清晰、统计规则公开、分析结果能够回到论文,不是数据覆盖量超过成熟学术平台。它也存在不足:样本有限、词典规则需要维护、没有引文网络分析、尚未开展正式用户研究。

本项目的技术栈选择 Java 17 + Spring Boot + MySQL,属于企业级 Web 开发的主流组合,便于后续扩展和部署。前端采用原生 HTML/CSS/JavaScript + ECharts,避免引入复杂构建工具,降低课程作业的部署难度。

3.5 D — Delivery:如何交付和推广

通过 CodeArts 交付源代码与版本记录,通过腾讯云服务器提供在线访问入口,通过博客集中呈现功能、实现、测试和人机协作。演示时优先使用一条完整路径:在首页选择某个主题,查看相关论文,进入趋势比较,再回到指定会议年度的论文列表。


4. 原型设计与原型链接

4.1 工具与信息架构

在这里插入图片描述

我将主要页面组织为:研究总览、论文文库、趋势洞察。论文详情使用弹窗承载,让用户查看摘要后仍能回到原来的列表。首页负责提供方向入口,列表负责精细检索,趋势页负责比较。

页面或状态主要内容关键交互规则
研究总览统计卡片、Top 10、主题气泡点击主题进入该主题论文
论文文库搜索、筛选、详情弹窗精确查询匹配完整题目;模糊查询覆盖题目、作者、关键词
论文详情作者、摘要、主题、原文与来源弹窗关闭后保留列表;外部链接另开页面
趋势洞察主题、会议、年度走势、明细表图表与明细采用一致条件

4.2 设计中的取舍

我希望界面先回答"看什么",再回答"怎么筛选",所以首页不堆放全部输入控件。浅色背景配合一个紫色主色突出关键操作,图表同时保留文字标签,避免只靠颜色传递信息。

原型阶段的人机讨论重点是信息层级、操作反馈和页面联动。AI 给出布局候选,我需要检查是否符合小刚的实际使用路径。


5. 成品展示

以下截图来自实际运行的项目,部署在腾讯云轻量应用服务器上,公网地址 http://49.232.19.164:8080/%E3%80%82

5.1 首页总览

在这里插入图片描述

首页汇总论文总数、会议数、年份范围、热门主题数,并展示 Top 10 主题柱状图和关键词图谱。当前服务器数据库包含 20 篇 CVPR 论文,覆盖 2022—2025 年。

5.2 关键词图谱

在这里插入图片描述

ECharts 力导向图展示主题节点和共现关系,节点大小表示该主题的论文数,连线粗细表示共现次数。点击节点可跳转到论文文库并自动筛选。

5.3 论文文库

在这里插入图片描述

论文文库支持按题目、作者、关键词模糊搜索,支持按会议和年份筛选。

5.4 论文详情弹窗

在这里插入图片描述

详情显示作者、原文摘要、主题标签、来源,并提供官方页面和 PDF 链接。

5.5 趋势洞察

在这里插入图片描述

展示指定主题在 CVPR、ICCV、ECCV 各年度的论文数变化。因为服务器数据目前只有 CVPR,图上仅显示 CVPR 一条线。

5.6 云端部署验证

在这里插入图片描述

服务器上 java -jar 启动 Spring Boot,Tomcat 监听 8080 端口,HikariPool 成功连接 MySQL。


6. 结对(人机)讨论过程描述

说明:以下按需求与原型、编码、调试三个案例整理。提示词为便于复盘而重述的完整版本,不冒充逐字聊天记录。

6.1 案例 A:从零搭建 Spring Boot 项目

第一轮提示词:

我要用 Java + Spring Boot + MySQL 完成一个论文热词分析平台,
需要论文采集、增删改查、Top 10、关键词图谱、三大顶会跨年度趋势。
请先拆解 NABCD 和页面信息架构,再给出 Spring Boot 项目骨架。

AI 输出摘要: AI 拆解了 NABCD,给出 3 个页面的信息架构建议,并生成了 Spring Boot 4.1.1 的 pom.xml 和基本目录结构。

我的追问与调整:

我已有 Java 和数据库基础,但 Spring Boot 和云部署还不熟练。
请压缩技术范围,只保留 Spring Boot + MySQL + 原生 HTML/CSS/JS + ECharts,
不要引入 Spring Data JPA,改用 JdbcTemplate。

采纳与调整:

  • 保留 Spring Boot + MySQL + JdbcTemplate 的技术栈
  • 采纳 AI 建议的三页面结构(研究总览、论文文库、趋势洞察)
  • 手动修正了 Maven 依赖版本和 Maven 镜像仓库地址(阿里云镜像)

没有采用的方向:

  • AI 建议使用 Spring Data JPA 简化数据库操作,但 JdbcTemplate 更可控、SQL 更直观
  • AI 建议引入 Vue 或 React,但原生前端更适合本次作业

AI 对话截图:

在这里插入图片描述

6.2 案例 B:爬虫 URL 与选择器的调试

问题现象: 调用 /api/papers/crawl-test 返回:

{"added":0,"skipped":0,"failed":0}

第一轮提示词:

爬虫返回 0 篇论文,检查一下 URL 和选择器是否正确。
目标页面:https://openaccess.thecvf.com/CVPR2024

AI 分析: 指出 CVF 列表页需要带 ?day=all 参数才返回论文列表,且列表项是 <dt class="ptitle"><a href="..."> 结构。

我的验证: 在 PowerShell 里执行

curl.exe -s "https://openaccess.thecvf.com/CVPR2024?day=all" | Select-String "content/CVPR2024/html"

返回大量论文链接,证实 AI 的判断。

第二轮提示词:

确认了 URL 是 CVPR2024?day=all。
请把 PaperCrawlerService 里的列表页选择器
从 a[href] 改成 dt.ptitle > a[href],
并且保证 href 里含 /html/ 且以 .html 结尾。

最终修改:

for (Element a : doc.select("dt.ptitle > a[href]")) {
    String href = a.attr("href");
    String lowerHref = href.toLowerCase();
    if (lowerHref.contains("/html/") && lowerHref.endsWith(".html")) {
        detailUrls.add(resolve(listingUrl, href));
    }
}

验证结果: 重新运行后返回

{"added":5,"skipped":0,"failed":0}

成功抓取 5 篇 CVPR 2024 论文入库。

AI 对话截图:

在这里插入图片描述

6.3 案例 C:服务器 JDK 版本不兼容

问题依据: 在腾讯云服务器上 mvn clean package 报错:

[ERROR] Fatal error compiling: error: release version 25 not supported

第一轮提示词:

Maven 编译报错 release version 25 not supported,
pom.xml 里 java.version 是 25,
服务器上装的是 JDK 17,怎么解决?

AI 建议: 两个方案。

  • 方案一:服务器上安装 JDK 25
  • 方案二:把 pom.xml 的 java.version 改成 17

我的选择: 方案二。JDK 25 在 Ubuntu 22.04 上安装要额外配置源,不如用系统自带的 JDK 17 稳定。

执行的修改:

sed -i 's|<java.version>25</java.version>|<java.version>17</java.version>|' pom.xml

验证结果: 重新编译后 BUILD SUCCESS,jar 成功生成在 target/CV_TrendAtlas-0.0.1-SNAPSHOT.jar。

没有采用的简化方式: 不把 mvn 编译命令搞成"跳过编译直接下载 jar",也不把本地打包好的 jar 直接传上去(因为后续可能需要服务器重新编译)。

AI 对话截图:

在这里插入图片描述

6.4 人机协作怎样形成闭环

这三个案例共同采用"给出上下文—缩小问题—提出候选方案—检查代码—验证结果"的方式。相比一次让 AI 生成整个项目,分轮讨论更容易明确某段代码到底解决什么问题,也更容易拒绝与现有数据或需求不一致的建议。

协作记录的价值不在于展示很多聊天,而在于保留关键判断:我为什么追问,哪条建议被修改,依据是什么,验证是否支持结论。


7. 设计实现过程

7.1 功能结构图

在这里插入图片描述

系统由三层组成:

  • 数据采集层:PaperCrawlerService 从 CVF/ECVA 官方目录抓取论文列表和详情页,TopicExtractor 基于标题+摘要的主题规则抽取标签。
  • 后端服务层:PaperController 提供 REST 接口,PaperService 负责业务校验和去重,PaperMapper 用 JdbcTemplate 操作 MySQL。
  • 前端展示层:index.html(研究总览)、paper.html(论文文库)、trends.html(趋势洞察),用原生 fetch 调接口,用 ECharts 画图。

7.2 项目目录

CV_TrendAtlas/
├── src/main/java/com/example/cv_trendatlas/
│   ├── controller/
│   │   └── PaperController.java
│   ├── entity/
│   │   └── Paper.java
│   ├── mapper/
│   │   └── PaperMapper.java
│   ├── service/
│   │   ├── PaperService.java
│   │   ├── PaperCrawlerService.java
│   │   └── TopicExtractor.java
│   └── CvTrendAtlasApplication.java
├── src/main/resources/
│   ├── static/
│   │   ├── index.html
│   │   ├── paper.html
│   │   └── trends.html
│   └── application.properties
├── pom.xml
├── README.md
└── codestyle.md

7.3 数据范围与清洗

会议年份已收录篇数(云端)
CVPR20225
CVPR20235
CVPR20245
CVPR20255
合计4 个会议年度20

云端部署时使用 crawl-batch?limitPerEdition=5 抓取小批量数据用于演示。本地开发环境的数据量更大。

清洗包括 HTML 标签移除、实体解码、空白归一化、相对链接补全、题目规范化去重、会议和年份校验。数据库以标准化题目建立唯一约束(title_key),避免重复题目重复入库;这一方法简单明确,但也可能把极少数同名论文合并。

7.4 关键词抽取与热度口径

当前采用可解释的主题规则,未使用 TF-IDF、TextRank 或大模型语义分类。算法把标题和摘要拼接后转为小写,匹配明确主题规则;未匹配的论文标为 other vision research,该类不参加热门方向榜单。

对主题 k、会议 v、年份 y,定义:

主题篇数 N(k, v, y)
= 同时属于会议 v、年份 y,且主题集合包含 k 的论文数量

样本占比 S(k, v, y)
= N(k, v, y) / 该会议该年度已收录论文总数 × 100%

每篇论文在同一主题下只计一次,不因摘要中重复出现该词而增加权重;一篇论文可以命中多个主题。图谱展示的是主题节点和共现连线,共现统计方式是:若两篇论文的 keywords 都包含 A、B,则 A-B 连线计数加 1。

7.5 查询、导入与异常处理

精确查询采用忽略大小写的完整题目匹配;模糊查询将题目、作者、关键词、摘要组成文本进行包含匹配。会议、年份、主题筛选在同一条过滤链上组合。

JSON/CSV 导入先逐条验证,再在事务中写入;空题目、不合法会议、错误年度与危险 URL 协议会被拒绝。当前系统适合课程演示,尚不是多用户生产服务。


8. 关键代码说明

8.1 PaperMapper 的 SQL 与 RowMapper

@Repository
public class PaperMapper {

    private static final String SELECT_COLUMNS =
            "id, title, title_key, authors, venue, year, `abstract`, keywords, url, pdf_url, source, fetched_at";

    private final JdbcTemplate jdbcTemplate;

    public PaperMapper(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    private static final RowMapper<Paper> ROW_MAPPER = (rs, rowNum) -> {
        Paper paper = new Paper();
        paper.setId(rs.getString("id"));
        paper.setTitle(rs.getString("title"));
        paper.setTitleKey(rs.getString("title_key"));
        paper.setAuthors(rs.getString("authors"));
        paper.setVenue(rs.getString("venue"));
        paper.setYear(rs.getObject("year", Integer.class));
        paper.setAbstractText(rs.getString("abstract"));
        paper.setKeywords(rs.getString("keywords"));
        paper.setUrl(rs.getString("url"));
        paper.setPdfUrl(rs.getString("pdf_url"));
        paper.setSource(rs.getString("source"));
        Timestamp fetchedAt = rs.getTimestamp("fetched_at");
        paper.setFetchedAt(fetchedAt != null ? fetchedAt.toLocalDateTime() : null);
        return paper;
    };

    public int insert(Paper paper) {
        String sql = "INSERT INTO papers (" + SELECT_COLUMNS + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        return jdbcTemplate.update(sql,
                paper.getId(), paper.getTitle(), paper.getTitleKey(),
                paper.getAuthors(), paper.getVenue(), paper.getYear(),
                paper.getAbstractText(), paper.getKeywords(),
                paper.getUrl(), paper.getPdfUrl(),
                paper.getSource(), paper.getFetchedAt());
    }

    public Optional<Paper> findByTitleKey(String titleKey) {
        String sql = "SELECT " + SELECT_COLUMNS + " FROM papers WHERE title_key = ?";
        return jdbcTemplate.query(sql, ROW_MAPPER, titleKey).stream().findFirst();
    }
}

设计思路: 用 SELECT_COLUMNS 常量避免重复列名;abstract 是 MySQL 关键字,SQL 中用反引号 `abstract` 引用;RowMapper 集中处理 snake_case 到 camelCase 的映射。SQL 一律使用参数化查询,防止注入。

8.2 PaperCrawlerService 的列表页解析

public List<Paper> crawlFromListing(String listingUrl, String venue, int year, int limit) {
    Document doc = Jsoup.connect(listingUrl)
            .userAgent(USER_AGENT)
            .timeout(TIMEOUT_MILLIS)
            .get();

    Set<String> detailUrls = new LinkedHashSet<>();
    // CVF 列表页结构:<dt class="ptitle"><a href="/content/CVPR2024/html/xxx.html">
    for (Element a : doc.select("dt.ptitle > a[href]")) {
        String href = a.attr("href");
        String lowerHref = href.toLowerCase();
        if (lowerHref.contains("/html/") && lowerHref.endsWith(".html")) {
            detailUrls.add(resolve(listingUrl, href));
        }
    }

    log.info("列表页解析出 {} 条详情链接: {}", detailUrls.size(), listingUrl);

    List<Paper> papers = new ArrayList<>();
    for (String detailUrl : detailUrls) {
        if (papers.size() >= limit) break;
        try {
            papers.add(crawlDetail(detailUrl, venue, year));
        } catch (Exception e) {
            log.warn("抓取详情页失败: {}", detailUrl, e);
        }
    }
    return papers;
}

设计思路: 先用 dt.ptitle > a[href] 精确选择器抓取列表链接,LinkedHashSet 去重;逐条访问详情页,单个失败只 log.warn 不中断,保证批量抓取的鲁棒性。

8.3 PaperService 的去重与校验

public Paper save(Paper paper) {
    validate(paper);
    String titleKey = buildTitleKey(paper.getTitle());
    paper.setTitleKey(titleKey);
    if (paperMapper.findByTitleKey(titleKey).isPresent()) {
        throw new IllegalStateException("论文已存在,titleKey: " + titleKey);
    }
    if (paper.getId() == null || paper.getId().isBlank()) {
        paper.setId(UUID.randomUUID().toString().replace("-", ""));
    }
    if (paper.getFetchedAt() == null) {
        paper.setFetchedAt(LocalDateTime.now());
    }
    if (paper.getKeywords() == null || paper.getKeywords().isBlank()) {
        List<String> topics = topicExtractor.extractTopics(
                paper.getTitle(),
                paper.getAbstractText() == null ? "" : paper.getAbstractText());
        paper.setKeywords(String.join(",", topics));
    }
    paperMapper.insert(paper);
    return paper;
}

private String buildTitleKey(String title) {
    return title.trim().toLowerCase().replaceAll("\\s+", " ");
}

设计思路: titleKey 是规范化后的题目(去空格、转小写),数据库有 UNIQUE 约束,防止重复入库。keywords 为空时自动调用 TopicExtractor 抽取主题。

8.4 TopicExtractor 的主题规则匹配

@Component
public class TopicExtractor {

    private static final Map<String, Pattern> TOPIC_RULES = new LinkedHashMap<>();

    static {
        TOPIC_RULES.put("diffusion", Pattern.compile("diffusion", Pattern.CASE_INSENSITIVE));
        TOPIC_RULES.put("vision-language", Pattern.compile("vision.language|visual.language", Pattern.CASE_INSENSITIVE));
        TOPIC_RULES.put("transformer", Pattern.compile("transformer", Pattern.CASE_INSENSITIVE));
        TOPIC_RULES.put("segmentation", Pattern.compile("segment", Pattern.CASE_INSENSITIVE));
        TOPIC_RULES.put("3d vision", Pattern.compile("\\b3d\\b|point cloud", Pattern.CASE_INSENSITIVE));
        TOPIC_RULES.put("image restoration", Pattern.compile("denois|deblur|super.resolution", Pattern.CASE_INSENSITIVE));
        // ... 其他规则
    }

    public List<String> extractTopics(String title, String abstractText) {
        String text = (title + " " + abstractText).toLowerCase();
        List<String> topics = new ArrayList<>();
        for (Map.Entry<String, Pattern> e : TOPIC_RULES.entrySet()) {
            if (e.getValue().matcher(text).find()) {
                topics.add(e.getKey());
            }
        }
        return topics.isEmpty() ? List.of("other vision research") : topics;
    }
}

设计思路: 规则用 Pattern 预编译,LinkedHashMap 保证主题顺序稳定;没匹配任何主题的论文归入 other vision research,不参与 Top 10 统计。


9. 测试、Git 协作与云端部署

9.1 接口测试

本地开发阶段通过 PowerShell 的 curl.exe 和浏览器对接口做了验证:

验证内容接口结果
论文总数GET /api/papers/count返回 {"count":20}
论文列表GET /api/papers返回 JSON 数组
Top 10GET /api/papers/top10返回 10 个主题及数量
关键词图谱GET /api/papers/topic-graph返回 nodes 和 links
趋势走势GET /api/papers/trend?topic=diffusion返回各会议年度数据
批量抓取POST /api/papers/crawl-batch返回新增/跳过/失败统计
主题抽取POST /api/papers/extract-topics返回更新条数

9.2 Git 过程与提交记录

本地 dev 分支共有 10 次提交,master 分支通过 Fast-forward 合并了 dev 的全部提交,并发布了 tag 1.0.0。

提交主要变化
a10eab2添加 pom.xml 和项目基础配置
f0200bd添加 PaperService 业务逻辑层
074b555完成趋势洞察功能
6807c50完成论文文库页面 paper.html
a53ab36添加论文实体类并完善前后端功能

在这里插入图片描述

分支和发布状态:

  • master 和 dev 都已推送到 CodeArts 远程仓库
  • tag 1.0.0 已创建并推送,可在 CodeArts 的标签页查看

在这里插入图片描述

9.3 运行与部署说明

本地运行:

git clone https://codehub-cn-south-1.devcloud.huaweicloud.com/538373125b434b55873faa4c8494ac8b/102400325.git
cd 102400325
mvn clean package -DskipTests
java -jar target/CV_TrendAtlas-0.0.1-SNAPSHOT.jar

浏览器访问 http://localhost:8080/。

云端部署(腾讯云轻量应用服务器,Ubuntu 22.04):

# 1. 安装环境
apt update
apt install openjdk-17-jdk mysql-server git maven -y

# 2. 配置 MySQL
mysql -u root -p
# CREATE DATABASE cv_trendatlas ...;
# CREATE TABLE papers (...);

# 3. 克隆代码
cd /root && git clone <仓库 HTTPS 地址> app && cd app

# 4. 修改配置
# 编辑 src/main/resources/application.properties 里的数据库密码
# 修改 pom.xml 的 java.version 为 17

# 5. 编译并运行
mvn clean package -DskipTests
nohup java -jar target/CV_TrendAtlas-0.0.1-SNAPSHOT.jar --server.port=8080 > app.log 2>&1 &

实际云端入口: http://49.232.19.164:8080/

说明: 因华为云 ECS 按需计费成本较高,本项目实际部署在腾讯云轻量应用服务器上。项目代码本身平台无关,部署流程与华为云 ECS 完全相同:安装 JDK + MySQL、克隆代码、mvn package、nohup java -jar 后台运行。

在这里插入图片描述


10. 心路历程与收获

这次作业从零开始搭建一个完整的 Spring Boot Web 项目,过程比预想的复杂。

一开始我以为"写个网页展示论文统计"很简单,但真正做起来才发现,光是让项目跑起来就踩了不少坑:JDK 版本、Maven 镜像、Lombok 注解处理器、MySQL 连接配置、IDEA 的运行配置……每一步都可能卡住。尤其是有一次 IDEA 一直弹出"找不到 chrome"的报错,查了半天才发现是运行配置绑定了错误的浏览器路径,而不是代码问题。

数据这一块也让我重新认识了"持久化"。最初我把数据写在前端 JS 里,后来发现刷新就没了;改用数据库后,又要处理连接池、字符编码、唯一约束这些细节。

AI 在这个过程中帮了很多忙,但也不是万能的。比如爬虫代码,AI 一开始给我的 URL 是 CVPR2024,实际验证后发现必须是 CVPR2024?day=all 才返回论文列表。再比如 Java 25 在服务器上不兼容,AI 提出了两种方案,我需要自己判断哪种更稳。这让我意识到:AI 能给出方案,但判断方案对不对、能不能用,还是得靠自己动手验证。

部署到云服务器的过程也是一波三折。SSH 密码、防火墙、系统重装、JDK 版本、Maven 编译……每解决一个问题,都让我更理解"从代码到能访问"这件事有多少环节。最后看到浏览器能打开 http://49.232.19.164:8080/,看到首页的统计卡片显示数字,那一刻挺有成就感的。

最大的收获不是学会了某个框架,而是养成了一种工作习惯:写代码之前先讲清需求,写完代码一定要实际运行验证,遇到问题先看日志再下结论。


11. 对 AI 结对伙伴的评价

结合《构建之法》第 4 章对结对协作的讨论,我把人机结对理解为需要不断切换"提出方案"和"审查方案"的过程。AI 可以承担代码初稿与备选思路,我必须检查它是否符合任务。

维度人人结对人机结对中的体会
反馈与分工双方可交流背景、承担协作任务AI 反馈快,但需要主动补充上下文和约束
审查作用同伴可依据经验发现问题AI 擅长列出检查方向,但建议需要运行与资料验证
知识边界能通过讨论表达不熟悉的领域AI 可能以确定语气给出不适用的 API 或部署建议
责任与理解团队成员共同承担工程责任AI 不替我承担提交责任;最终仍需我理解、核实和交付
过程证据任务分工、评审和提交可追踪还应保留 Prompt、追问、采纳理由和修复结果

贡献:

  • 快速生成 Spring Boot 项目骨架和 REST 接口
  • 爬虫代码和 Jsoup 选择器建议
  • 数据库操作代码(JdbcTemplate + RowMapper)
  • 前端页面 HTML/CSS/JS 和 ECharts 图表配置
  • 遇到报错时快速给出排查方向

局限:

  • 数据/API 可能过时:例如 CVF 列表页 URL 的实际格式,AI 一开始给的 URL 是错的
  • 环境假设偏差:AI 默认本地是 JDK 25,但服务器是 JDK 17,需要手动调整
  • 不主动验证:AI 只能给出代码,不能替你运行和调试
  • 文档表述可能夸大:AI 有时会把"存在"写成"完成",需要自己核对

总体评价:

它是一位高效的候选方案提供者和辅助审查者,但不是最终验收人。今后我会继续使用 AI,同时坚持让每个重要结论能够落到可检查的文件、数据或运行结果上。


12. 扩展功能与后续改进

本项目已完成作业要求的 5 个基础功能。后续可扩展的方向:

  1. 采集与导入页面:提供单篇/批量题目输入、JSON/CSV 导入预览
  2. 了解更多页面:介绍三大顶会背景和统计口径
  3. 年度热词演变:展示历年 Top 10 榜单的演变
  4. 数据导出:把论文列表导出为 CSV/JSON
  5. 分页与后端统计:数据量增长后,把前端统计改为后端 SQL 聚合
  6. 主题规则的维护:建立小规模人工标注数据集,评估规则的误匹配和遗漏

13. 参考资料与数据来源

  1. 《构建之法》第 3、4、8 章:个人过程、结对协作与需求分析。
  2. 本次作业要求。
  3. CVF Open Access:CVPR、ICCV 官方论文目录与详情。
  4. ECVA Papers:ECCV 开放论文目录与详情。
  5. Google JavaScript Style Guide、Google HTML/CSS Style Guide。
  6. Spring Boot 官方文档。
  7. Jsoup 官方文档。
  8. ECharts 官方文档。

...全文
51 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

101

社区成员

发帖
与我相关
我的任务
社区描述
202601福大-软件工程实践-W班
软件工程 高校 福建省·福州市
社区管理员
  • 202601福大-软件工程实践-W班
  • 李之尹
  • 123
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧