结对作业二
#软工实践结对作业二
------
|这个作业属于哪个课程|[2021春软件工程实践 W班 (福州大学)](https://edu.cnblogs.com/campus/fzu/2021SpringSoftwareEngineeringPractice)|
|-- |-- |
|这个作业要求在哪里|[软工实践结对作业二 ](https://edu.cnblogs.com/campus/fzu/2021SpringSoftwareEngineeringPractice/homework/11890)|
|这个作业的目标|详细阅读作业要求、采用web技术实现原型功能、撰写博客 |
|其他参考文献| [邹欣老师的博客园讲义](https://www.cnblogs.com/xinz/archive/2011/11/27/2265425.html) |
------
[toc]
# git仓库链接和代码规范链接
[git仓库链接](https://github.com/Pilipala-cmy/PairProject)
[代码规范链接](https://github.com/BingKui/javascript-zh)
# 部署后的项目地址
http://47.119.130.124
# 项目接口文档
https://www.showdoc.com.cn/1294949416702524?page_id=6549970937759390
# PSP表格
|PSP2.1|Personal Software Process Stages|预估耗时(分钟)|实际耗时(分钟)|
|-- |-- |-- |-- |
|Planning|计划|30|30|
|• Estimate|• 估计这个任务需要多少时间|30|30|
|Development|开发|2520|2800|
|• Analysis|• 需求分析|240|240|
|• Discuss|• 结对讨论|120|120|
|• Study|• 学习对应技术|900|1200|
|• Design Spec|• 生成设计文档|120|180|
|• Design Review|• 设计复审|60|60|
|• Design|• 界面设计|180|240|
|• encode|• 具体编码实现|900|1260|
|Reporting|报告|210|210|
|• Report|• 写总结|120|120|
|• Size Measurement|• 计算工作量|30|30|
|• Postmortem & Process Improvement Plan|• 事后总结, 并提出过程改进计划|60|60|
|合计||2760|3040|
# 成品展示
主页搜索:
描述:在主页输入搜索词搜索(返回按关键词和标题)
![](https://blog-static.cnblogs.com/files/blogs/664396/%E4%B8%BB%E9%A1%B5%E6%90%9C%E7%B4%A2.gif)
原文链接:
描述:在搜索结果点击查阅原文可跳转到官网的原文地址
![](https://blog-static.cnblogs.com/files/blogs/664396/%E5%8E%9F%E6%96%87%E9%93%BE%E6%8E%A5.gif)
下载论文:
描述:扩展了下载功能,将以爬取的部分论文的pdf传到服务器,以破石头请求获取文件下载流实现下载
![](https://blog-static.cnblogs.com/files/blogs/664396/%E4%B8%8B%E8%BD%BD2.gif)
实现收藏夹:
描述:利用cookie保存自己想保存的关键字,点击可实现跳转搜索
![](https://blog-static.cnblogs.com/files/blogs/664396/%E6%94%B6%E8%97%8F.gif)
实现库搜索:
![](https://blog-static.cnblogs.com/files/blogs/664396/%E5%BA%93%E6%90%9C%E7%B4%A2.gif)
分析界面:
描述:分析各类的论文近几年的热词,并提供所有热词的频度分析和快捷搜索
![](https://blog-static.cnblogs.com/files/blogs/664396/%E5%88%86%E6%9E%90.gif)
# 结对讨论过程描述
·第一次看到题目的时候,我们先约在咖啡厅里面讨论,首先先对项目进行了需求分析,分析并确定了要使用的语言和框架以及还需掌握的知识,而且一起制定了共同的代码规范。
![](https://images.cnblogs.com/cnblogs_com/blogs/664396/galleries/1929644/o_210330172935jdw1.png)
·再需求分析后,后端代码实现的过程中,一起讨论了前端可能需要的api返回数据和格式
![](https://images.cnblogs.com/cnblogs_com/blogs/664396/galleries/1929644/o_210330172939jdw2.png)
·在线上一般会用qq聊天,方便的时候会使用qq电话📞和屏幕共享💻
![](https://images.cnblogs.com/cnblogs_com/blogs/664396/galleries/1929644/o_210330172943jdw3.png)
·在遇到问题时,我们会同享代码,一起分析问题所在,同时,我们会对彼此的代码和所实现的功能进行复审,以减少出现程序故障的情况。
![](https://images.cnblogs.com/cnblogs_com/blogs/664396/galleries/1929644/t_210330172948jdw4.png)
# 设计实现过程(前后端分离)
前端:
- 使用了bootstrap开发框架。
- 通过AJAX来获取json数据
- 通过js来进行主要功能的实现
- 设置了主页、库页、分析页、收藏页、结果页,实现了搜索、显示删除、可视化分析、增加删除收藏,显示搜索结果等功能
后端:
- 使用spring boot + Mybatis框架。
- 封装了实体类Paper,和keywordanalysis来对数据进行传递。
- 在dao层中,设计了各种PaperMapper,keyWordMapper接口,定义了访问数据库的方法,主要用于实现数据持久化。
- 在service层中,通过调用dao层的方法,定义了服务keywordService和paperService,实现了业务模块的应用逻辑。
- 在controller中,通过调用service层,定义了控制类keywordController和paperController,来实现具体业务模块流程的控制
。
功能模块图:
![](https://images.cnblogs.com/cnblogs_com/blogs/664396/galleries/1929644/o_210331032206jdw5.png)
# 代码说明
实现动态添加图表JS代码
```
function addall_canvas() {
var div1 = document.createElement("div");
div1.style.height = "720px";
div1.style.width = "540px";
div1.style.float = "left";
div1.innerHTML = "<canvas id= \"allChart1\"></canvas><canvas id= \"allChart2\"></canvas>";
document.getElementById("allyearall").appendChild(div1);
}
```
实现收藏的增加、删除的主要JS代码 (初始化与其类似)
```
addbtn.onclick = function add_div() {
var love = prompt("请输入收藏关键字:","");
if(love != null){
var div = document.createElement("div");
div.className = "form-group";
div.id = "ltags" + detail_div;
if(love != null){
var index = detail_div;
div.innerHTML = '<button type="button" class="btn btn-primary btn-lg btn3d" onclick="totags(this)"><span class="glyphicon glyphicon-tag">'+love+"</button><button type=\"button\" class=\"close\" aria-label=\"Close\" onclick='deleteTag(this,"+index+")'><span aria-hidden=\"true\">×</span></button>";
}
document.cookie="lovetag"+detail_div+"="+love+";path=/";
document.getElementById("lovetag").appendChild(div);
detail_div++;
}
}
```
```
function deleteTag (obj,index){
detail_div--;
var dallcookies = document.cookie;
// Get all the cookies pairs in an array
var dcookiearray = dallcookies.split(';');
for(var i = index; i<detail_div; i++){
var dname = dcookiearray[i].split('=')[0];
var dvalue = dcookiearray[i+1].split('=')[1];
document.cookie = dname+"="+dvalue+";path=/";
}
document.cookie = "lovetag"+detail_div+"=;path=/";
obj.parentNode.parentNode.removeChild(obj.parentNode);
}
```
前端获取数据代码
```
{
if(window.XMLHttpRequest){
xmlhttp1 = new XMLHttpRequest();
}else{
xmlhttp1 = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlhttp1.onreadystatechange = function() {
if(xmlhttp1.readyState == 4){
if(xmlhttp1.status == 200) {
var str = xmlhttp1.responseText;
var arr = eval('('+str+')');
jsonlength = arr.length;
for(var i = 0; i < jsonlength; i++){
var temp = i%10 ;
if( arr[i].type == "CVPR"){
if( temp == 0){
CVPRyear[chartnum1] = arr[i].publishyear;
add_canvas("CVPR");
}
var publishyear = arr[i].publishyear;
CVPRKeyword[publishyear][temp] = arr[i].keyword;
CVPRfrequency[publishyear][temp] = arr[i].frequency;
}
}
}
}
}
xmlhttp1.open("POST",url,true);
xmlhttp1.setRequestHeader("Access-Control-Allow-Origin","*");
xmlhttp1.send("{\"type\":\"CVPR\"}");
}
```
**后端主要代码**(以实体类paper为例,代码为美观易于理解做了紧凑处理)
paper实体类:
```
public class paper {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof paper)) return false;
paper paper = (paper) o;
return Objects.equals(id, paper.id);
}
private Integer id;
private String typeandyear;
private String releasetime;
private String link;
private String title;
private String abstractcontext;
private String keyword;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getTypeandyear() {returntypeandyear;}
public void setTypeandyear(String typeandyear) {
this.typeandyear = typeandyear == null ? null : typeandyear.trim();
}
public String getReleasetime() {
return releasetime;
}
public void setReleasetime(String releasetime) {
this.releasetime = releasetime == null ? null : releasetime.trim();
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link == null ? null : link.trim();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title == null ? null : title.trim();
}
public String getAbstractcontext() {
return abstractcontext;
}
public void setAbstractcontext(String abstractcontext) {
this.abstractcontext = abstractcontext == null ? null : abstractcontext.trim();
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword == null ? null : keyword.trim();
}
@Override
public String toString() {
return "paper{" +
"id=" + id +
", typeandyear='" + typeandyear + '\'' +
", releasetime='" + releasetime + '\'' +
", link='" + link + '\'' +
", title='" + title + '\'' +
", abstractcontext='" + abstractcontext + '\'' +
", keyword='" + keyword + '\'' +
'}';
}
}
```
PaperMapper:
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.geiyepa.demo.mapper.paperMapper">
<resultMap id="BaseResultMap" type="com.geiyepa.demo.entity.paper">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="typeandyear" jdbcType="VARCHAR" property="typeandyear" />
<result column="releasetime" jdbcType="VARCHAR" property="releasetime" />
<result column="link" jdbcType="VARCHAR" property="link" />
<result column="title" jdbcType="LONGVARCHAR" property="title" />
<result column="abstractContext" jdbcType="LONGVARCHAR" property="abstractcontext" />
<result column="keyword" jdbcType="LONGVARCHAR" property="keyword" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.geiyepa.demo.entity.paperWithBLOBs">
<result column="title" jdbcType="LONGVARCHAR" property="title" />
<result column="abstractContext" jdbcType="LONGVARCHAR" property="abstractcontext" />
<result column="keyword" jdbcType="LONGVARCHAR" property="keyword" />
</resultMap>
<sql id="Base_Column_List">
id, typeandyear, releasetime, link
</sql>
<sql id="Blob_Column_List">
title, abstractContext, keyword
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from paper
where id = #{id,jdbcType=INTEGER}
</select>
<select id="getPaper" parameterType="java.lang.Integer" resultType="com.geiyepa.demo.entity.paper">
SELECT
<include refid="Base_Column_List" />
FROM paper WHERE id = #{id}
</select>
.......
```
Service:
```
@Service
@Component
public class paperService implements paperMapper{
@Autowired
private paperMapper paperMapper;
@Override
public int deleteByPrimaryKey(Integer id) {
return 0;
}
@Override
public List<paper> selectLikeKeyword(String keyword) {
return paperMapper.selectLikeKeyword(keyword);
}
...
}
```
Controller 的搜索接口:
```
@ResponseBody
@RequestMapping(value = "/searchPaper" , method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public JSONArray search(@RequestBody String JSONBody){
JSONObject object = JSONObject.parseObject(JSONBody);
String searchKeyword = (String) object.get("searchKeyword");
List<paper> paperList1 = paperService.selectLikeKeyword("%"+ searchKeyword +"%");
List<paper> paperList2 =paperService.selectLikeWord("%"+ searchKeyword +"%");
for (paper p2:paperList2
) {
int flag = 0;
for (paper p1:paperList1
) {
flag = 0;
if(p1.equals(p2)) flag = 1;
}
if(flag ==0 ) paperList1.add(p2);
}
JSONArray array= JSONArray.parseArray(JSON.toJSONString(paperList1));
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
Date date = new Date(System.currentTimeMillis());
System.out.println(formatter.format(date) + " ====> 搜索文章 ##搜索关键词:" + searchKeyword + " ##搜索结果数:" + paperList1.size());
return array;
}
```
## **代码描述与实现思路:**
**cmy**主要负责后端代码的编写和服务器的架设,项目部署。如上,后端使用Springboot + Mybatis框架。以paper为例,首先使用Mybatis的generator生成Bean,Dao,Mapper的代码,在根据实际需要稍作修改,创建PaperService类注入paperMapper,因为本次项目的逻辑简单直接调用dao的接口就行,接着编写controller 注入paperService,格式化数据实现接口功能。
**cq**主要负责前端的页面显示和图表制作,使用bootstrap开发框架+原生js,保证美观的同时加快开发效率。按照接口文档的描述发起破石头请求,将获得的String类型的响应体格式化为JSON,利用DOM和bootstrap的各类class的模板动态添加div到页面。
# 心路历程和收获
陈起:
在此次的结对编程中,因为我的项目经验比较少,实现的过程中有的概念会比较模糊,在此之前,对于一些框架也不了解,所以在项目前期,我比较没有头绪,大部分时间都在进行学习新的技术的过程中,但是后面投入实践中,我的队友总是会很耐心的跟我解释,让我加深理解了什么是框架,也学会不少新的知识,提高了自身能力。因为是两个人结对合作,所以也锻炼了书写规范的代码。这次结对合作,我们在磨合的过程中锻炼并成长,在合作的过程中学习并进步,对我们而言,是一次很有帮助的实践。
陈明煜:
在此次的结对编程中,虽然我之前有过类似的项目经验,但是在这次的实践过程中还是有我之前没有接触过或者是不够熟练的技术,于是在项目前期,我的大部分时间也都在进行学习新的技术,所以我会主动去承担比较复杂的部分,同时在需要的时候主动去给我的队友一些帮助;这一次的实践,使我对springboot这一框架的使用熟练度大大提升,同时学会了Mybatis使用generator自动生成代码,理解的逆向工程的思路,提高了自身能力,也学到了新的知识,加深了的不只是友谊,更是对知识的掌握,不得不说,这次结对编程是一次很有帮助的实践。
# 评价结对队友
陈起对陈明煜的评价:
陈明煜是一个不错的队友,不管是前端还是后端,都是比较熟练的项目经验比较多的,而我的项目经验比较少,结对过程中有的概念会不了解,这时他总是会很耐心的跟我解释,使我们的工作完美完成。工作认真负责,细心,学习能力很强,接受新事物快,理解能力强,完成工作任务,然后也很感谢耐心回答我的问题,有机会的话希望能配合的像和这次一样的好。
陈明煜对陈起的评价:
陈起是一位很优秀的队友,虽然他之前的项目经验可能比较少,但是他很谦虚,会比较积极主动学习新知识,项目中有什么不会的地方,也会立即直接地提出来一起讨论解决,同时遇到新事物也会有自己的思考,有时会想出很好的点子,在编程过程中遇到问题,我们都能一起配合解决,与他合作是一次非常愉快的合作体验。