首页 > 技术文章 > 结对作业二

SakuChyan 2021-03-31 12:20 原文

这个作业属于哪个课程 2021春软件工程实践|W班 (福州大学)
这个作业要求在哪里 结对作业二
结对学号 221801208、221801227
这个作业的目标 实现顶会热词统计
其他参考文献 github、CSDN

网站链接

论文爬取

tips

  • 由于注册功能还未完成,所以提供了两个可用的初始登录账号密码:
    • 账户1:账号:admin ,密码:admin
    • 账户2: 账号:user ,密码:123456
  • “热词图谱”加载较慢,需等待10秒才能加载出完整界面,请不要在刚打开“热词图谱”界面时就点击关键词;
  • 搜索论文时,个别关键词匹配的论文较多,需要等待;
  • 请用除IE以外的浏览器打开

git仓库链接和代码规范链接

仓库链接
代码规范

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Development 开发
• Analysis • 需求分析 40 30
• Design Spec • 生成设计文档 20 30
• Design Review • 设计复审 10 10
• Coding Standard • 代码规范 80 100
• Coding • 具体编码 3500 4000
• Code Review • 代码复审 200 160
• Test • 测试 200 300
Reporting 报告
• Test Repor • 测试报告 50 40
• Size Measurement • 计算工作量 20 15
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 50 60
合计 4170 4745

成品展示

账户1: 账号:admin ,密码:admin

账户2: 账号:user ,密码:123456

界面展示

登录、注册、找回密码界面

目前只实现登录功能,注册与找回密码只有界面

主页

查询论文

支持标题模糊查询、标签查询;查询结果分页展示,每页展示10条结果;有两个查询对象:1. 对爬取的所有论文查询;2. 对已经收藏的论文进行查询;

(图1)对爬取的所有论文进行查询,支持标题模糊搜索


(图2)对已经收藏的论文进行查询,支持标题以及关键字查询

热词分析

其中共有热词图谱,热度走势,热度对比三大部分:

(图3)热词图谱,3大会议的热词词云,以轮播图的方式展示


(图4)热度走势:Top10热词按 年份-频数 得出的折线图


(图5)热词热度:以饼图的形式对比Top10的热词热度

更多功能

(图6)词云图上的词语,点击跳转至相应词的搜索结果


(图7)对搜索结果的地址进行一键复制

结对讨论过程描述

1.前后端交互讨论

    一开始不确定用什么框架,经讨论之后确定为vue和springboot,这是我们小组用的相对来说最熟悉的框架。vue有一定基础,而且java接触较多,所以选择springboot作为后端框架。比较麻烦的是数据交换,JSONArray和JSONObject相互嵌套,后端跟前端的同学解释了好一会儿返回值的意义。后面放弃了,还是老老实实写个接口文档,帮助理解。

2.github使用讨论

    之前没有用过远程分支,再加上还是用命令行管理项目版本的,所以始终整不明白怎么在远程建立分支并提交。ch建议换成github desktop可视化操作,理解和操作都简单了许多。

3.框架使用讨论

    springboot花了不少时间找教程,目的是快速上手。对于新手来说易于理解的教程找起来不容易,二人互相发了很多教程,费了不少功夫才找到简单的一个demo,自己慢慢理解。注解在springboot中出现频率最高,和java语言不同,没有前后端交互经验的初学者不好理解,没看懂的时候就和队友讨论。

4.数据库讨论

    数据库表结构关乎后端模型。讨论出的第一种方案是一张表解决,把所有关键词作为text存储。起初以为在同一张表存储所有信息会加快查找速度,但是不利于编码,而且每次取出来还要分割字符串会降低性能(果然讨论比个人的想法要周全)。另一种方案是两张表。最后还是决定对于每个会议建了两张表,一张主表存一对一信息(id为主键),另一张表存id对应的所有关键词(id为外键)。

设计实现过程

1.后台数据管理:
    论文与关键词是一对多关系,所以需要两张表:表一包含编号、题目、摘要、原文链接、年份,表二包含编号、关键词。依照数据库的设计原则,这种方式可以避免冗余。后端代码中一个Dao接口服务一个Controller,将单个Controller的所有数据库操作封装到唯一的Dao中,调用的时候不易混乱。
2.后端框架选择:
    后端使用springboot框架。由于集成了spring和mybatis,编程时能更多地集中于与需求相关的实现上,像数据库连接关闭、解析前端请求和给前端返回数据都,这些都只要通过简单的注释就能解决。实现逻辑放在Controller下,数据库相关操作放在Dao层,对象模型放在model,三者分离开有利于debug和后期维护。
