结对作业2
这个作业属于哪个课程 | 2021春软件工程实践|S班 |
---|---|
这个作业的完成者的学号 | 221801209 221801222 |
这个作业要求在哪里 | 结对作业2 |
这个作业的目标 | 1、对结对作业一原型的实现 2、操作爬取的列表 3、图表展示 4、部署 5、附加功能 |
其他参考文献 | 《Vue.js官方文档》 《element UI》官方文档 |
git仓库链接和代码规范链接
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 25 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 25 |
Development | 开发 | 1440 | 1580 |
• Analysis | • 需求分析 (包括学习新技术) | 30 | 25 |
• Design Spec | • 生成设计文档 | 20 | 20 |
• Design Review | • 设计复审 | 10 | 12 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 25 |
• Design | • 具体设计 | 20 | 20 |
• Coding | • 具体编码 | 1120 | 1230 |
• Code Review | • 代码复审 | 30 | 28 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 70 | 90 |
• Test Repor | • 测试报告 | 45 | 60 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 1575 | 1595 |
网站访问链接
[http://ccreater.top:63000/](http://ccreater.top:63000/)
成品展示
注册
- 登录
搜索界面
- 支持按论文标题、关键字、作者检索
- 可跳转至论文原网站
- 支持收藏
排序
- 因为展示界面所采用的是表格,element-ui的表格有排功能,所以只需自己实现排序规则函数,写起来很方便
分页
- 用户可选择每页展示页数
- 前进一页,后退一页
收藏页面
跳转和取消收藏
检索
- 当收藏列表中不含检索内容,将从数据库中检索并展示
- 否则展示用户收藏内容中检索到的内容
数据分析展示界面
- 可自定义选择年份(交互性),根据年份动态生成图表
- 展示的图标有:玫瑰图,柱状图和词云图三种
- 图表可下载
结对讨论过程描述
为了队友之间及时交流、高质量交流,我们有共享文档、线上通讯工具交流和线下交流三种交流方式。
- 共享文档 (石墨文档:支持markdown格式编辑)
1、前后端API交流(方式、请求参数和返回结果)
2、作业博客的共同编写
3、资料共享
- 线上通讯工具
1、实现实时沟通
2、问题反馈(通过图片方式更直观)
3、谈人生,聊理想,互相鼓励
- 线下沟通
1、共同解决bug
2、需求讨论
3、API制定
设计实现过程
功能结构图
前端设计
采用的框架和库
1、Vue.js
选用原因:
* Vue.js是较为完善的框架(github第三多星哦),资料更容易获得。
* 由于以前没有前端框架开发经验,所以权衡于React,Vue.js更加轻量,也更容易入门。
* Vue能把页面划分成很多小组件,方便代码复用。
* 数据处理方面,Vue很优秀,很方便
2、Element UI
选用原因:
* 简单、易上手
* 功能完善
* 和Vue.js耦合很好
3、Axios(请求服务器)
选用原因:
* 轻量,适用于像此次作业这类小型网站
4、Echarts
选用原因:
* 强,支持的图形种类丰富
* js库,在Vue中方便处理数据,使用起来较为简便
* 动态效果美啊
页面划分和组件设计
* 页面划分 (5个界面)
* 登录
* 注册
* 搜索页面
* 收藏列表页面
* 数据分析展示
* 组件(能在不同页面进行复用的模块)
* 菜单导航栏
* 论文信息展示(展示搜索结果和收藏列表)
* 图表展示
后端设计
采用的框架和库
使用nodejs来开发,以express作为WEB框架,sequelize作为数据库到对象的转化的模块
API的设计
1、/user/login
简要描述:
- 用户登入接口
请求方式:
- POST
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
username | 是 | string | 用户名 |
password | 是 | string | 密码 |
返回示例
登入成功
{
"code": 0,
"msg": "登入成功"
}
登入失败
{
"code": 1,
"err": "账号或密码错误"
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
2、/user/register
简要描述:
- 用户注册接口
请求方式:
- POST
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
username | 是 | string | 用户名 |
password | 是 | string | 密码 |
返回示例
注册成功
{
"code": 0
}
注册失败
{
"code": 1,
"err": "用户存在"
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
3、/search
简要描述:
- 通过title或keyword搜索论文
请求方式:
- GET
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
title | 否 | string | 标题 |
keyword | 否 | string | 关键字 |
返回示例
搜索结果
{
"code": 0,
"article": [
{
"aid": 8195,
"title": "Teaching Machines to Understand Baseball Games: Large-Scale Baseball Video Database for Multiple Video Understanding Tasks",
"no": null,
"author": "[]",
"type": "ECCV",
"keywords": "[\"Video understanding\", \"Large-scale video dataset\", \"Action recognition\", \"Temporal localization\"]",
"url": "https://doi.org/10.1007/978-3-030-01267-0_25",
"abstract": "A major/."
}
]
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
article | list | 搜索结果 |
4、/star/add
简要描述:
- 收藏文章
请求方式:
- POST
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
aid | 是 | string | 论文id |
返回示例
{
"code": 0,
"msg": "添加成功"
}
报错:
{
"code": 1,
"err": "Cannot add or update a child row: a foreign key constraint fails (`article`.`star`, CONSTRAINT `star_ibfk_1` FOREIGN KEY (`aid`) REFERENCES `article` (`aid`))"
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
article | list | 搜索结果 |
5、/star/delete
简要描述:
- 删除收藏文章
请求方式:
- POST
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
aid | 是 | string | 论文id |
返回示例
{
"code": 0,
"msg": "成功删除1条数据"
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
6、/star/list
简要描述:
- 列出用户收藏文章
请求方式:
- GET
参数:
无
返回示例
{
"code": 0,
"starlist": [
1
]
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
starlist | list | 收藏文件aid列表 |
7、/top10
简要描述:
- 返回top10的关键词和论文数量,以及总的论文数量
请求方式:
- POST
参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
min | 否 | int | 最小年份 |
max | 否 | int | 最大年份 |
返回示例
{
"code": 0,
"top": [
{
"keyword": "Computer vision",
"count": 3887
},
{
"keyword": "feature extraction",
"count": 3439
},
{
"keyword": "Cameras",
"count": 2860
},
{
"keyword": "image segmentation",
"count": 2856
},
{
"keyword": "learning (artificial intelligence)",
"count": 2493
},
{
"keyword": "Object detection",
"count": 2487
},
{
"keyword": "training",
"count": 2028
},
{
"keyword": "image reconstruction",
"count": 1951
},
{
"keyword": "image classification",
"count": 1623
},
{
"keyword": "face recognition",
"count": 1485
}
],
"total": 237919
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
total | int | 在min和max之间论文总数 |
top | list | 在min和max之间top10论文和数量 |
8、/article/:id
简要描述:
- 根据aid返回论文
请求方式:
- GET
参数:
将路径的:id换成id
参数名 | 必选 | 类型 |
---|---|---|
id | 否 | int |
返回示例
{
"code": 0,
"article": {
"aid": 1,
"title": "Proceedings IEEE Conference on Computer Vision and Pattern Recognition. CVPR 2000 (Cat. No.PR00662)",
"no": "855865",
"author": "[\"I. Stamos\", \"P.E. Allen\"]",
"type": "IEEE Conference",
"keywords": "[\"Bismuth\", \"virtual reality\", \"image segmentation\", \"image reconstruction\", \"3-D models\", \"virtual reality\", \"tele-presence\", \"digital cinematography\", \"urban planning\", \"dense depth estimates\", \"image sensing\", \"registration\", \"segmentation algorithms\", \"photorealistic models\"]",
"url": "https://doi.org/10.1109/CVPR.2000.855865",
"abstract": "This paper deals with the automated creation of geometric and photometric correct 3-D models of the world. Those models can be used for virtual reality, tele-presence, digital cinematography and urban planning applications. The combination of range (dense depth estimates) and image sensing (color information) provides data-sets which allow us to create geometrically correct, photorealistic models of high quality. The 3-D models are first built from range data using a volumetric set intersection method previously developed by us. Photometry can be napped onto these models by registering features from both the 3-D and 2-D data sets. Range data segmentation algorithms have been developed to identify planar regions, determine linear features from planar intersections that can serve as features for registration with 2-D imagery lines, and reduce the overall complexity of the models. Results are shown for building models of large buildings on our campus using real data acquired from multiple sensors."
}
}
返回参数说明
参数名 | 类型 | 说明 |
---|---|---|
code | int | 1:错误,0:正常运行 |
err | string | 错误信息 |
msg | string | 正常运行下返回的信息 |
article | list | 论文信息 |
数据库设计
account表
article表
keywords表
star表
代码说明
前端
- 接口访问逻辑(Axios):已获取top10热词数据为例
- axios提供post和get两个参数
- then中写的函数为成功访问时的响应函数
- 根据后端返回的不同状态,执行不同的操作或者给出提示信息
- catch中的函数是错误访问时的响应函数
getData() {
const that = this;
this.axios.post('top10', stringify({
'min': this.minYear,
'max': this.maxYear
} //访问方法,get或post,写明参数
))
.then(
function (response) { //成功访问时的响应函数
if (response.data.code == '0') {
let mes = response.data.top;
let len = mes.length;
let m = new Map();
//清空原来数据
that.data11 = [];
that.data12 = [];
that.data2 = [];
for (let i = 0; i < len; i++) {
that.data11.push(mes[i]['keyword']);
that.data12.push(mes[i]['count']);
m.set("value", parseInt(mes[i]['count']));
m.set("name", mes[i]['keyword']);
that.data2[i] = {value: mes[i]['count'], name: mes[i]['keyword']};
}
// console.log(that.data2);
}
})
.catch( //出错时的响应函数
function (error) {
console.log(error);
});
},
- 分页功能
- 主要由两个函数实现
- handleSizeChange:当用户改变每页显示条数时
- handleCurrentChange:当用户翻页时
- 主要由两个函数实现
handleSizeChange(val) {
/*console.log(`每页 ${val} 条`);*/
this.tableMes.eachPageItem = val;
this.tableMes.total_page = Math.ceil(this.tableMes.totalItem / this.tableMes.each_page_item);
this.tableMes.current_page = 1
this.handleCurrentChange(this.tableMes.current_page);
}
handleCurrentChange(val) {
/*console.log(`当前页: ${val}`);*/
this.tableMes.currentPage = val;
let itemCnt = this.tableData.length;
/* 计算展示的数组左右值 */
let leftIndex = (val - 1) * this.tableMes.eachPageItem;
let rightIndex = val * this.tableMes.eachPageItem;
/* 最后一页 */
if( rightIndex > itemCnt)
this.displayedTableData = this.tableData.slice(leftIndex);
else
this.displayedTableData = this.tableData.slice(leftIndex, rightIndex);
}
- 自定义弹出框
- 原生的弹出框有点丑,所以自己实现了一个好看的的弹出框
- mes是需要提示的信息
- type是提示框种类,例如:确认,警告,错误(不同类型有不同的样式)
- action则是触发事件
alertMes(mes, type, action){
this.$alert(mes, '提示', {
confirmButtonText: '确定',
callback: action => {
this.$message({
type: 'info',
message: `action: ${ action }`
});
}
});
},
后端
- 鉴权代码
- 将auth作为中间件插在需要鉴权的路由之前
const auth = function (req, res, next) {
if (req.session.auth) {
next()
} else {
res.status(401)
res.json({ code: 1, err: '请先登入' })
}
}
- 数据模型:
- 将数据获取封装再数据模型中,这里以用户模型为例
- 通过init来定义模型
const { Sequelize, DataTypes, Model } = require('sequelize')
const config = require('../config')
const sequelize = new Sequelize(config.sqlurl)
class User extends Model {
static login (username, password) {
return User.findOne({
where: {
username,
password
}
}).then(user => {
if (user === null) {
return Promise.reject(new Error('账号或密码错误'))
} else {
return { uid: user.uid, msg: '登入成功' }
}
})
}
static register (username, password) {
return User.findOne({
where: {
username: username
}
}).then(user => {
console.log(user)
if (user == null) {
return User.create({ username, password })
} else {
return Promise.reject(new Error('用户存在'))
}
})
}
}
User.init({
uid: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
sequelize,
modelName: 'User',
tableName: 'account',
timestamps: false
})
module.exports = User
- 安全相关:
- 将用户密码的加密封装在函数放在中间件
router.use('/', function (req, res, next) {
let username = req.body.username
let password = req.body.password
if (typeof username !== 'string' || typeof password !== 'string') {
return res.json({ code: 1, err: '用户名和密码必须为字符串' })
}
username = username.trim()
if (username === '') {
return res.json({ code: 1, err: '用户名不能为空' })
}
if (password === '') {
return res.json({ code: 1, err: '密码不能为空' })
}
password = md5(password)
req.body.username = username
req.body.password = password
next()
})
心路历程和收获
221801209
第一次体验到了合作开发,在这个过程总最大的问题是沟通,双方对于同一句话的理解是不同的,导致在后面开发的时候出现了差错。还有一个就是对项目的估计,由于是多人合作所以我们得对我们的项目有个直观的印象,之后我们需要设计好接口,但是最大的问题是,我们只是有一个大概的印象,并没有细节到能一劳永逸的定义好细节,这也导致了预计的接口和实际需要的接口可能少了,或者说某些地方没设计好。在这次结对作业中我体会到了多人合作开发过程中切实会遇到的困难。
221801222
刚看到题目时,其实心里还是有很大不确定性的,因为两个人要在差不多半个月的时间内完成一个web应用。所以,我和我的搭档在出题目的那天便开始讨论网站的实现,确认好了基本的分工和需要使用的技术。之后的几天除了吃饭睡觉几乎都花在学习新技术上了。
实际开发过程中虽然bug不断,曲折万分,但总算还是按时完成。这也让我对自己多了一丝肯定,有了些许的成就感,对陌生的东西的恐惧感也消除了不少。第一次用前端框架化开发还是收获很大的。
以下是我在开发过程中遇到的困难以及解决过程(或者说是一种收获吧):
1、面对一项全新的技术,该如何上手学习?
以往面对一项新技术,自己往往是无从下手,学习效率极低,掌握效果也不好。经过这次结对作业我认为学习新技术较好的方式最有效且最重要的有以下两种:
* 视频(直观、易于理解)
* 官方文档(个人认为是最为重要的,也是第一手资料,最直观的想法)
2、明白了计算机基础知识对编程的重要性。
在这次结对作业中,我好几个难以解决的bug在结对队友的帮助下都很好的解决了,结对队友有很好的web基础,所以这也让我认识到计算机基础对编程的影响。
例如:
* 只有了解了axios的内部访问机制是多线程的,才能更好地组织代码架构,让代码更高效。
* 面对跨源访问401问题,结对队友能很快意识到这是cookie的问题,在远程电脑上保存的cookie并不会被传送到服务器上,所以自然没有权限访问。之后队友一波设置电脑host,将远程服务器映射到本地,就解决了。
* 此外还有文件夹命名中含有“&"的解决方案,队友解决过程命令行一堆,实在看不懂,我只能直呼内行。
评价结对队友
221801209 To 221801222
和221801222结对完成这次设计后,我更加深刻的了解到了demoJi的由来,他写的代码很规范很美观。他的规划能力很强,大部分时间都是我在拖拉,他来催促我,如果没有他,我们这次作业可能没办法按时完成。总之22哥很优秀,向22哥学习。
221801222 To 221801209
和09哥结对完成这次原型编码实现后,又一次体会到了队友的风采。09哥真的是效率极高,并且有极强的规划能力,今日事今日毕在他身上完美体现,这都是值得我学习的地方。并且,在编码过程中很多问题是建斌哥帮忙解决的(这些问题我弄了大半天,而09哥只需思考片刻)。总之,09哥很优秀,向09哥学习。