结对作业二
这个作业属于哪个课程 | 2021春软件工程实践|S班 (福州大学) |
---|---|
这个作业要求在哪里 | 结对作业二/结对作业二 |
结对学号 | 221801330陈少彬、221801332李达明 |
这个作业的目标 | 对上一次结对作业的功能进行实现 |
其他参考文献 | 博客园、CSDN |
git仓库链接和代码规范链接
git仓库链接
https://github.com/Weirdo341/PairProject
代码规范链接
https://github.com/Weirdo341/PairProject/blob/main/221801330%26221801332/codestyle.md
技术和框架
- 前端:bootstrap、bootstrap-table框架以及ECharts。
- 后端:springboot框架
PSP表格
221801332的PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
•Estimate | 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 2170 | 2500 |
•Analysis | 需求分析 (包括学习新技术) | 600 | 650 |
•Design Spec | 生成设计文档 | 40 | 60 |
•Design Review | 设计复审 | 20 | 20 |
•Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 40 | 50 |
•Design | 具体设计 | 20 | 40 |
•Coding | 具体编码 | 1100 | 1200 |
•Code Review | 代码复审 | 50 | 80 |
•Test | 测试(自我测试,修改代码,提交修改) | 300 | 400 |
Report | 报告 | 60 | 50 |
•Test Report | 测试报告 | 30 | 30 |
•Size Measurement | 计算工作量 | 10 | 10 |
•Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 10 |
合计 | 2250 | 2570 |
221801330的PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
•Estimate | 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 2170 | 1870 |
•Analysis | 需求分析 (包括学习新技术) | 540 | 500 |
•Design Spec | 生成设计文档 | 30 | 30 |
•Design Review | 设计复审 | 30 | 30 |
•Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
•Design | 具体设计 | 30 | 40 |
•Coding | 具体编码 | 1200 | 1000 |
•Code Review | 代码复审 | 60 | 50 |
•Test | 测试(自我测试,修改代码,提交修改) | 250 | 200 |
Report | 报告 | 60 | 60 |
•Test Report | 测试报告 | 30 | 30 |
•Size Measurement | 计算工作量 | 10 | 10 |
•Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 2250 | 1950 |
云访问链接
http://112.74.177.186:8080/paperlist.html
(首页加载很慢,需要三五分钟,请提前耐心等待)
成品展示
主页面,展示论文列表,对论文列表进行分页,右上角搜索框可对论文信息进行模糊搜索
论文的搜索
当鼠标停留时显示论文列表的完整信息
可通过多选框进行多选删除,左上角为本页面的列全选
点击删除按钮可删除列表中的论文信息,不会对后端数据产生影响
(此处截图看不出明显效果,暂不放截图)
右下角可选择每页列数
左上角为各个页面的导航,点击可跳转到相应页面
点击左上角跳转到top10热词
点击表格能跳转到首页展示该行热词相关论文同时搜索框默认值为Computer vision(这里点击的是Computer vision)
热词走势是根据十大热词在14年到20年的热度数据制作
可对走势折线进行筛选
点击右上角的保存可将走势图保存为本地图片
结对讨论过程描述
过程
拿到题目后,经过讨论,最开始是打算纯前端的编码的(因为后端springboot框架都不熟悉),我们决定使用bootstrap开发框架来进行设计,完成了论文列表的设计之后觉得时间还充裕,便也开始后端技术的学习和编写。约定了两个人的代码规范之后就开始了页面的设计。页面需要有如下几部分:首页、论文列表、top10列表、热词折线图、十大热词列表。然后我们经过一段时间设计后发现,可以将全部论文列表直接置于首页,然后在首页加入实时搜索框以便进行筛选,然后点击相关的十大热词能显示响应关键词的相关论文,这个我们也决定将其与首页结合,即点击关键词跳到首页然后全部论文列表变为相应的论文列表。
截图
(以下沙雕表情包请自动忽略)
设计实现过程
前端页面设计实现:
- 论文列表页面:通过bootstrap官方文档以及网上的资源学习其使用,以列表的形式对论文信息进行显示。论文列表的加载是通过js语句实现的,通过bootstrap-table自带的表格加载功能实现论文数据的展示。论文数据的获取通过后端发送的数据获取,通过getTable接口接收论文数据。
- 热词排行:通过hashmap统计每个热词的频数,用list排序获取前十个热词。
- 热词走势:根据所获取的前十个热词,根据年份信息统计该热词每年的频数。
后端设计实现:
- 数据库设计:用一个数据表存储论文信息,根据当前所有的论文数据对数据表进行填充。
截图
代码说明
布局(bootstrap的栅栏系统)
- 左上角下拉菜单栏设计:
<div class="dropdown">
<button type="button" class="btn dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown">首页
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li role="presentation">
<a role="menuitem" tabindex="-1" href="paperlist.html">首页</a>
</li>
<li role="presentation" class="divider"></li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="index1.html">Top10 热门领域排行榜</a>
</li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="index2.html">热词走势对比</a>
</li>
</ul>
- top10表格设计
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<table class="table">
<caption>Top10 热门领域排行榜</caption>
<thead>
<tr>
<th>排名</th>
<th>关键词</th>
<th>搜索热度</th>
</tr>
</thead>
<tbody>
<tr onClick="window.location.href='paperlist.html?tj_type=Cameras'">
<th scope="row">1</th>
<td id="keyword1">热词1</td>
<td id="search_heat1"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Training'">
<th scope="row">2</th>
<td id="keyword2">热词2</td>
<td id="search_heat2"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Computer vision'">
<th scope="row">3</th>
<td id="keyword3">热词3</td>
<td id="search_heat3"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Feature extraction'">
<th scope="row">4</th>
<td id="keyword4">热词4</td>
<td id="search_heat4"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Shape'">
<th scope="row">5</th>
<td id="keyword5">热词5</td>
<td id="search_heat5"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Image segmentation'">
<th scope="row">6</th>
<td id="keyword6">热词6</td>
<td id="search_heat6"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Robustness'">
<th scope="row">7</th>
<td id="keyword7">热词7</td>
<td id="search_heat7"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Visualization'">
<th scope="row">8</th>
<td id="keyword8">热词8</td>
<td id="search_heat8"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Three-dimensional displays'">
<th scope="row">9</th>
<td id="keyword9">热词9</td>
<td id="search_heat9"></td>
</tr>
<tr onClick="window.location.href='paperlist.html?tj_type=Image reconstruction'">
<th scope="row">10</th>
<td id="keyword10">热词10</td>
<td id="search_heat10"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
- 热词走势图设计
<script type="text/javascript">
var dom = document.getElementById("container");
var myChart = echarts.init(dom);
var app = {};
var option;
option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['热词1','热词2','热词3','热词4','热词5','热词6','热词7','热词8','热词9','热词10']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2014年', '2015年', '2016年', '2017年', '2018年', '2019年', '2020年']
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Cameras',
type: 'line',
stack: '热词1',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Training',
type: 'line',
stack: '热词2',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Computer vision',
type: 'line',
stack: '热词3',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Feature extraction',
type: 'line',
stack: '热词4',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Shape',
type: 'line',
stack: '热词5',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Image segmentation',
type: 'line',
stack: '热词6',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Robustness',
type: 'line',
stack: '热词7',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Visualization',
type: 'line',
stack: '热词8',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Three-dimensional displays',
type: 'line',
stack: '热词9',
data: ['0','0','0','0','0','0','0']
},
{
name: 'Image reconstruction',
type: 'line',
stack: '热词10',
data: ['0','0','0','0','0','0','0']
},
]
};
- 论文列表加载
$('#myTable').bootstrapTable({
method: 'get',
url: "getTable", // 请求路径
striped: true, // 是否显示行间隔色
pageNumber: 1, // 初始化加载第一页
pagination: true, // 是否分页
sidePagination: 'client', // server:服务器端分页|client:前端分页
sortable: true,
sortName:'论文名称',
sortOrder: 'desc',
pageSize: 10, // 单页记录数
pageList: [5, 20, 30],
search: true,
paginationLoop: true,
paginationHAlign: 'left',
paginationDetailHAlign: 'right',
paginationPreText: '上一页',
paginationNextText: '下一页',
// showRefresh : true,// 刷新按钮
queryParams: function(params) { // 上传服务器的参数
var temp = {
name: 1
};
return temp;
},
columns: [{
checkbox: true
}, {
title: '论文名称', //表头
field: 'title', //数据源
sort: true,
searchable:true,
formatter:paramsMatter, //paramsMatter为显示表格信息的一个函数
}, {
title: '原文链接',
field: 'url',
searchable:false,
formatter:paramsMatter,
}, {
title: '会议',
field: 'meeting',
searchable:true,
},{
title: '年份',
field: 'year',
searchable:true,
}, {
title: '摘要',
field: 'paperAbstract',
searchable:false,
formatter:paramsMatter,
}, {
title: '关键词',
field: 'keyWords',
searchable:true,
formatter:paramsMatter,
}
]
});
后端代码
- 热词走势获取
public ArrayList<KeyWord> getKwdFrequency(HashMap<String, Integer> topTenWords){
String word;
ArrayList<KeyWord> keyWords = new ArrayList<>();
for(HashMap.Entry<String, Integer> entry : topTenWords.entrySet()) {
HashMap<String, Integer> wordsFrequency = new HashMap<>();
word = entry.getKey();
Paper paper = new Paper(word);
//根据前10关键词获取论文集合
ArrayList<Paper> papers = getYearByKwd(paper);
//根据论文的年份计算该年热词频数
for (int i = 0; i < papers.size() ; i++){
paper = papers.get(i);
if (wordsFrequency.containsKey(paper.getYear())) {
//key存在
Integer value = wordsFrequency.get(paper.getYear());
value++;
wordsFrequency.put(paper.getYear(), value);
//System.out.println(s);
} else {
//key不存在
wordsFrequency.put(paper.getYear(), 1);
}
}
//遍历HashMap,给Keyword对象赋值存到队列中
for (HashMap.Entry<String, Integer> haEntry : wordsFrequency.entrySet()
) {
KeyWord keyWord = new KeyWord();
keyWord.setFrequency(haEntry.getValue());
keyWord.setWord(word);
keyWord.setYear(haEntry.getKey());
keyWords.add(keyWord);
System.out.println(keyWord.getWord() + " " + keyWord.getYear() + " " + keyWord.getFrequency());
}
}
return keyWords;
}
- 热词获取
public HashMap<String, Integer> sortMap( HashMap<String, Integer> map){
List<Map.Entry<String, Integer>> list = new ArrayList<>();
HashMap<String, Integer> topTenWords = new HashMap<>();
Integer value ;
String keyWord;
//将map放入list中
for (Map.Entry<String, Integer> entry : map.entrySet()){
list.add(entry);
}
list.sort(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue() - o1.getValue(); //从大到小排列
}
});
//将列表前十个存到hashmap
for (int i = 0 ; i < 10 ; i ++ ){
value = list.get(i).getValue();
keyWord = list.get(i).getKey();
System.out.println(value + " " + keyWord);
topTenWords.put(keyWord, value);
}
return topTenWords;
}
- 关键词统计
public HashMap<String,Integer> getKeyWords(ArrayList<Paper> papers){
HashMap<String,Integer> map = new HashMap<>();
Paper paper;
for (int i = 0; i < papers.size() ; i++){
paper = papers.get(i);
String[] words = paper.getKeyWords().split(",");
for (String s:words) {
if (map.containsKey(s)) {
//key存在
Integer value = map.get(s);
value++;
map.put(s, value);
//System.out.println(s);
} else {
//key不存在
map.put(s, 1);
}
}
}
return map;
}
- 论文数据的获取
JSONObject jsonObject = JSON.parseObject(readMMAP(file)); //转化为JSON格式数据
Paper paper = new Paper();
paper.setTitle(jsonObject.getString("论文名称")); //根据key获取value值
paper.setPaperAbstract(jsonObject.getString("摘要"));
paper.setUrl(jsonObject.getString("原文链接"));
paper.setMeeting("ECCV");
paper.setYear(jsonObject.getString("会议和年份").split(" ")[1]);
JSONArray keys = jsonObject.getJSONArray("关键词");
StringBuilder sb = new StringBuilder();
for (Object key : keys) {
sb.append(key).append(",");
}
paper.setKeyWords(sb.toString());
papers.add(paper);
- 文件读取
public static String readMMAP(File file){
RandomAccessFile raf = null;
MappedByteBuffer mbb = null;
try {
raf = new RandomAccessFile(file, "r");
mbb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
if (mbb != null){
return StandardCharsets.UTF_8.decode(mbb).toString();
} else {
return "";
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (mbb != null){
Cleaner var1 = ((DirectBuffer)mbb).cleaner();
if (var1 != null) {
var1.clean();
}
}
if (raf != null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return "";
}
- 读取eccv文件
public static List<Paper> readECCV() {
File root = new File(ECCV_ROOT);
File[] files = root.listFiles();
List<Paper> papers = new ArrayList<>(4096);
for (File file : files) {
JSONObject jsonObject = JSON.parseObject(readMMAP(file));
Paper paper = new Paper();
paper.setTitle(jsonObject.getString("论文名称"));
paper.setPaperAbstract(jsonObject.getString("摘要"));
paper.setUrl(jsonObject.getString("原文链接"));
paper.setMeeting("ECCV");
paper.setYear(jsonObject.getString("会议和年份").split(" ")[1]);
JSONArray keys = jsonObject.getJSONArray("关键词");
StringBuilder sb = new StringBuilder();
for (Object key : keys) {
sb.append(key).append(",");
}
paper.setKeyWords(sb.toString());
papers.add(paper);
}
return papers;
}
- 将对象列表写入数据库
private PapersService papersService;
@GetMapping("/paper")
public String create(){
ReadJsonFile readJsonFile = new ReadJsonFile();
Paper paper = new Paper();
List<Paper> papers = new ArrayList<>();
papers = ReadJsonFile.readCVPR();
for (int i = 0;i < papers.size(); i++){
papersService.insertMessage(papers.get(i));
}
}
- 将数据写入数据库中
public void insertMessage(Paper paper){
papersMapper.insertMessage(paper);
}
- 数据库操作(mybatis)
<insert id="insertMessage" keyProperty="id" useGeneratedKeys="true" parameterType="com.daming.bootstring.model.Paper">
insert into papers(keywords,abstract,title,meeting,year,url)
values (#{keyWords},#{paperAbstract},#{title},#{meeting},#{year},#{url})
</insert>
心路历程与收获
-
221801330:李对于这次作业的整体方向和细节提出了很好的建议,虽然作业的界面比较简陋,但是我们都付出了很大的努力。并且通过这次结对作业,我深刻体会到了什么是“1+1>2”。很多时候我模糊的地方他刚好擅长,就形成了一种很好的良性互补,非常有利于作业的进行与完成。而且,在两个人进行代码交流的时候,往往能发现对方代码中难以发现的错误,进而进行修改或者完善,这样不仅互相能从中得到提醒,并且能减少这种问题在以后点中出现的概率,提升代码质量,取长补短,互利共赢。通过这次结对编程,两个人彼此监督学习很有效果,对自己来说也是种新的尝试,也培养了我们的团队意识,在这次作业中也学到了很多东西。最后也要谢谢队友的帮助,让我们顺利地完成了作业。
-
221801332:由于没学过springboot,最开始是打算以纯前端的方式来完成这次作业,也由于较少写前端,前面两天浪费了较多的时间在bootstrap的学习、查看官方文档等。写完论文列表之后发现还有较多的时间,想着应该还能来得及便也开始学习springboot的使用。springboot框架让我的工作率大大提高,在较短的时间内便完成了一些后端所需要的操作。本次结对作业让我掌握了未曾了解过的知识,也更加了解了前后端的交互。由于个人原因导致了这次的作业完成比较简陋,前后端兼顾的学习让我花费了较多的时间,没有实现更多的功能,也是这次作业的一个遗憾。
评价结对队友
-
221801330:李的个人能力很强,前后端均有所涉猎,在这次作业中我有很多不懂的地方也向他请教。他能合理地安排时间来进行学习和作业,做事认真负责,遇到不懂上网去查询相关的资料让我们一起去学习,去弄懂,能够在这次作业积极地提出自己的建议和看法,并且就此进行讨论与修改。
-
221801332:对于任务的分配、编程过程中的讨论能够较及时的反馈,但完成度没有达到较理想的程度。能够根据我所提的需求在网上搜索学习,也较积极的向我讨论所遇到的一些问题。