结对作业二
这个作业属于哪个课程 | 2021春软件工程实践 W班 (福州大学) |
---|---|
这个作业要求在哪里 | 结对作业二 |
结对学号 | 081800306&041802224 |
这个作业的目标 | 顶会热词统计 |
PSP表格和效能分析
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | ||
• Analysis | • 需求分析 | 30 | 60 |
• Design Review | • 设计复审 | 10 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 3000 | 3500 |
• Code Review | • 代码复审 | 60 | 90 |
• Test | • 测试(自我测试,修改代码,提交修改) | 10 | 30 |
Reporting | 报告 | 50 | 60 |
• Test Report | • 测试报告 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 10 |
合计 | 3310 | 3920 |
Github地址&代码规范
访问地址
成果展示
项目的首页,由导航栏、搜索框、词云构成
导航栏由首页、论文检索、热度分析构成,支持三个页面的跳转
词云由echarts-wordcloud引入,从数据库中获取关键词简易排列
论文检索页面,在下拉框中可选择关键词、编号、内容、标题的方式检索论文
按照关键词检索论文
按照编号检索论文
按照标题检索论文
点击论文列表的修改图标跳转到详情修改界面
修改标题为TEST
查询标题为TEST的论文
热词分析页面,展示近三年三个会议的关键词统计
切换显示近三年各个会议的热词走势
结对过程
实现过程
-
前端
页面的标题栏复用,
首页的词云使用了echarts-wordcloud
论文列表页
分析页面使用echarts的饼图和折线图
html、css设计页面,jquery ajax请求接口返回数据给图表 -
后端
使用基于nodejs的koa框架
sequelize创建数据库表对应的模型,生成增删改查的数据库方法
路由接口提供数据 -
数据库设计
- 功能结构图
代码说明
sequelize根据数据库表article创建模型,编写所需的数据库语句
static async getArticles(list, page) {
let ids = [];
for (let i = page * 5; i < page * 5 + 5; i++) {
ids.push(list[i].arcid);
}
return await Article.findAll({
where: {
id: {
[Op.in]: ids,
},
},
});
}
static async getArticleByid(id) {
let article = await Article.findOne({
where: {
id,
},
});
return article;
}
static async getArticleByContent(content) {
let article = await Article.findAll({
where: {
content,
},
});
return article;
}
static async getArticleByTitle(id) {
let article = await Article.findAll({
where: {
id,
},
});
return article;
}
static async getIdsByKey(key) {
let ids = await keywords.getIdByKey(key);
return ids;
}
static async updateArticle({ id, title, conclude, link, magazine }) { // eslint-disable-line
await Article.update({ title, conclude, link, magazine }, { // eslint-disable-line
where: { id },
});
}
static async deleteArticle({ id }) {
await Article.destroy({
where: {
id,
},
});
}
}
Article.init({
id: {
type: DataTypes.STRING,
primaryKey: true,
},
title: DataTypes.STRING,
year: DataTypes.INTEGER,
conclude: DataTypes.STRING,
link: DataTypes.STRING,
magazine: DataTypes.STRING,
}, {
sequelize,
tableName: 'article',
});
module.exports = {
Article,
};
路由请求,通过调用model类的方法返回相应的数据
let key = ctx.query.key; // eslint-disable-line
let page = ctx.query.page || 0;
let kind = ctx.query.kind; // eslint-disable-line
let articles;
let list;
if (key && (kind == 1)) { // eslint-disable-line
console.log(key);
list = await Article.getIdsByKey(key);
articles = await Article.getArticles(list, page);
}
if (key && (kind == 0)) { // eslint-disable-line
articles = await Article.getArticleByid(key);
}
if (key && (kind == 2)) { // 内容
list = await Article.getIdsByKey(key);
articles = await Article.getArticles(list, page);
}
if (key && (kind == 3)) { // 标题
list = await Article.getIdsByKey(key);
articles = await Article.getArticles(list, page);
}
ctx.body = articles;
});
module.exports = router;
前端复用的标题栏
<div class="logo">
</div>
<div class="items">
<a class="item" index="1">首页</a>
<a class="item">论文检索</a>
<a class="item">热度分析</a>
</div>
</div>
<script>
let items = document.querySelector('.items')
items.addEventListener('click', (e) => {
let str = e.target.innerHTML
if (str === '首页') {
window.location.href = '/'
} else if (str === '论文检索') {
window.location.href = '/list'
} else if (str === '热度分析') {
window.location.href = '/hot'
}
console.log(str === '首页');
})
</script>
展示数据的list
<% if (articles) {%>
<% articles.forEach(function(article){ %>
<%- include('./item.html', {article}); %>
<% }); %>
<%}%>
</div>
list中的item
<p class="piece-title"><%= article.title %></p>
<p class="piece-content"><%= article.conclude%></p>
</div>
列表数据通过jquery的ajax方法获取
type: 'GET',
url: `http://localhost:3000/getArticles?key=${key}&page=${page}&kind=${kind}`,
success(res) {
console.log(res);
if (!Array.isArray(res)) {
res = [res]
}
let $lis = ''
$('.list').empty()
for(let i = 0; i < res.length; i++) {
$lis += `<div class="piece"><a href="/detail?id=${res[i].id}"><i><svg t="1616326953943" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4559" width="32" height="32"><path d="......" fill="#e6e6e6" p-id="4561"></path><path d="......" fill="#e6e6e6" p-id="4562"></path><path d="......" fill="#e6e6e6" p-id="4563"></path></svg></i></a>
<p class="piece-title">${res[i].title}</p>
<p class="piece-content">
${res[i].conclude}
</p>
<a class="link" href="${res[i].link}" target="_blank">原文链接</a>
</div>`
}
$('.list').append($lis)
}
})
echart饼图的数据请求
type: 'POST',
url: 'http://localhost:3000/hot/gettop',
//http://150.158.180.107:3000
success(res) {
var data1 = []
data1 = res
var chartDom1 = document.getElementById('container1');
var myChart1 = echarts.init(chartDom1);
var option1;
option1 = {
title: {
text: '关键词TOP10',
left: 'center',
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'item'
},
series: [{
name: '关键词',
type: 'pie',
radius: '50%',
data: data1,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
option1 && myChart1.setOption(option1);
}
})
项目入口
const app = new Koa();
ejs.clearCache();
app.use(bodyparser());
app.use(serve(path.join(__dirname, '/public')));
app.use(views('view', {
root: path.join(__dirname, '/view'),
map: { html: 'ejs' },
}));
function registerRouters(item) {
if (item instanceof Router) {
app.use(item.routes());
}
}
const modules = new RequireDirectory(module, './routes', { visit: registerRouters });
package
"name": "work",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint src",
"lint:create": "eslint --init"
},
"author": "",
"license": "ISC",
"dependencies": {
"ejs": "^3.1.6",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1",
"koa": "^2.13.1",
"koa-bodyparser": "^4.3.0",
"koa-router": "^10.0.0",
"koa-static": "^5.0.0",
"koa-views": "^7.0.1",
"mysql2": "^2.2.5",
"require-directory": "^2.1.1",
"sequelize": "^6.6.2"
},
"devDependencies": {
"eslint": "^7.22.0"
}
}
心路历程
hanmajack(0418022224):
这次的作业用到的技术对我来说很陌生,web的基础掌握的也不够好,花费了大量的时间去看API文档、博客等。一路上遇到了很多困难,也都一一与队友讨论解决了。总的来说这次结对的体验很好,让我学到了很多新知识。
陈志君(081800306):
这次的作业的技术选型本来想采用前后端分离,前端采用vue框架来完成,但是由于作业文件夹命名必须有&字符,Vue框架就不能采用了,所以我们决定用web半分离的方式来开发,前端通过模板语法来渲染。学到了挺多新知识,包括数据库的高级查询,服务器的部署等等。收获蛮大的
队友评价
hanmajack -> 从零开始的代码生活:
队友很给力,执行力很强,自己的部分总是很快就完成实现了。而且队友在我遇到困难时给了我很多帮助,在编程方面指导我,很耐心的给我讲解,很庆幸这次结对作业遇到了这么好的队友。
陈志君 -> hanmajack
队友学习能力很强,很勤奋,刚开始对web可能不太熟悉,但是他课后自学,b站看视频,csdn上看博客,一步一步提升自己,给队友一个大大的好评