结对作业二
结对作业二
这个作业属于哪个课程 | 2021春软件工程实践 W班(福州大学) |
---|---|
这个作业要求在哪里 | 结对第二次作业 |
这个作业的目标 | 顶会热词统计的实现 |
其他参考文献 | 无 |
一、github仓库地址和代码规范
二、云服务器访问链接
三、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
• Estimate | • 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 3590 | 3815 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 180 |
• Design Spec | • 生成设计文档 | 60 | 45 |
• Design Review | • 设计复审 | 20 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
• Design | • 具体设计 | 120 | 160 |
• Coding | • 具体编码 | 3000 | 3200 |
• Code Review | • 代码复审 | 60 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 180 | 160 |
Reporting | 报告 | 140 | 130 |
• Test Repor | • 测试报告 | 60 | 60 |
• Size Measurement | • 计算工作量 | 20 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 3740 | 3955 |
四、成品展示
搜索页注册及登录
点击右上角头像框登录或注册,账号及密码均需6位以上
搜索页搜索及分页加载
搜索框输搜索框输入关键词后跳转到主页,加载出对应的文章列表,向下滑动到底部加载第二页文章,分页为5篇文章/页
主页——搜索论文列表页
- 文章右边点击复选框可选中这篇文章,在文章列表顶部点击爬取论文,右上角复选框可对当前列表已加载文章进行全选
顶部搜索框可对当前已加载列表进行二次本地过滤搜索,未设置搜索按钮,实时检测输入内容
- 左上角logo点击回到搜索页
- 右上角头像点击:未登录跳出登录注册窗口、已登录跳出管理账号窗口
4.点击文章可跳转到原文链接
主页——数据统计页
- 左上角为TOP40热词词云图,右上角为TOP10热词玫瑰图;
第二列为某一热词对应的在三大顶会中的热度趋势(出现的文章篇数);
第三列为当前热词对应的文章列表。
- 在上面两个图中点击任意热词可在下面的柱状图生成对应热词的热度趋势和论文列表。
主页——爬取记录页
这一页面要求登录后才能使用,列表中为用户在搜索论文列表页点击爬取论文后的文章。
这一页面同样可以对列表进行过滤搜索以及删除管理
修改信息页
用户在这里修改个人信息
- 点击头像框上传头像。
- 点击账号名右边的小铅笔修改账户名,再次点击小铅笔保存。
- 密码修改需要输入旧密码以及新密码,点击确认按钮提交修改密码。
退出登录
五、结对讨论过程描述
- 主要形式:线下
因为两个人在实验室的位置都靠一块,所以平时讨论都是在线下,一般开发时间都能直接讨论,如果某一端有需求都能够直接处理,两边的进度都是实时的。
- 需求讨论
根据作业给的要求,主要的需求映射到开发的功能:
1.得有一个服务器2.论文数据在服务器的存储
3.前端实现交互的功能:登陆注册、论文查询、论文收藏、论文统计
4.后端实现数据处理和接口:论文数据处理、登陆注册查询收藏统计接口、数据过滤、接口优化
- 初期分工讨论
一个想做前端一个想做后端,其实分工还是比较明确的,所以前期我们选择spring boot + Vue前后端分离的结构
- 中期进度讨论
主要是前端对后端接口数据需求改变,因为前端显示的数据比如论文标题、关键字、热词统计的数据结构有要求,所以开发中期前后端的交互主要是对接口数据进行讨论
- 后期测试与优化
在把前端部署到服务器后还是发现了有部分地方存在bug和瑕疵,所以在后期还是将前端的页面打回修改了很多次
以及在后期测试方面发现服务器http请求响应时间过长,对后端的程序算法进行了一定的优化
- 线上讨论截图
六、设计实现过程
设计架构图
后端
数据库表
部分表结构
后端架构
后端开发语言Java 使用框架Spring boot
前端
前端采用Vue进行开发,开发工具为WebStorm 2020.3,测试接口工具Postman
项目中使用Element-ui、vue-wordcloud、echarts等框架。
七、关键代码描述
后端
Artical类
public class Artical
{
public String title; //论文标题
public String link; //原文链接
public String Abstract; //原文摘要
public int academicNum; //论文在服务器上的编码(做逻辑外键使用)
public String magazine; //出版期刊
public int year; //发布年份
}
User类
public class User implements Serializable
{
public String account; //账号
public String password; //密码
public String username; //用户名
public String description; //备注
}
ArticalService类
public interface ArticalService
{
List<Artical> searchArtical(String keywords); //通过keyword搜索文章
List<keywords> searchKeywords(int academicNum); //通过文章id获取文章关键字
List<authors> searchAuthors(int academicNum);//通过文章id获取文章作者
int Collect(String username,int [] academicNum);//收藏功能
List<Artical> getCollection(String username);//获取用户收藏列表
Set<String> getAllkeywords();//获取全部keyword
int deleteCollections(String username,int []academicNum);//删除用户收藏
List<hotkey> top20();//获取热词列表
}
UserService类
public interface UserService
{
User getUserByAccount(String account,String Password); //搜索用户(登陆用)
boolean Register(String Account,String Password); //注册
int getUserByAccount(String account);//搜索用户(获取信息用)
String saveImg(String username,String imgUrl);//上传头像
String getImg(String account);//获取头像
boolean changeInfo(String Account,String oldPassword,String newPassword,String username);//用户修改密码
boolean changeInfoWithoutPsw(String Account,String username);//用户修改个人信息
}
搜索文章方法
@GetMapping("/search")
public ajAxResponse search(@RequestParam String keyword,@RequestParam(required = false)Integer pageNum)
{
List<Artical> list = articalService.searchArtical(keyword);
List<FullArtical> full = new ArrayList<FullArtical>();
int totalNum = (list.size()/5 )+(list.size()%5==0?0:1); //计算搜索文章页数
if(pageNum == null)//PageNum是可选参数 默认为0
{
pageNum = 0;
}
if(pageNum > totalNum)
{
return ajAxResponse.fail("页数超了!");
}
//获取指定页的文章(一页5篇)
for(int i = pageNum*5;i < pageNum*5+5&&i < list.size();i++)
{
FullArtical art = new FullArtical();
art.artical = list.get(i);
//获取文章作者
List<authors> authors = articalService.searchAuthors(art.artical.academicNum);
//获取文章关键字
List<keywords> keywords = articalService.searchKeywords(art.artical.academicNum);
List<String> realKeyword = new ArrayList<String>();
List<String> realAuthor = new ArrayList<String>();
for (authors au:
authors ) {
realAuthor.add(au.author);
}
for (keywords key:
keywords) {
realKeyword.add(key.keyword);
}
art.authors = realAuthor;
art.keywords = realKeyword;
full.add(art);
}
//分页
PageModel page = new PageModel();
page.totalNum = totalNum;
page.CurrentNum = pageNum;
page.list = full;
return ajAxResponse.successfully(page);
}
前端
页面跳转
采用vue-router来实现页面跳转
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect:'/search'
},
{
path: '/search',
name: 'Search',
component: Search
},
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/manageAccount',
name: 'ManageAccount',
component: ManageAccount
}
]
const router = new VueRouter({
routes
})
export default router
登录功能
采用vuex储存当前用户的账户号、用户名、头像链接
Vue.use(Vuex)
export default new Vuex.Store({
state: {
username:'',
avatarUrl:'',
account:'',
},
mutations: {
setUsername:function(state,loginUsername){
state.username=loginUsername;
sessionStorage.setItem("username", loginUsername);
},
setAvatarUrl:function(state,loginAvatarUrl){
state.avatarUrl=loginAvatarUrl;
sessionStorage.setItem("avatarUrl", loginAvatarUrl);
},
setAccount:function(state,loginAccount){
state.account=loginAccount;
sessionStorage.setItem("account", loginAccount);
},
},
actions: {
},
modules: {
}
})
论文搜索功能
使用axios表单请求论文列表,请求中给出当前页数,实现分页请求。
search(){
const loading = this.$loading({
lock: true,//lock的修改符--默认是false
text: '加载中...',//显示在加载图标下方的加载文案
spinner: 'el-icon-loading',//自定义加载图标类名
background: 'rgba(226,226,226,0.7)',//遮罩层颜色
target: document.querySelector('.collection_frame')//loading覆盖的dom元素节点
});
if((this.pageNum<this.totalPageNum||this.totalPageNum===0)&&this.searchWord!==undefined){
axios
.get('http://121.5.100.116:8080/api/search?keyword='+this.searchWord+'&pageNum='+this.pageNum)
.then(response=>{
this.papers=this.papers.concat(response.data.data.list);
for(var paper of response.data.data.list){
this.paperIds.push(paper.artical.academicNum)
}
this.totalPageNum=response.data.data.totalPageNum;
this.totalPaperNum=response.data.data.totalNum;
if(this.pageNum===0){
if(this.totalPageNum!==0){
this.isEmpty=false
this.searchSuccess(this.totalPaperNum)
}else{
this.$message({
message:'未搜索到文章',
type:'warning'
});
this.isEmpty=true
}
}else{
this.$message({
message:'请求第'+(this.pageNum+1)+'页',
type:'success'
});
}
})
.finally(()=>{
this.pageNum++;
this.isReadyLoad=true
loading.close()
})
}else{
this.$message({
message:'没有更多了',
type:'warning'
});
loading.close()
}
}
页面触底请求下一页的函数
//触底触发函数
listenBottomOut(){
let scrollTop=document.documentElement.scrollTop||document.body.scrollTop;
let clientHeight=document.documentElement.clientHeight;
let scrollHeight=document.documentElement.scrollHeight;
if(scrollTop+clientHeight>=scrollHeight-1&&this.isReadyLoad===true){
this.isReadyLoad=false
this.search()
}
},
数据统计页面
监听当前组件的数据结构,变化时调用echarts的构造函数重新画图,并请求相关论文列表重新填充
watch:{
top10Data(){
this.drawLine()
},
hotTrendWord(){
this.$message.success('当前热词为'+this.hotTrendWord)
this.fillHotWordChart()
this.$refs.searchList2.$props.searchWord=this.hotTrendWord
Object.assign(this.$refs.searchList2.$data,this.$refs.searchList2.$options.data())
this.$refs.searchList2.search()
},
CVPRData(){
this.drawLineChart()
}
},
八、心路历程收获
081800330吴尚辉:
这次作业还是比较有意义的,因为以前都是做的前端,没怎么接触过后端,这次作业基本是从零开始学后端,从一开始的菜鸟到现在会基本的接口编写,以及后面服务器的部署,离全栈又近了一步。
221801201凌铧钦
这次作业体会了一下三天速成Vue的快感,学到新的技术栈非常爽,在学习过程中体会到了学新技术的时候看什么第三方教程都不如看官方文档(当然前提条件是官方文档写得不错),在这次作业完成后,对前端技术的理解更加深刻了。
九、评价结对队友
221801201凌铧钦对081800330吴尚辉评价:
结对过程中非常流畅,由于后端部署在服务器上的速度非常迅速,所以测试接口过程中非常舒爽、优雅,在实现过程中因为在后端已经解决例如跨域请求这种问题,所以编写网络请求的逻辑时没有出现太多问题。后端生成的接口文档也比较好使。每次给后端提需求也能很快速地实现。
081800330吴尚辉对221801201凌铧钦评价:
结对过程还是很顺利的,前端的技术还是比较不错的,两个人在讨论和后面修改需求或者更改接口数据等也谈的比较融洽,结对过程中也比较主动的推进度,主要是对需求的定位比较准确,后面也增加了很多比较合理和实用的建议,世界上最好的前端,lhq!