结对作业二
基本信息
这个作业属于哪个课程 | 2021春软件工程实践|W班 |
---|---|
这个作业要求在哪里 | 作业要求 |
结对学号 | 221801301 221801303 |
这个作业的目标 | 结对编程初体验,前后端技术学习 |
其他参考文献 | CSDN,LayUI框架官方文档,SpringBoot官方文档 |
目录
git仓库链接和代码规范链接
git仓库链接
代码规范链接
PSP表格
|PSP2.1|Personal Software Process Stages|预估耗时(分钟)|实际耗时(分钟)|
|:----|:----|:----|:----|:----|:----|
|Planning|计划| | |
|• Estimate|• 估计这个任务需要多少时间|10|5|
|• Frontend Design|• 前端设计|100|60|
|• Backend Design|• 后端设计|100|120|
|Development|开发| | |
|• Analysis|• 需求分析 (包括学习新技术)|60|180|
|• Design code rule|• 设定代码规范|30|30|
|• Team Communication|• 结对讨论|150|120|
|• Frontend+Backend Coding|• 前端编码|600|1500|
|Reporting|报告| | |
|• Test Report|• 测试报告|60|60|
|• Size Measurement|• 计算工作量|10|10|
|• Postmortem & Process Improvement Plan|• 事后总结, 并提出过程改进计划|30|2085|
| |合计|1150|2145|
成品展示
云服务器访问链接
成品介绍
主界面为默认装填的论文列表信息,选择按标题搜索可以实现按照标题的关键词模糊搜索。实现了按标题字母排序功能。
选择按论文作者搜索可以实现按照作者模糊搜索。实现了按照作者名排序的功能(但考虑到有的论文作者不止一个,这个功能就不太有用)
选择按关键词搜索可以实现按照关键词准确搜索。
表格下方有分页选项,用户既可以点击页码进行跳转,也可以按照输入的页号进行跳转。用户也可以选择每页显示的论文条数。
点击每条论文信息右边的详情按钮,可以跳到该论文详情信息的展示页面。从上而下展示了论文的标题、作者、发表日期、摘要、关键词、链接,点击链接可以跳转到论文原文网页。之后还提供了三大顶会的官方网页,以及最后的关闭页面的按钮。
点击左侧导航栏的热词分析选项可以展示TOP10热词。鼠标悬停展示该热词的数据。点击可以跳转到含有该关键词的论文列表。
热词走势页面展示了三大顶会近年来的最热关键词和点击量。点击上方的选项卡可以切换不同顶会。
结对讨论过程描述
- 在项目初期权衡每个人的优势和劣势,我们决定使用前后端分离的方式。221801301邵涵洋同学负责前端,221801303宋家锐同学负责后端。
- 因为使用前后端分离的开发方式,在查阅了相关开发流程之后,我们决定首先在项目设计初我们使用石墨在线文档确定了前后端交互的接口。以下是我们讨论制定出的接口文档。
- 因为221801301邵涵洋同学和221801303宋家锐同学宿舍仅一墙之隔,在项目的中后期主要以线下面对面的方式进行讨论。
- 以下是我们部分qq上向对方汇报的进度以及问题。
设计实现过程
结构功能图
实现过程
前端
- 主要实现了main、search、detail、data、statics等界面。
- main页面的侧边栏和search页面的table主要复用了layui的组件。页面的切换通过main页面的iframe动态加载。
- 对于不同方式的搜索,首先从下拉列表和表单中获取搜索类别和搜索的内容。根据不同的搜索类别使用不同的table.render()方法,使用table.reload()方法异步加载数据。
- 论文详情信息页面复用了网上优秀的模板,在页面上获得上一个页面传递过来的论文id,发送ajax请求,向后端请求对应id论文的详细数据,并按照document.getElementByID()装填到对应的html元素中。
- data和statics都是从后端请求对应的关键词json数据。其中data页面通过添加layui的切换事件监听实现三个顶会的分页。statics通过添加echart的点击事件向main页面的url中添加要搜索的关键词,search页面获取keyword后通过关键词搜索得到结果、装填表格。
后端
- 整个项目是基于SpringBoot和MyBatis开发的,使用Maven进行项目管理
- 数据库表格主要是三张,一张是主要的论文信息,另外两张是以多对多关系存储的论文关键词和作者
- 对JSON数据的解析和封装使用了阿里的FastJSON
- 整个项目分为四层controller是跟前端交互的接口,dao是跟数据交互的接口,pojo则是对论文数据的一个封装,service里有两个类,一个是前端接口的实现类,另一个是将论文数据导入数据库的实现类
代码说明
前端代码说明
- 页面切换:页面顶部的banner和左侧导航栏在大多数窗口都固定不变,仅仅是中间的区域有页面的切换,因此我考虑在中间区域使用iframe,通过点击导航栏块实现iframe中src属性的修改,从而实现中间区域页面的切换。
<div class="layui-body">
<!-- 内容主体区域 -->
<iframe src="search.html" frameborder="0" id="demoAdmin" style="width: 100%; height: 100%; padding: 15px;"></iframe>
</div>
<script>
<!-- 为每一个导航块添加onclick,例如onclick="change('search')" -->
function change(title){
let url=title+".html";
document.getElementById("demoAdmin").src=url;
}
</script>
- 论文搜索页面的实现(表格信息的加载,“详情”“删除”的实现)
<script>
var tableByName;
layui.use('table', function(){
var table1 = layui.table;
tableByName=table1.render({
elem: '#LAY_table_user'
,url: 'http://47.111.17.116:8080/queryName'
,method : "POST"
,contentType:"application/json"
,where:{
// name:"computer",
}
,parseData: function(res){ //后端传来的json数据与表格要求的头部信息不匹配,使用parseData加上需要的头部信息
console.log(res);
return {
"code": 0, //解析接口状态
"msg": "", //解析提示文本
"count": res.count, //解析数据长度
"data": res.data //解析数据列表
};
}
,cols: [[//sort:true表示该列可以实现排序
{field:'name', title: '标题', sort: true, fixed: true},
{field:'abstract', title: '摘要', sort: true},
{field:'keyword', title: '关键词'},
{field:'author', title: '作者', sort: true},
{field:'year', title: '发表年份', sort: true},
{field:'link', title: '链接'},
{fixed: 'right', title:'操作', toolbar: '#barDemo'}
]]
,id: 'testReload'
,page: true
,limit:10
,height: 500
});
table1.on('tool(user)', function(obj){
if(obj.event==='detail'){//点击详细信息通过obj.data.iD;读出该行论文的id号,传递给新页面的url中
var item=obj.data.iD;
var url= "detail/index.html?id="+item;
window.open(url);
}
else{
layer.confirm('要删除改行吗', function(index){
obj.del();
layer.close(index);
});
}
});
});
</script>
- 论文详细信息页面读出url中的id信息,将论文信息填充至页面
<script type="text/javascript">
function getQueryVariable(variable)//函数截取“id=xxx”字符串
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
document.getElementById("demo").innerHTML = getQueryVariable("id");//将获取到的id存在demo块上
</script>
<script>
var id=Number($("#demo").html());
$(function(){
$.ajax({
type:"POST",
url:"http://47.111.17.116:8080/getDetail",//通过id向后端请求论文json数据
data:JSON.stringify({
"id":id
}),
dataType:"json",
contentType:"application/json",
success:function(data){//装填json中的信息至html中的元素中
document.getElementById("title").innerHTML = data.name;
document.getElementById("authors").innerHTML = (data.author!=""?data.author:"No AUTHOR");
document.getElementById("year").innerHTML = data.year;
document.getElementById("abstract").innerHTML = data.abstract;
document.getElementById("keywords").innerHTML = data.keyword;
document.getElementById("ref").href=data.link;
document.getElementById("ref").innerHTML=data.link;
}
})
})
</script>
- 统计某一个顶会每一年的最热热词。首先加载echart柱形图,在ajax请求中向后端请求相关数据,将返回数据分keyword(关键词)和num(数据)分至两个数组并装填至echart的相关区域。
var keyword=[];
var num=[];
for(var i=0;i<data.length;i++){
keyword.push(data[i].keyword);
num.push(data[i].num);
}
console.log(keyword);
console.log(num);
- TOP10热词辐射图实现点击每一个关键词模块跳转到相应关键词论文列表。查询echart官网发现可以给echart添加点击事件。点击实现跳转,并在url之后拼接keyword=XXX,在返回的页面获取keyword后调用按关键词查询。
myChart.on('click', function (param){
var name=param.name;
var url= "main.html?keyword="+name;
window.open(url);// 打开新的search界面
});
后端代码说明
-
数据库建表
会议和作者/关键词都是多对多的关系,所以共使用三张表:paper、keyword、author
CREATE TABLE `paper` ( `id` int NOT NULL AUTO_INCREMENT, `abstract` varchar(255) DEFAULT NULL, `year` varchar(255) DEFAULT NULL, `time` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `link` varchar(255) DEFAULT NULL, `meeting` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=13141 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `keyword` ( `id` int NOT NULL AUTO_INCREMENT, `pid` int DEFAULT NULL, `keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`id`), KEY `keyword` (`pid`), CONSTRAINT `keyword` FOREIGN KEY (`pid`) REFERENCES `paper` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=233553 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `author` ( `id` int NOT NULL AUTO_INCREMENT, `pid` int DEFAULT NULL, `author` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `author` (`pid`), CONSTRAINT `author` FOREIGN KEY (`pid`) REFERENCES `paper` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=37366 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-
将数据导入数据库
因为ECCV会议的数据格式和另外两个会议的格式并不相同,所以一开始的想法就决定将三个会议的导入放到了三个函数之中。因为逻辑基本一致,所以这边就展示一个导入中的代码。
-
虽然导入的格式不同,但是需要获取和展示的方式是相同的,因此我只用使用一个pojo类Paper
public class Paper { int ID; int meeting;//0为CVPR,1为ECCV,2为ICCV String Abstract; String year;//会议年份 String time;//文章发布时间 String name; List<String> author; String link; List<String> keyword; //后面的构造函数和get/set方法就不展示了 }
-
使用File.list()按文件将JSON读到一个字符串中
String filepath = "./论文数据/CVPR(2000年至2020年,6916篇"; File file = new File(filepath); if (file.isDirectory()) { System.out.println("文件夹"); String[] filelist = file.list(); for (int i = 0; i < filelist.length; i++) { Paper obj = new Paper(); // File readfile = new File(filepath + "\\" + filelist[i]); File readfile = new File(filepath + "/" + filelist[i]); try { FileReader fr = new FileReader(readfile); BufferedReader bfr = new BufferedReader(fr); StringBuilder json = new StringBuilder(); String line = null; while ((line = bfr.readLine()) != null) { // System.out.println(line); json.append(line); }
-
使用FastJSON对数据进行解析并装配到Paper对象中
JSONObject parse; JSONArray kw; JSONArray at; parse = (JSONObject) JSON.parse(json.substring(0, json.length() - 1)); kw = parse.getJSONArray("keywords"); at = parse.getJSONArray("authors"); if (parse.getString("abstract") != null) { //如果摘要过长,则截取部分 if (parse.getString("abstract").length() < 200) obj.setAbstract(parse.getString("abstract")); else obj.setAbstract(parse.getString("abstract") .substring(0,200).concat("...")); } obj.setMeeting(0); obj.setName(parse.getString("publicationTitle")); obj.setLink(parse.getString("doiLink")); obj.setYear(parse.getString("conferenceDate")); obj.setTime(parse.getString("dateOfInsertion"));
-
通过MyBatis将数据放入数据库
dao.addPaper(obj); if (kw != null) for (int j = 0; j < kw.size(); j++) { JSONObject ko = kw.getJSONObject(j); JSONArray ka = ko.getJSONArray("kwd"); if (ka != null) for (int k = 0; k < ka.size(); k++) { //检查是否有重复 if (dao.checkKeyword(obj.getID(), ka.getString(k)) == 0) // dao.addCVPRKeyword(obj.getID(), ka.getString(k)); dao.addKeyword(obj.getID(), ka.getString(k)); } } if (at != null) for (int j = 0; j < at.size(); j++) { JSONObject ao = at.getJSONObject(j); if (dao.checkAuthor(obj.getID(), ao.getString("name")) == 0) // dao.addCVPRAuthor(obj.getID(), ao.getString("name")); dao.addAuthor(obj.getID(), ao.getString("name")); }
<insert id="addPaper" parameterType="com.fzu.server.pojo.Paper" useGeneratedKeys="true" keyProperty="ID"> insert into paper(abstract, year, time, name, link, meeting) values (#{Abstract}, #{year}, #{time}, #{name}, #{link}, #{meeting}) </insert> <insert id="addKeyword"> insert into keyword(pid, keyword) values (#{pID}, #{keyword}) </insert> <insert id="addAuthor"> insert into author(pid, author) VALUES (#{pID}, #{author}) </insert>
-
-
功能实现
-
通过文章名模糊查询
public Object queryName(Map<String, Object> req) { String name; //首次加载默认查询一次computer if(req.get("name")==""||req.get("name")==null) name="computer"; else name=req.get("name").toString(); int page = (int) req.get("page"); int limit = (int) req.get("limit"); int start = (page - 1) * limit; if(page==1)count=dao.getCount(name,0); List<Paper> paper = dao.getPaperByName(name, start, limit); for (Paper pp : paper) { pp.setKeyword(dao.getKeyword(pp.getID())); pp.setAuthor(dao.getAuthor(pp.getID())); } Map<String,Object> result=new HashMap<>(); result.put("data",paper); result.put("count",count); return JSONObject.toJSON(result); }
<select id="getPaperByName" resultType="com.fzu.server.pojo.Paper"> select * from paper where name like CONCAT('%', #{name}, '%') limit #{start},#{lim}; </select> //获取查询到的总数 <select id="getCount" resultType="java.lang.Integer"> select COUNT(*) from paper where 1=1 <if test="state==0"> and name like CONCAT('%',#{str},'%'); </if> <if test="state==1"> and id in (select DISTINCT pid from author where author like CONCAT('%',#{str},'%')); </if> <if test="state==2"> and id in (select DISTINCT pid from keyword where keyword = #{str}); </if> </select> //获取keywords <select id="getKeyword" resultType="java.lang.String"> select keyword from keyword where pid = #{id} limit 5; </select> //获取author <select id="getAuthor" resultType="java.lang.String"> select author from author where pid = #{id} limit 5; </select>
-
通过作者名模糊查询
public Object queryAuthor(Map<String, Object> req) { String author; author = req.get("author").toString(); int page = (int) req.get("page"); int limit = (int) req.get("limit"); int start = (page - 1) * limit; if (page == 1) count = dao.getCount(author, 1); List<Paper> paper = dao.getPaperByAuthor(author, start, limit); for (Paper pp : paper) { pp.setKeyword(dao.getKeyword(pp.getID())); pp.setAuthor(dao.getAuthor(pp.getID())); } Map<String, Object> result = new HashMap<>(); result.put("count", count); result.put("data", paper); return JSONObject.toJSON(result); }
<select id="getPaperByAuthor" resultType="com.fzu.server.pojo.Paper"> select * from paper where id in (select DISTINCT pid from author where author like CONCAT('%', #{author}, '%')) limit #{start},#{lim}; </select>
-
通过关键词查询
public Object queryKeyword(Map<String, Object> req) { String keyword; keyword = req.get("keyword").toString(); int page = (int) req.get("page"); int limit = (int) req.get("limit"); int start = (page - 1) * limit; if (page == 1) count = dao.getCount(keyword, 2); List<Paper> paper = dao.getPaperByKeyword(keyword, start, limit); for (Paper pp : paper) { pp.setKeyword(dao.getKeyword(pp.getID())); pp.setAuthor(dao.getAuthor(pp.getID())); } Map<String, Object> result = new HashMap<>(); result.put("count", count); result.put("data", paper); return JSONObject.toJSON(result); }
<select id="getPaperByKeyword" resultType="com.fzu.server.pojo.Paper"> select * from paper where id in (select DISTINCT pid from keyword where keyword = #{keyword}) limit #{start},#{lim}; </select>
-
通过文章ID获取详细信息
public Object getDetail(Map<String,Object> req){ int id = (int)req.get("id"); Paper paper = dao.getDetail(id); paper.setAuthor(dao.getAuthor(id)); paper.setKeyword(dao.getKeyword(id)); return JSONObject.toJSON(paper); }
<select id="getDetail" resultType="com.fzu.server.pojo.Paper"> select * from paper where id = #{id}; </select>
-
获取会议TOP10(这里列举的是获取全部文章的TOP10,每个分会的TOP10逻辑相似,这里就不重复说了)
public Object getTOP() { List<Map<String, String>> top = dao.getTOP(); return JSONArray.toJSON(top); }
<select id="getTOP" resultType="java.util.Map"> select keyword, COUNT(*) num from keyword group by keyword order by COUNT(*) DESC limit 10; </select>
-
心路历程和收获
221801301邵涵洋
心路历程
这次结对作业带给我的冲击和收获是很大的。一开始看到这个作业要求的时候,说实话我认为我根本无法按时完成,因为自己除了上学期学习的web基础知识啥也不知道。一开始无处下手,到处询问班级同学之后他们建议我用layui框架构建前端界面,但是刚开始除了复制官网的例子之外没有啥能做的。在看了b站的layui+spring boot开发视频之后,也仅仅是略有头绪。
中途有一天因为团队编程耽搁了结对编程的项目进度,在团队编程中我才意识到负责前端的同学还需要设计与后端的数据交互,例如使用jquery的ajax操作。因为我自己是一个心态比较容易崩的人,遇到需要很多没有学过的知识的时候就很慌,所以那几天心情一直都不好,前端进度也基本没有推进,也没有跟负责后端的家锐同学交流进度。
直到截止日期前三天的晚上,家锐过来和我说他的接口已经写好了,只要我调用就可以了,我终于是振作起来写前后端交互,同时又修改了之前写出来的难看的界面。最后几天从早到晚呆在家锐的宿舍里。最后写完感觉前端也不是想我之前想的那样难以理解。实现了整个项目之后成就感蛮强的,自信心也得到了提升。真的非常感谢家锐的支持和帮助,也感谢林浩然同学、吴凯嘉同学、张晨星同学等同学给予的技术上的支持,谢谢爸爸妈妈能在我沮丧迷茫时候的鼓励,也感谢所有一起做这项作业的同学的陪伴!
收获
这次做前端给我带来的最重要的收获是要考虑到
<script>
语句块的执行顺序。在实现点击echart跳转到关键词搜索的部分,我因为没有考虑到用于渲染表格的json数据可能会比通过关键词查询返回的json数据先填充至表格中,导致表格中的论文不符合关键词搜索的结果。查找资料了解到中的script会先于<body>
中的script执行,<body>
中的script按照从上到下的顺序执行,便调整了顺序,同时通过settimeout方法让数据装填至表格的动作延后,从而实现正确的数据装填。
另一个比较大的收获是在iframe里获得当前页面的url需要用到window.parent.location.search方法。因为我在main页面中的iframe中加载了search页面,如果没有加parent就无法获得main页面中的url。
221801303宋家锐
心路历程
因为像这样的进行一个完整的项目开发的经历是在是太少了,所以刚开始的时候是非常迷茫的,也是非常没底的。我和涵洋最开始的问题是一样,不知道该做什么,就迷迷糊糊的开始写了,不过好在是得到了一些大佬的帮助,还是将项目顺利的搭建起来,知道了自己该使用什么,该学习什么,搜索资料也有了方向,这才算是整个项目开始了吧。
可能是之前也有用过SpringBoot,再加上大佬的帮助,数据的导入,接口的编写过程都十分的顺利(当然也导致了后面好几次的删库)。自己的部分有了点结果后,就开始和涵洋进行前后端的交互和联调了,这部分是整个开发过程中花费时间最长的了吧。不过已经不像之前的还在线上,现在能够面对面的进行这个交互和调试的过程,也是让效率大大的提升了。经过两天多的努力把遇到的困难都解决了。
收获
我觉得这次最大的收获就是让我明白了前期分析设计是多么的重要,这次就是因为前期分析设计时候太过于随意了些导致我已经记不清我删了多少次库了。一开始数据库表不合理删了一次,然后没有对摘要部分处理又删了一次,没有去除重复关键词、作者,删库。CVPR、ICCV链接拿错了,删库。还有在删库的时候忘记把自增设置为1又得删,每次导库都得花十几二十分钟,哎后悔呐,以后一定好好做人,认真分析设计。
队友评价
221801301邵涵洋=>221801303宋家锐:
家锐给我的印象一直都是冷静稳重。投入编程任务的时候就极其专心,思考问题也会钻的很深。他给我的支持和鼓励并单单是一句”加油“那么简单,还是晚上会来我寝室说”我把接口都实现了,你累了就休息一下,明天只要调用就可以了“,还有我在写数据交互的时候会在我旁边跟我说调用的格式、检查我的语法错误,遇到bug也会和我一起分析。给我感觉他是前后端、部署服务器样样精通,而我就只是被他带飞的小白而已。家锐让我非常敬佩的就是他遇到困难能够深入思考、不轻易放弃的品质,这些品质恰恰是我所缺乏的。
感谢家锐!感谢这次结对编程中你的陪伴和鼓励!
221801303宋家锐=>221801301邵涵洋:
涵洋就是怎么说呢,永远的行动派,做起事情来从不会拖泥带水,比较起来,我就是那种很喜欢偷偷闲。跟涵洋进行合作也算是让我更加按时高效的去完成任务。在开发过程中,虽然有些语法不太熟悉,但是他一直都在学习和进步,他的这种学习精神和行动力是非常值得我学习的地方。