结对第二次作业
这个作业属于哪个课程 | 2021春软件工程实践S班 (福州大学) |
---|---|
这个作业要求在哪里 | 作业连接 |
结对学号 | 221801135 & 221801114 |
这个作业的目标 | 顶会热词统计的实现 |
其他参考文献 | 《码出高效_阿里巴巴Java开发手册》 |
一、git仓库链接和代码规范链接
git仓库链接
代码规范链接
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 15 | 20 |
Development | 开发 | 1600 | 1680 |
• Analysis | • 需求分析 (包括学习新技术) | 60 | 50 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 50 | 40 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
• Design | • 具体设计 | 300 | 390 |
• Coding | • 具体编码 | 960 | 990 |
• Code Review | • 代码复审 | 50 | 50 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 100 |
Reporting | 报告 | 80 | 75 |
• Test Repor | • 测试报告 | 30 | 35 |
• Size Measurement | • 计算工作量 | 20 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 25 |
合计 | 1695 | 1775 |
三、成品展示
云服务器的访问链接
-
初始页面:
-
论文列表页
列表左上角显示了一共有多少篇论文。点击列表项可以进入论文详情页。
列表实现了分页功能,也能跳转到指定的页面。
论文列表右上角的放大镜图标可以唤出搜索框,输入内容可以实现对论文标题的模糊查询。如果搜索框里的内容是空串时,搜索的论文为全部已有论文
点击论文项右侧的删除图标可以删除该论文。
- 论文详情页
论文详情页展示了该论文的详细信息,包括标题、原文链接、年份、会议、关键词和摘要。点击左上角的返回图标可以返回刚刚的论文列表。点击某一个关键词可以显示拥有该关键词的论文列表。
点击原文链接可以跳转到这篇论文的原链接。
- 关键词图谱页
点击图表下方的按钮可以控制显示哪几个关键词。
点击某一个关键词可以显示拥有该关键词的论文列表。
- 热词走势图界面
以折线图或者柱状图的形式展现数据,鼠标移动到上面可以显示具体数据。
点击图表上方的按钮可以选择不展示哪一个会议的数据。
点击下方的关键词按钮可以选择展示某一个关键词的相关数据。
可以通过点击图表右上方的按钮切换图表样式。
四、结对讨论过程描述
进行本次结对作业时已经返校了,而且我们是舍友,所以我们主要采用的时线下讨论的方式来完成结对编程。
我们首先讨论了采用什么技术实现这一次的作业,最后决定采用JSP和Servlet实现。
然后是关键词图谱和热度走向图的实现部分,刚开始是打算使用自己编写的图表实现,因为开始使用Echarts的时候没办法进行点击跳转页面,所以关键词图表我们是自己编写的,而热度走向图则采用Echarts提供的动态图表实现。
以下是功能对接时的聊天记录截图:
五、设计实现过程
设计实现过程
- 本次的原型实现是基于Web来开发的,使用了Web框架;
- 前端主要采用JSP来编写界面布局,后端使用JAVA语言编写,使用JDBC与数据库进行连接,通过Servlet使用url来与前端的JSP进行对接,转递数据库相关数据;
- 图表部分使用了Echarts提供的动态图表,能够实现动态显示与点击跳转的功能。
功能结构图
六、代码说明
- 论文列表实现
-
前端代码
获取从ListServlet处得到的当前页面的论文列表,并显示在页面上。
<body>
<div>
<form class="searchBox_listPage" method="get" action="/partnerwork_war_exploded/SearchServlet">
<input class="searchBox_text" type="text" name="search" placeholder="Type to search">
<input type="submit" class="searchBox_btn" value="">
</form>
</div>
<a class="title_otherPage" href="mainPage.jsp">Crwaler</a>
<a class="leftpage_btn" href="/partnerwork_war_exploded/ChartPageServlet"><img src="png/leftpage.png"></a>
<a class="rightpage_btn" href="trendPage.jsp"><img src="png/rightpage.png"></a>
<%
out.print("<div class=\"totalNumber\">共查询到"+request.getAttribute("totalNumber")+"篇论文</div>");
%>
<ul class="main">
<%
List<Paper> list = (List<Paper>) request.getAttribute("list");
int iterator=(int)request.getAttribute("iterator");
for (int i=0;i<list.size();++i)
{
out.print("<li class=\"single_paper boxshadow\">" +
"<a class=\"paper_number\">"+(iterator+i+1)+"</a>"+//显示数字比迭代器多+1
"<a class=\"paper_title\" href=\"/partnerwork_war_exploded/InfoServlet?iterator="+(iterator+i)+"\">"+list.get(i).getTitle()+"</a>"+
"<div class=\"buttonbox\">" +
"<a class=\"delete_btn\" onclick=\"func("+(iterator+i)+","+request.getAttribute("currentPage")+")\">" +
"<img src=\"png/delete.png\">" +
"</a>" +
"</div>" +
"</li>");
}
%>
<li class="single_paper">
<%
out.print("<a class=\"lastpage_btn\" href=\"/partnerwork_war_exploded/LastPageServlet?page="+((int) request.getAttribute("currentPage") - 1)+"\">\n");
%>
<img src="png/lastpage.png">
</a>
<div class="pagenumber">
<%
out.print("<form method=\"get\" action=\"/partnerwork_war_exploded/SkipServlet?currentPage="+request.getAttribute("currentPage")+"\"><input type=\"text\" name=\"page\" class=\"currentpage\" placeholder=\""+request.getAttribute("currentPage")+"\"/></form>");
%>
/<a class="maxpage">
<%
int num=1;
if(request.getAttribute("maxPage")!=null)
num=(int)request.getAttribute("maxPage");
out.print(num);
%>
</a>
</div>
<%
out.print("<a class=\"nextpage_btn\" href=\"/partnerwork_war_exploded/NextPageServlet?page=" + ((int) request.getAttribute("currentPage") + 1) + "\">\n");
%>
<img src="png/nextpage.png">
</a>
</li>
</ul>
<script>
function func(iterator,page){ /* 绑定事件 */
var r = confirm("确定从库中移除该论文?")
if (r == true) {
var str="/partnerwork_war_exploded/DeleteServlet?iterator="+iterator+"&page="+page;
window.location.href=str;
}
}
</script>
</body>
- 后端代码
后端通过list()函数获取数据库中的论文,再以重载函数list(int page, int count)获得某一页的制定数量的文论列表通过Servlet转递给前端的JSP,实现分页的功能。
public List<Paper> list(){
paperList = new ArrayList<Paper>();
try (Connection c = DBUtil.getConnection(); Statement s = c.createStatement()) {
String sql = "select * from paper";
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
Paper paper = new Paper();
paper.setId(rs.getInt("id"));
paper.setTitle(rs.getString("title"));
paper.setAbstractText(rs.getString("abstract"));
paper.setKeywords(rs.getString("keywords"));
paper.setTagList();
paper.setDoiLink(rs.getString("doiLink"));
paper.setPublicationDate(rs.getInt("publicationDate"));
paper.setConference(rs.getString("conference"));
paperList.add(paper);
}
} catch (SQLException e) {
e.printStackTrace();
}
return paperList;
}
public List<Paper> list(int start, int count){
if (paperList == null){
list();
setTagMap();
}
List<Paper> list = new ArrayList<Paper>();
if (start * count > paperList.size()){
for (int i = (start - 1) * count;i < paperList.size();i ++){
list.add(paperList.get(i));
}
}
else{
list = paperList.subList((start - 1) * count , (start - 1) * count + count);
}
return list;
}
- 热词统计实现
- 前端代码
以柱状图表的形式表现出爬取论文中的top10个关键词,从ChartPageServlet获取传来的排序过的关键词列表,并逐个显示在页面上。
<body>
<a class="title_otherPage" href="mainPage.jsp">Crwaler</a>
<a class="leftpage_btn" href="trendPage.jsp"><img src="png/leftpage.png"></a>//页面切换到trendPage
<a class="rightpage_btn" href="/partnerwork_war_exploded/ListServlet"><img src="png/rightpage.png"></a>
<div class="main">
<div class="chartname">热词统计</div>
<div class="selectbox">
<input type="checkbox" name="select" id="slide_1" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_2" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_3" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_4" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_5" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_6" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_7" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_8" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_9" class="checkbox" checked>
<input type="checkbox" name="select" id="slide_10" class="checkbox" checked>
<div class="chart">
<%
/*这个地方获取tag列表然后下面每一个地方都要放tag的具体内容,链接跳转到searchServler去进行搜索然后重定向到listPage*/
List<HashMap.Entry<String, Integer>> list = (List<HashMap.Entry<String, Integer>>) request.getAttribute("list");
out.print("<div class=\"chart_slide\" id=\"slide_chart_1\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(0).getKey()+"'\">"+list.get(0).getKey()+"<br>"+list.get(0).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_2\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(1).getKey()+"'\">"+list.get(1).getKey()+"<br>"+list.get(1).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_3\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(2).getKey()+"'\">"+list.get(2).getKey()+"<br>"+list.get(2).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_4\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(3).getKey()+"'\">"+list.get(3).getKey()+"<br>"+list.get(3).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_5\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(4).getKey()+"'\">"+list.get(4).getKey()+"<br>"+list.get(4).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_6\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(5).getKey()+"'\">"+list.get(5).getKey()+"<br>"+list.get(5).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_7\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(6).getKey()+"'\">"+list.get(6).getKey()+"<br>"+list.get(6).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_8\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(7).getKey()+"'\">"+list.get(7).getKey()+"<br>"+list.get(7).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_9\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(8).getKey()+"'\">"+list.get(8).getKey()+"<br>"+list.get(8).getValue()+"</div>\n" +
"<div class=\"chart_slide\" id=\"slide_chart_10\" onclick=\"document.location.href='/partnerwork_war_exploded/TagSearchServlet?search="+list.get(9).getKey()+"'\">"+list.get(9).getKey()+"<br>"+list.get(9).getValue()+"</div>");
%>
</div>
<div class="slide">
<label for="slide_1" id="slide_btn_1">TOP 1</label>
<label for="slide_2" id="slide_btn_2">TOP 2</label>
<label for="slide_3" id="slide_btn_3">TOP 3</label>
<label for="slide_4" id="slide_btn_4">TOP 4</label>
<label for="slide_5" id="slide_btn_5">TOP 5</label>
<label for="slide_6" id="slide_btn_6">TOP 6</label>
<label for="slide_7" id="slide_btn_7">TOP 7</label>
<label for="slide_8" id="slide_btn_8">TOP 8</label>
<label for="slide_9" id="slide_btn_9">TOP 9</label>
<label for="slide_10" id="slide_btn_10">TOP 10</label>
</div>
</div>
</div>
</body>
- 前端代码
首先setTagMap()函数了统计爬取论文的关键词,保存在一个Map中,再使用setSortTagList()函数对关键词进行排序,排序结果保存在sortTagList中,并只取sortTagList的前十项。
public void setTagMap(){
tagMap = new HashMap<String, Integer>();
for (Paper paper : paperList){
for (String tag : paper.getTagList()){
tag = tag.toLowerCase();
if (tagMap.containsKey(tag)) {
int n = tagMap.get(tag);
tagMap.put(tag, n + 1);
} else {
tagMap.put(tag, 1);
}
}
}
}
public void setSortTagList(){
sortTagList = new ArrayList<>(tagMap.entrySet());
Collections.sort(sortTagList, new Comparator<HashMap.Entry<String, Integer>>() {
@Override
public int compare(HashMap.Entry<String, Integer> tag1, HashMap.Entry<String, Integer> tag2) {
if (tag1.getValue().equals(tag2.getValue())) {
return tag1.getKey().compareTo(tag2.getKey());
} else {
return tag2.getValue() - tag1.getValue();
}
}
});
sortTagList = sortTagList.subList(0, 10);
}
public List<HashMap.Entry<String, Integer>> getSortTagList() {
return sortTagList;
}
- 热度走向图实现
- 前端代码
使用Echarts来展示top10个关键词在近几年三大会议的出现的次数,通过json文件解析所需要的数据展示在图表上。
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/trendPage.css">
<title>Trend Page</title>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.0.2/echarts.common.js"></script>
</head>
<body>
<a class="title_otherPage" href="mainPage.jsp">Crwaler</a>
<a class="leftpage_btn" href="/partnerwork_war_exploded/ListServlet"><img src="png/leftpage.png"></a>
<a class="rightpage_btn" href="/partnerwork_war_exploded/ChartPageServlet"><img src="png/rightpage.png"></a>
<div class="main">
<div class="selectbox">
<input type="radio" name="select" id="slide_1" class="checkbox" checked>
<input type="radio" name="select" id="slide_2" class="checkbox" >
<input type="radio" name="select" id="slide_3" class="checkbox" >
<input type="radio" name="select" id="slide_4" class="checkbox" >
<input type="radio" name="select" id="slide_5" class="checkbox" >
<input type="radio" name="select" id="slide_6" class="checkbox" >
<input type="radio" name="select" id="slide_7" class="checkbox" >
<input type="radio" name="select" id="slide_8" class="checkbox" >
<input type="radio" name="select" id="slide_9" class="checkbox" >
<input type="radio" name="select" id="slide_10" class="checkbox" >
<div id="chartbar1" class="chartbar"></div>
<div id="chartbar2" class="chartbar"></div>
<div id="chartbar3" class="chartbar"></div>
<div id="chartbar4" class="chartbar"></div>
<div id="chartbar5" class="chartbar"></div>
<div id="chartbar6" class="chartbar"></div>
<div id="chartbar7" class="chartbar"></div>
<div id="chartbar8" class="chartbar"></div>
<div id="chartbar9" class="chartbar"></div>
<div id="chartbar10" class="chartbar"></div>
<div class="slide">
<%
PaperDao paperDao = PaperDao.getInstance();
if (paperDao.getSortTagList() == null){
paperDao.setSortTagList();
}
List<HashMap.Entry<String, Integer>> list = paperDao.getSortTagList();
out.print("<label for=\"slide_1\" id=\"slide_btn_1\">"+list.get(0).getKey()+"</label>\n" +
" <label for=\"slide_2\" id=\"slide_btn_2\">"+list.get(1).getKey()+"</label>\n" +
" <label for=\"slide_3\" id=\"slide_btn_3\">"+list.get(2).getKey()+"</label>\n" +
" <label for=\"slide_4\" id=\"slide_btn_4\">"+list.get(3).getKey()+"</label>\n" +
" <label for=\"slide_5\" id=\"slide_btn_5\">"+list.get(4).getKey()+"</label>\n" +
" <label for=\"slide_6\" id=\"slide_btn_6\">"+list.get(5).getKey()+"</label>\n" +
" <label for=\"slide_7\" id=\"slide_btn_7\">"+list.get(6).getKey()+"</label>\n" +
" <label for=\"slide_8\" id=\"slide_btn_8\">"+list.get(7).getKey()+"</label>\n" +
" <label for=\"slide_9\" id=\"slide_btn_9\">"+list.get(8).getKey()+"</label>\n" +
" <label for=\"slide_10\" id=\"slide_btn_10\">"+list.get(9).getKey()+"</label>");
%>
</div>
</div>
</div>
<script type="text/javascript">
var chartDom1 = document.getElementById('chartbar1');
var myChart1 = echarts.init(chartDom1,'dark');
var option1;
option1 = {
legend: {},
toolbox:{
show:true,
feature:{
restore:{
show:true},
magicType:{
type:['line','bar']
}
}},
title: {
text:'热词趋势'
},
tooltip: {},
xAxis: [
{type: 'category'},
],
yAxis: {},
dataset: {
source: []
},
series: [
{type: 'bar', seriesLayoutBy: 'row'},
{type: 'bar', seriesLayoutBy: 'row'},
{type: 'bar', seriesLayoutBy: 'row'},
]
};
option1 && myChart1.setOption(option1);
$.get('../json/tag1.json').done(function(data){
var obj = $.parseJSON(data);
myChart1.setOption({
source:[obj.source]
}
);
});
/*剩余9个图表的数据填充操作,同上,篇幅限制,就不展示了*/
</script>
</body>
</html>
- 后端代码
通过循环来向数据库查询某一个关键词在指定的几年内在三大会议中出现的次数,存放在一个二维数组中并返回。
public int[][] countTagByYear(int[] years, String tag){
int[][] counts = new int[3][3];
String[] conferences = new String[]{"CVPR","ICCV","ECCV"};
try (Connection c = DBUtil.getConnection(); Statement s = c.createStatement()) {
for (int j = 0;j <= 2;j++){
for (int i = 0; i <= 2;i ++){
int total = 0;
String sql = "select count(*) from paper_library where lower(keywords) like '%" + tag +
"%' and (publicationDate="+years[i] + " or publicationDate=" + (years[i] + 1) +
") and conference ='" + conferences[j] + "'";
System.out.println(sql);
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
total = rs.getInt(1);
}
counts[j][i] = total;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return counts;
}
七、心路历程和收获
221801135
说实话一开始接到这个任务的时候我是很懵的,好在第一次任务只是模型的构建,相对来说比较简单,于是完成起来没什么太大的压力,但是转而迎来了第二次作业,要求我们实现,突然就懵掉了,加上不久前刚刚经历了24小时的极限编程,有些身心俱疲,还没有调整过来,这次又是写前端的,对于没有框架经验的我,直接用纯html+css方式很难写出好的页面,虽然不用框架也避免了公式化页面的结果,算是还原了自己原型设计的特色。后来经过了好几天加班加点终于完成了本地版本,可是距离作业真正的完成还有关键的一步,那就是服务器的搭载,完成了人生中第一次购买服务器并搭载项目的成就。忙活了一个上下午,服务器运行以后还有频发的突发情况,意外的bug之类种种,解决下来属实头大,好在最后甚至在有盈余的情况下完成了,还可以教舍友配置服务器(其实不难就是走了太多弯路)。可以说是收获满满了,不过说实在对于我以后从事游戏岗的作用就没有那么大了,平台类项目终究是个框架,现在网上轮子很多,想水的话很容易就可以做出一个缝合怪,认真从底层写起,懂得怎么团队协作,才是我从这次作业中得到的最大收获。
221801114
在看到作业要求的时候,我心里是很慌张的,因为自己本身并没有什么项目经验,不知道前后端的工作区分、如何进行对接等等。好在有队友的帮助,他负责前端,我负责后端,我可以使用自己熟悉的Java编写后端数据库连接部分、数据处理和一些Servlet。在尝试编写了部分功能以及功能成功对接以后,渐渐进入了状态,更有信心完成剩下的功能。好的队友也很重要,能够一起学习、一起进步,队友也给我提供了很多的帮助,让我能狗顺利的完成这一次的作业。相对于以前的个人编程,从这一次的结对作业中,我对合作编程有了更好的经验与理解,学会了怎么更好的和他人沟通编程。
八、评价结对队友
对221801135的评价
很有想法,思路很清晰,知道自己想要做的是什么、想要做成什么样子。技术也过硬,虽然负责前端部分的设计,但是如果我后端出现了问题,他也能够为我提供帮助,给人一种能够很安心的感觉。
对221801114的评价
善于沟通,勤奋刻苦,可以很快的对新的改动提出合理意见,并且能够虚心接受建议。认真负责,对于任务一丝不苟,会及时汇报进度,并且帮助完成一些额外工作,不会抱怨辛苦,任劳任怨。