3. 前端总体
    前端使用Vue框架,使用了axiosechartsview-designvue-routervue-woedcloudvuescrollvuex插件。

功能结构图

界面结构图

设计概述

1.登录

  • 前端参数:用户输入账号、密码,账号密码打包成JSONObject,传给后端验证。
  • 后端处理:查看数据库里已有的账号密码,看是否有完全匹配的元组。
  • 后端返回值:返回一个status和一个验证结果。(status为状态码)
  • 登陆后,主页为侧边导航菜单以及主面板构成,共有5个界面分别为:论文搜索 、本地收藏、热词图谱、热度走势、热度对比。

2.论文搜索

  • 前端布局:搜索结果在搜索框下,是一个无限滚动的论文列表。每个论文项带有题目、编号、摘要、原文链接、年份和关键词。
  • 前端参数:用户在搜索框搜索题目或者题目的一部分,title作为给后端的参数。
  • 后端处理:将title作为查询条件,查询对应论文的所有信息。
  • 后端返回值:后端经过模糊匹配(可以通过数据库的模糊匹配实现),返回给前端一个JSONArray,内部包含多个JSONObject,每个JSONObject是一篇论文的所有信息。

3.本地收藏按钮

  • 前端参数:被收藏的论文的所有信息。
  • 后端处理:将收藏论文插入数据库表。

4.论文删除

  • 前端参数:前端提供论文id。
  • 后端处理:将对应论文从数据库删除。

5.论文本地查找

  • 前端布局:和论文搜索界面相似,多了一个关键词搜索
  • 前端参数:如果是模糊搜索则参数名为fuzzyTitle,如果是关键词搜索参数名为keyword,调用的后端接口不同。
  • 后端处理:用子查询的方式查询两个表。
  • 后端返回值:返回论文列表,是一个JSONArray类型的数据。

6.top10热词(用于关键词图谱)

  • 前端界面:关键词云图,支持点击跳出相应论文。分别由三张云图,对应三个会议,以轮播图的形式展现。
  • 后端处理:借助单词统计的算法实现,统计所有关键词的频度。
  • 后端返回值:返回top10热词及其频度。

7.关键词走势

  • 前端界面:热度走势为Top10的每个热词的不同年份与出现频数的折线图。用户易于看出近期热词。
  • 后端处理:对top10的关键词查询数据库,按年份统计出现次数。
  • 后端返回值:年份横坐标,出现频度纵坐标,都为JSONArray。

8.top10热词的饼图

  • 前端界面:展示top10热词各自的占比,有助于用户了解长期以来热度较高的词。

代码说明

前端代码

图表组件及其复用

使用props向写好的图表组件传入数据,进行渲染,实现复用

图表主要使用echarts组件实现折线图与饼图

使用vue-wordcloud实现词云图

 <div id="myChart2"></div>

