结对作业二——顶会热词统计的实现
这个作业属于哪个课程 | 2021软件工程实践|W班 (福州大学) |
---|---|
这个作业要求在哪里 | 结对第二次作业——顶会热词统计的实现 |
结对学号 | 221801129、221801322 |
这个作业的目标 | 实践Github协作开发、实现顶会热词统计 |
其他参考文献 | Gin框架学习、Gorm、Gin、Vue |
Github仓库链接和代码规范链接
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟 | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | ||
· Analysis | · 需求分析(包括学习新技术) | 480 | 600 |
· Design Spec | · 生成设计文档 | 30 | 60 |
· Design Review | · 设计复审 | 30 | 60 |
· Coding Standard | · 代码规范(为目前的开发指定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 1800 | 2100 |
· Code Review | · 代码复审 | 60 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 480 | 600 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 30 | 60 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 时候总结,并提出过程改进计划 | 30 | 60 |
Total | 合计 | 3055 | 3775 |
成品展示
1、进行登录并回到首页
2、首页搜索后跳到结果页展示论文列表
3、对论文列表进行分页展示
4、点击top10关键字展示相关搜索列表
5、三大顶会热词趋势展示
6、按关键词搜索显示论文列表
7、论文列表收藏
8、论文列表删除
9、在结果页按论文题目搜索显示论文列表
10、按论文编号搜索显示论文列表
结对讨论过程描述
收到需求后,两人还是有点懵的,因为确实感觉会来不及完成。然后,因为在第一次作业的时候就有讨论一些了,所以一开始两人的实现思想就很接近了,后面进行打代码过程的时候交流就比较顺利了
1.对于接口数据交互的讨论
2.对于关键词TOP10接口的讨论
3.结对使用局域网,进行代码测试
设计实现过程
前端实现过程
前端使用html
+css
+js
语言以及jquery
+vue
框架进行开发
-
页面设计
整体页面的样式和布局做到尽量还原原型设计。首页、搜索结果页、收藏夹页共用一个
nav
,论文列表、热词top10通过vue
框架的v-for
指令进行循环显示。刚进首页时隐藏显示登录框,登录成功或点击除登录框外其他地方时登录框淡出。三大顶会的趋势图通过标签页形式在一个空间内切换显示。 -
前后端交互
前后端交互主要用
ajax
技术实现,统一进行post
请求,请求参数和返回参数均为json
格式,在请求时加上请求头"Content-Type": "application/json"
。在传入请求参数时通过JSON.stringify()
将参数转成json格式,对不能在回调函数内处理的数据通过localStorage
存储起来,然后其他地方取出数据并处理。在回调函数中将响应参数进行相应处理并转成json
格式,便与后续其他地方对数据的处理和展示。
后端实现过程
后端使用go
语言进行开发
-
数据库设计
根据用户的需求,建立了一个
article
和keyword
表。考虑到一篇文章有多个关键词,一个关键词又可以对应多篇文章,一开始想要设计一个多对多的关联表, 但是,一开始因为对gorm
API的不熟悉,存数据的过程中出现了很多问题无法解决。所以存论文数据的时候,将数据库的表修改成了一对多的模式,方便了存数据,不可避免造成了数据的冗余。(项目后期发现解决方法,受于代码结构,没能更改)因为设计了收藏夹功能,创建了
user
和bookmark
表,bookmark
实现user
和article
表的多对多关联-
article表
-
keyword表
-
user表
-
bookmark表
-
-
后端代码
使用MVC模式进行开发,
model
层使用go
的第三方库gorm
进行数据库交互,controller
层采用轻量级的gin
框架,负责调用model
层与前端进行数据交互
代码说明
前端代码
1、论文列表展示
<div class="list">
<div class="listContent" v-for="list in lists">
<div class="title lineBreak" :data-paper-uid="list.article_id">
<h2 :title="list.title">{{ list.title }}</h2>
</div>
<div class="abstract">
<span>摘要:</span>
<div>
<p class="wrapBreak" :title="list.abstract">
{{ list.abstract }}
</p>
</div>
</div>
<div class="listBottom">
<span>关键词:</span>
<div class="keyword lineBreak" v-for="key in list.keyword" :title="key">{{ key }}</div>
</div>
<img src="../img/Result/delete.svg" class="deleteSvg" :id="delete(index)" alt="delete" title="删除">
<img src="../img/Result/mark.svg" class="markSvg" alt="mark" title="收藏">
</div>
<button id="btn" @click="getLists()" style="border-width: 0;"></button>
</div>
2、热词top10展示
<ul id="rank">
<li v-for="topRank in topRanks">
<div class="num-box">
<span :id="num(topRank.index)">{{ topRank.index }}</span>
</div>
<div class="name-box lineBreak" :title="topRank.name">
<span :class="name(topRank.index)" @click="rankClick(topRank.name)">{{ topRank.name }}</span>
</div>
<div class="total-box lineBreak" :title="topRank.count">
<span>{{ topRank.count }}</span>
</div>
</li>
</ul>
3、展示论文列表的js代码
function showList(urlStr,searchVal){
PostHandle(urlStr, JSON.stringify(searchVal), function(data){
if(data.code == 200){
//console.log(data.data.articlelist[0].article_id);
var articlelist = data.data.articlelist;
var len = articlelist.length;
var lists = [];
for(var i = 0 ; i < len ; i++){
var keywords = [];
var keylen = articlelist[i].Keywords.length;
if(keylen <= 3){
for(var j = 0 ; j < keylen ; j++){
keywords[j] = articlelist[j].Keywords[j].name;
}
} else {
for(var k = 0 ; k < 3 ; k++){
keywords[k] = articlelist[k].Keywords[k].name;
}
}
lists[i] = {
title: articlelist[i].title,
abstract: articlelist[i].abstract,
article_id: articlelist[i].article_id,
keyword: keywords
};
};
localStorage.setItem("lists",JSON.stringify(lists));
localStorage.setItem("totalPage",data.data.pagetotal);
$("#btn").trigger("click");
} else {
alert(data.code + " " + data.msg);
}
});
}
function getList(){
return JSON.parse(localStorage.getItem("lists"));
}
var app1 = new Vue({
el: '.list',
data: {
lists: getList()
},
methods:{
getLists : function(){
this.lists = getList();
}
}
});
4、分页点击展示论文列表
methods: {
btnClick: function(data){
if(data != this.cur){
this.cur = data;
}
console.log(this.cur+'页');
//var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
var urlStr = "http://192.168.0.110:8000/list";
var searchVal = {
pagenum: this.cur,
type: 1,
searchval: localStorage.getItem("searchVal")
};
showList(urlStr,searchVal);
},
pageClick: function(){
console.log('现在在'+this.cur+'页');
//var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
var urlStr = "http://192.168.0.110:8000/list";
var searchVal = {
pagenum: this.cur,
type: 1,
searchval: localStorage.getItem("searchVal")
};
showList(urlStr,searchVal);
},
},
5、热词top10展示的js
//var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/rank";
var urlStr = "http://192.168.0.110:8000/rank";
var rankVal = {
type:"rank"
};
PostHandle(urlStr, JSON.stringify(rankVal), function(data){
if(data.code == 200){
var topList = data.data.top_list;
var topRanks = [];
for(var i = 0 ; i < 10 ; i++){
topRanks[i] = {
name: topList[i].name,
count: topList[i].count,
index: i+1
};
}
var app = new Vue({
el: '#rank',
data: {
topRanks: topRanks,
},
methods: {
num: function(index){
return "num" + index;
},
name: function(index){
return "name" + index;
},
rankClick :function(data){
//var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
var urlStr = "http://192.168.0.110:8000/list";
var searchVal = {
pagenum: 1,
type: 2,
searchval: data
};
showList(urlStr,searchVal);
}
}
});
} else {
alert(data.code + " " + data.message);
}
});
后端代码
-
返回所有顶会的热词TOP10列表
func GetTopKeywordList() (results []KeyNum) { var tmp1, tmp2 interface{} rows, _ := db.Raw("SELECT name, count(*) FROM papercrawler.crawler_keyword group by name order by count(*) desc").Limit(10).Rows() defer rows.Close() for rows.Next() { rows.Scan(&tmp1, &tmp2) count, _ := strconv.Atoi(string(tmp2.([]byte))) name := string(tmp1.([]byte)) results = append(results, KeyNum{ Name: name, Count: count, }) } return }
-
返回数据用于渲染趋势图(两段代码结合使用)
//相关结构体 type Chart struct { Forum string `json:"forum"` Years []string `json:"years"` KeyValues []KeyValue `json:"key_values"` } type KeyValue struct { Name string `json:"name"` Counts []int `json:"counts"` } type KeyYearCount struct { Name string `gorm:"column:name"` Year string `gorm:"column:year"` Count int `gorm:"column:count(*)"` }
//获取某个顶会TOP关键词 func GetTOPByForum(forum string, size int) (topkeys []TopKey) { db.Raw("SELECT forum, name, count(*) FROM papercrawler.crawler_keyword where forum = '" + forum + "' group by name order by count(*) desc").Limit(size).Find(&topkeys) return }
//获取图表所需数据 func GetChart(forum string, topkeys []TopKey) (chart Chart) { var keyyearcounts []KeyYearCount for _, topkey := range topkeys { db.Raw("SELECT name, year, count(*) FROM crawler_keyword WHERE forum = '" + topkey.Forum + "' " + "and name = '" + topkey.Name + "'").Group("year").Find(&keyyearcounts) var keyvalue KeyValue keyvalue.Name = keyyearcounts[1].Name if forum == "ECCV" { for i := 0; i < 11; i++ { keyvalue.Counts = append(keyvalue.Counts, 0) } for _, keyyearcount := range keyyearcounts { index, _ := strconv.Atoi(keyyearcount.Year) //获取对应的年份 keyvalue.Counts[(index-2000)/2] = keyyearcount.Count //给对应年份赋值 } } else { for i := 0; i < 21; i++ { keyvalue.Counts = append(keyvalue.Counts, 0) } for _, keyyearcount := range keyyearcounts { index, _ := strconv.Atoi(keyyearcount.Year) //获取对应的年份 keyvalue.Counts[index-2000] = keyyearcount.Count //给对应年份赋值 } } chart.KeyValues = append(chart.KeyValues, keyvalue) } chart.Forum = forum if forum == "ECCV" { for i := 2000; i <= 2020; i = i + 2 { chart.Years = append(chart.Years, strconv.Itoa(i)) } } else { for i := 2000; i < 2021; i++ { chart.Years = append(chart.Years, strconv.Itoa(i)) } } return }
心路历程和收获
- 221801129
- 之前虽然有过一次web开发经验,但是是使用JAVA原生的servlet作为后端的开发,重复的代码部分无法有效的复用,深刻地体会到原生的开发效率之低,所以这次便下定决心要使用框架进行一次前后端分离的开发
- 在结对作业之前,先进行了组队,由于团队的后端开发采用
go
语言和gin
框架,便趁此机会学习并应用,为后面的团队开发打下基础 - 在学习和实践过程中,虽然老师延长了期限,但是因为先进行了框架和语言的学习,导致最后代码时间有点赶。所以感觉在有实践项目时,最好采用自己熟悉的框架和语言进行开发,然后在课余时间进行相关的学习,加强自己对框架的理解和运用
- 两人结对,因为前后端分离,一开始两人都是在各做各的,缺少交流,有点串行的合作,导致效率不是很高,也进而导致了一些功能没有实现
- 虽然又很多遗憾,但是还是学到了宝贵的后端知识
- 221801322
- 之前有过一些web项目经历,但由于自身web基础不够扎实,所以实现起来效果不是特别好,所以就趁这次结对巩固下web基础并学习一些新知识。
- 结对的过程中经历了一次团队实践,所以发现了并纠正了自己前后对接中出现的问题。这也使得后面结对编程的前后端对接不会出现类似的bug,使结对进行得更加顺利。
- 结对项目中使用了一些vue框架,虽然方便了代码编写,但也由于自身对vue的了解不深,使得相关的bug更容易出现并且更难修好。
- 结对过程中比较少给对方催进度,虽然这样每个人编写代码的自由度大,但也容易使得每个人的进度变慢,影响到了最后的实现。
评价结对队友
-
221801129 to 221801322
这次的结对整体而言还是挺ok的,队友在前端方面有能力照着原型图构造页面,在讨论接口的时候能给出一些合理的建议,交流沟通虽然有些分歧,但是最后总能达成共识。同时,队友能够在时间紧迫的情况下,还能比较冷静地debug。这次结对,虽有遗憾,两人都有一定责任,但是我对于整体还是持积极的态度(生活不易)。希望我俩在这次结对后,都有反思,都能有进步
-
221801322 to 221801129
这次结对总体还是比较满意的,沟通过程也较为顺利。队友的后端能力很可靠,所以自己能够更专注于前端工作。在构造接口时,队友能够结合前端的需求改进相应的api,为前端的页面编写提供了思路。队友进行后端开发很不容易,而且还得学新框架,所以也没有去特意催进度。希望在这次结对后,都能有所收获,并将结对经验应用到今后的学习生活中。