// data部分
data () {
    return {
      data1: [5, 10, 12, 69, 25, 14, 18, 55, 47, 33],
      data2: ['key1', 'key2', 'key3', 'key4', 'key5', 'key6', 'key7', 'key8', 'key9', 'key10'],
      datat: [],
      color: ['#349dff', '#fbd438', '#33c45e', '#f2637b', '#6dd48c', '#fbd437', '#4ecb73', '#eaa674', '#88d1ea', '#36cbcb']
    }

// 渲染折线图的代码
drawLine () {
      this.chartLine = echarts.init(document.getElementById('myChart2'))
      const option = {
        tooltip: { // 设置tip提示
          formatter: '{a}<br/>{b}:{c} ({d}%)'
        },

        legend: { // 设置区分(哪条线属于什么)
          y: 'bottom',
          x: 'center',
          data: this.data2
        },
        color: this.color, // 设置区分(每条线是什么颜色,和 legend 一一对应)
        series: [
          {
            name: '出现次数',
            data: this.datat,
            type: 'pie'
            // radius: ['50%', '70%']
            // center: ['50%', '50%']
          }
        ]
      }
      // 使用刚指定的配置项和数据显示图表。
      this.chartLine.setOption(option)
    },

论文列表的展示:调用接口后传来的数据存在一个数组变量中,使用vue的方法v-for对这个数组中的数据遍历展示

<div class="recordItem" v-for="(item,index) in searchResult" :key="index">
        <div class="recordTitle">{{item.title}}</div>
        <div class="recordCode"><span>论文编号:</span>{{item.number}}</div>
        <div class="recordTag">
          <span v-for="(item1,index1) in item.keyword.slice(0,3)" :key="index1">{{item1}}</span>
        </div>
        <div class="recordContent"><span>摘要内容:</span>{{item.abstract}}</div>
        <div class="recordAddress">
          <div @click="copy(item.link)">复制原文地址</div>
        </div>
        <div class="opeBtn">
<!--          <Button shape="circle" icon="ios-create-outline"></Button>-->
          <Button shape="circle" icon="ios-trash-outline" @click="myDelete(item)"></Button>
        </div>
      </div>

前端接口调用,使用的是axios。封装axis。

this.$axios.post('http://localhost:8081/PaperOperationController/fuzzyQuery', {
  fuzzyTitle: this.searchContent
})
  .then(res => {
    this.searchResult = res.data.result
    this.totle = res.data.item_num
    this.showResult = this.searchResult.slice(0, 10)
  })
  .catch(err => {
    console.log(err)
  })
  .finally({
  })

后端代码

1.论文title的模糊查询:为了将数据区分开,保证论文id的唯一性,三个会议分别建了2个表。所以下面的模糊查询需要针对不同的的会议数据库表查询,index = 0,1,2分别代表三个会议。模糊查询的本质是数据库的LIKE模糊查询。先通过题目查询论文id及其他信息,再通过论文id查询关键词。

public int queryPaper(JSONArray result,int index,String fuzzyTitle,int itemNum){
        List<Conference> conferenceList = null;
        if(index == 0){
            conferenceList = paperOperationDao.getCvpr(fuzzyTitle);
        }else if(index == 1){
            conferenceList = paperOperationDao.getEccv(fuzzyTitle);
        }else if(index == 2){
            conferenceList = paperOperationDao.getIccv(fuzzyTitle);
        }

        for (int j = 0; j < conferenceList.size(); j++) {
            Conference conference = conferenceList.get(j);
            JSONObject paperInfo = new JSONObject();
            paperInfo.put("title", conference.getTitle());
            paperInfo.put("number", conference.getNumber());
            paperInfo.put("abstract", conference.getPaperabstract());
            paperInfo.put("link", conference.getLink());
            paperInfo.put("year", conference.getYear());
            paperInfo.put("type",type[index]);

            List<ConferenceKwd> conferenceKwdList = null;

            if(index == 0){
                conferenceKwdList = paperOperationDao.getCvprKwd(conference.getNumber());
            }else if(index == 1){
                conferenceKwdList = paperOperationDao.getEccvKwd(conference.getNumber());
            }else if(index == 2){
                conferenceKwdList = paperOperationDao.getIccvKwd(conference.getNumber());
            }

            JSONArray keywordArray = new JSONArray();
            for (int k = 0; k < conferenceKwdList.size(); k++) {
                ConferenceKwd conferenceKwd = conferenceKwdList.get(k);
                keywordArray.add(conferenceKwd.getKeyword());
            }

            paperInfo.put("keyword", keywordArray);
            result.add(paperInfo);
            itemNum++;
        }
        return itemNum;
    }

2.基于论文关键词的查询:论文关键词查询和模糊查询方法区别不大。先用关键词查找id,再通过id查找论文的所有信息。代码类似,就不展示了。

3.生成关键词图谱的热词:前端需要top10热词以及热词在图谱中的字体大小,后端用随机数对热词字体进行变化,界面展示的时候会更美观。

public JSONArray getWordMap(){
        Random random = new Random();
        JSONArray map = new JSONArray();
        JSONObject result = getHotWords();
        JSONArray hotWords = result.getJSONArray("hotWord");
        for(int i = 0;i < hotWords.size();i++){
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("name",hotWords.get(i));
            jsonObject.put("value",random.nextInt(16)+12);
            map.add(jsonObject);
        }
        return map;
    }

4.单个热词的走势:用一个数组保存关键词2000~2007年的频率。前端需要的用echarts绘制折线图,所以返回两个数组,分别表示横纵坐标。

public JSONObject getTrend(@RequestBody JSONObject request){
        String keyword = request.getString("keyword");

        int[] frequency = new int[8];
        for(int i = 0;i < frequency.length;i++){
            frequency[i] = 0;
        }
        JSONObject trend = new JSONObject();
        JSONArray horizontal = new JSONArray();
        JSONArray vertical = new JSONArray();

        for(int i = 0;i < 3;i++){
            List<ConferenceKwd> conferenceKwdList = null;
            if(i == 0){
                conferenceKwdList = keywordTrendDao.getCvprKwd(keyword);
            }else if(i == 1){
                conferenceKwdList = keywordTrendDao.getEccvKwd(keyword);
            }else if(i == 2){
                conferenceKwdList = keywordTrendDao.getIccvKwd(keyword);
            }
            for(int j = 0;j < conferenceKwdList.size();j++){
                ConferenceKwd conferenceKwd = conferenceKwdList.get(j);
                int number = conferenceKwd.getNumber();
                List<String> yearList = null;
                if(i == 0){
                    yearList = keywordTrendDao.getCvprYear(number);
                }else if(i == 1){
                    yearList = keywordTrendDao.getEccvYear(number);
                }else if(i == 2){
                    yearList = keywordTrendDao.getIccvYear(number);
                }
                String year = yearList.get(0);
                String lastCharacter = year.substring(year.length() - 1);
                int index = Integer.parseInt(lastCharacter);
                frequency[index]++;
            }
        }
        for(int i = 0;i < frequency.length;i++){
            horizontal.add(2000+i);
            vertical.add(frequency[i]);
        }
        trend.put("year",horizontal);
        trend.put("frequency",vertical);

        return trend;
    }

5.获取top10热词及其对应频率:这项功能涉及到单词的统计。为提高效率,加快统计速度,将HashMap作为从关键词映射到频率的数据结构。HashMap本身不具有排序功能,所以需要把关键词和频率封装到类Word,放在TreeSet排序。具体实现步骤:select所有关键词--》插入HashMap--》插入TreeSet。

public JSONObject getHotWords(){
        Map<String, Integer> map = new HashMap<>();
        Set<Word> set = new TreeSet<>();
        for(int i = 0;i < 3;i++) {
            List<ConferenceKwd> conferenceKwdList = null;
            if(i == 0){
                conferenceKwdList = hotWordFrequencyDao.getCvprKwd();
            }else if(i == 1){
                conferenceKwdList = hotWordFrequencyDao.getEccvKwd();
            }else if(i == 2){
                conferenceKwdList = hotWordFrequencyDao.getIccvKwd();
            }
            for (int j = 0; j < conferenceKwdList.size(); j++) {
                ConferenceKwd conferenceKwd = conferenceKwdList.get(j);
                wordToHashMap(conferenceKwd.getKeyword(), map);
            }
        }

        return frequency(set,map);
    }

心路历程和收获

    hj:整个过程还是比较曲折的。为了确定数据库表,对表结构进行了两次修改,因此数据反复导入花了不少时间。最开始编码的时候,第一次使用springboot框架,改配置和debug也挺困难。后期逐渐熟练,慢慢对springboot有了整体的理解。总的来说收获颇丰,学会了应用新框架,学会了借助官方文档和demo入手新知识,学会了写接口文档,github管理版本的操作也更熟练。经验教训也没比学到的知识少:短时间开发尽量选择自己使用过的框架或工具,避免开发效率低;数据库对编码和类结构设计的影响极大,宁可多花些时间在数据库表设计。
    ch:刚看到作业时是比较焦虑的,因为我不仅有这一项作业,还多出上学期生病拉下的缓考,幸好延迟了时间。我在此之前已经有过相应的项目经历,但由于较久没有接触并且没有尝试过快速的,从0开始的两个人以结对的形式共同完成的方式。还是有一段适应期。慢慢地就和队友磨合好了。在写代码这一块,我主要在图表生成与前后端数据格式要求上遇到较大问题,但在多次的沟通以及查询资料后逐个解决了。

评价结对队友

    hj:在前端方面ch算是我们当中的专家了,所以和他合作起来很轻松。前后端交互测试的时候,前端基本没bug,很快就完成衔接完了。另外,ch还是个做事精益求精的人,如果界面布局不够美观或者功能展示不够合理,ch会再三考虑并给出可行的方案,并且短时间内完成代码修改,效率很高。ch解决了很多前端技术上的难题,是学习能力很强的队友,是最佳的partner。
    ch:hj是一个认真,有干劲的队友。对项目的布局、进度都有较好的安排。他很努力的进行相关知识的学习。打代码也很勤奋,带动我一起认真对待。我们磨合的十分顺利,很高心能跟hj同学合作。

推荐阅读