结对作业二

这个作业属于哪个课程 2021春软件工程实践|W班(福州大学)
这个作业要求在哪里 结对作业二
结对学号 221801113、221801128
这个作业的目标 顶会热词统计的实现
其他参考文献 简书/博客园/CSDN/B站

一、作业链接

网站链接

网站链接:链接

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

git仓库:https://github.com/imcjx/PairProject
代码规范链接:https://github.com/imcjx/PairProject/blob/main/221801113%26221801128/codestyle.md

二、PSP表格

Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
计划 15 15
估计这个任务需要多少时间 15 15
开发 2435 3000
原型复审 10 10
需求分析 (包括学习新技术) 60 200
生成设计文档 40 50
设计复审 10 10
代码规范 25 20
具体设计 30 60
具体编码 2100 2400
代码复审 40 30
网站部署 60 120
测试 60 100
报告 45 30
计算工作量 15 10
事后总结, 并提出过程改进计划 30 20
合计 2495 3045

三、成品展示

1、登录注册

2、论文搜索

3、论文内容详情以及添加至收藏夹

4、修改已收藏的论文并保存、将论文移出收藏夹

5、论文分析————热门领域展示以及词云图热词点击搜索

6、论文分析————2000-2020年间的热词走势对比

7、账号登出

8、论文爬取功能

四、结对讨论过程描述

  • 刚看到题目的时候,其实想用vue+springboot前后端分离进行开发,但因为我们两个人都没有什么后端经验,怕一个人后端压力太大,思考后决定选用springboot+thymeleaf来进行开发,接着我们就对这次作业进行模块划分(登录注册模块、论文列表模块、收藏夹模块、热词分析模块,走势对比模块),两个人负责不同的模块,这样在使用github进行结对编程时不会产生什么冲突。
  • 接着就简单的进行了需求分析和数据库表的设计,在导入助教的数据时也花费了一些时间,主要是json格式的问题,然后口头制定了此次的代码规范以防止后期编码时存在较大风格差异(虽然codestyle.md最后几次commit才交)
  • 前期基本把时间花费在学习springboot、mybatis等时间里在编码过程中,遇到问题,就采取讨论的方式解决,我们在线下的作息时间相同,所以编码的时段也很统一,省去了一大部分不必要的时间的浪费。
  • 后期基本就是对界面和一些细小bug的修改,期间顺便实现了论文爬取的功能,实现功能后,我们各自对彼此的代码以及本次作业所要求的所有功能进行复审,以防出现问题。
  • 结对过程中的讨论图(因为是舍友,基本都是线下交流,聊天记录较少):


五、设计实现过程

数据库表:

共有四张表:collect(收藏夹)、data(论文列表)、hotword(热词图表)、user(用户)

Table: collect
    Columns:
        uid varchar(255) PK 
        did int PK 
        publicationTitle varchar(255) 
        persistentLink varchar(255) 
        keywords mediumtext 
        abstrac mediumtext

Table: data
    Columns:
        id int AI PK 
        authors varchar(1024) 
        keywords text 
        abstrac text 
        publicationTitle varchar(256) 
        publicationYear varchar(256) 
        persistentLink varchar(256) 
        typepaper varchar(64)

Table: hotword
    Columns:
        id varchar(32) PK 
        hot json

Table: user
    Columns:
        id int AI PK 
        username varchar(255) 
        password varchar(255)

项目框架:

采用springboot + mybatis + thyemleaf + JQuery

设计:

  • 登录注册模块

    • 前端:通过绝对定位+transform的位移来实现注册登录的动效切换,js使用正则表达式对账号密码进行基本判断
    • 后端:通过设置session来判断用户的登录状态,通过拦截器判断是否有登录字段来决定是否放行
  • 侧边栏和顶部栏模块

    • 前端:通过thyemleaf模板引擎,将其作为组件抽离,以实现复用,通过判断地址栏地址,解决侧边栏高亮和搜索、爬取按钮disabled的问题
  • 论文查询、收藏夹模块

    • 前端:通过flex布局,设置页面的四个卡片,底部分页器使用的pagination.js分页插件,显示具体信息通过点击卡片,展示弹窗,提示信息用的是toastr.js
    • 后端:通过前端传输的搜索信息和分页器提供的页码从数据库查询指定位置数据并返回。爬虫则是根据搜索的信息通过HttpURLConnection获取所需json字符串,通过fastjson转化为所需map传回前端。
  • 词云图

    • 前端:采用卡片形式布局,每张卡片中都应用了highchart模板来处理后端返回的数据,并以图表的方式呈现。
    • 后端:采用springboot,mybatis框架,采用MVC的设计模式,Bean层设计实体类,存放热词相关属性keyvaluemapper层定义存取数据库表数据的方法,service层调用接口,最后在controller层调用实现;排序方法取出Top10热词;存入数据库以String字符串的形式,使用时从数据库取出并转换为json形式返回给前端js处理。
  • 动态柱状图

    • 前端:走势对比页面设计一个div用来显示动态图表,js应用相关图表模板处理后端返回的json数据。
    • 后端:springboot,mybatis框架实现,返回前端动态柱状图所需的两个json文件,从数据库里取到的所有论文信息,以相应的json结构来定义对象数组存放,并根据热词数量和年份递增排序,以实现图表按年份递增播放的效果。

功能结构图:

六、关键代码说明

1、论文爬取的实现

/**
* 获取爬取论文的json字符串
* @param searchInfo
* @return
*/
public String getJsonStr(String searchInfo) {
    String params = "{\"newsearch\":true,\"queryText\":\""+searchInfo+"\",\"highlight\":true" +
            ",\"returnFacets\":[\"ALL\"],\"returnType\":\"SEARCH\",\"matchPubs\":true}";
    HttpURLConnection httpURLConnection = null;
    BufferedReader reader = null;
    OutputStream out = null;
    String res = "";
    try {
        httpURLConnection=setConnection();
        httpURLConnection.connect();
        out = httpURLConnection.getOutputStream();
        out.write(params.getBytes());
        if (httpURLConnection.getResponseCode() == 200) {
            reader = new BufferedReader(
                    new InputStreamReader(httpURLConnection.getInputStream()));
            res = getResStr(reader);

        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            out.flush();
            out.close();
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return res;
}
  • 将流里的数据读出
/**
* 获取输入流的json字符串
* @param reader
* @return
* @throws IOException
*/
public String getResStr(BufferedReader reader) throws IOException {
    StringBuilder sb = new StringBuilder();
    String str = "";
    while ((str = reader.readLine()) != null) {
        sb.append(str);
    }
    return sb.toString();
}
  • 为请求设置请求头
/**
* 设置请求头参数
* @return
*/
public HttpURLConnection setConnection() {
    HttpURLConnection httpURLConnection = null;
    try {
        URL url = new URL("https://ieeexplore.ieee.org/rest/search");
        httpURLConnection = (HttpURLConnection) url.openConnection();
        httpURLConnection.setRequestMethod("POST");
        httpURLConnection.setConnectTimeout(10000);
        httpURLConnection.setDoOutput(true);
        httpURLConnection.setDoInput(true);
        httpURLConnection.setRequestProperty("Content-Type", "application/json");
        httpURLConnection.setRequestProperty("Accept", "application/json, text/plain, */*");
        httpURLConnection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9");
        httpURLConnection.setRequestProperty("Connection", "keep-alive");
        httpURLConnection.setRequestProperty("Referer", "https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&queryText=computer");
        httpURLConnection.setRequestProperty("Accept-Encoding", "gzip, deflate, br");
    } catch (IOException e) {
        e.printStackTrace();
    }
    return httpURLConnection;
}
  • 使用fastjson将获取到的json字符串转为json对象存入map
/**
* 将json字符串转变为json对象放入map里
* @param jsonStr
* @return
*/
public Map<List<Paper>, Integer> getMap(String jsonStr) {
    Map<List<Paper>, Integer> map= new HashMap<>();
    List<Paper> paperList=new ArrayList<>();
    JSONObject jsonObject= JSONObject.parseObject(jsonStr);
    JSONArray jsonArray = jsonObject.getJSONArray("records");
    if (jsonArray == null) {
        map.put(paperList, new Integer(0));
    } else {
        for (int i = 0; i < jsonArray.size(); i++) {
            Paper paper = new Paper();
            JSONObject jo = jsonArray.getJSONObject(i);
            paper.setAbstrac(jo.getString("abstract"));
            paper.setId(jo.getString("articleNumber"));
            paper.setPersistentLink("https://ieeexplore.ieee.org"+jo.getString("documentLink"));
            paper.setPublicationTitle(jo.getString("articleTitle"));
            paperList.add(paper);
        }
        map.put(paperList, new Integer(jsonArray.size()));
    }
    return map;
}

2、Top10词云图的实现

@Mapper//预存入热词top10数据的mapper
public interface HotwordMapper {
    @Select("select keywords from data where typepaper = 'ECCV'")
    public List<String> getAllWord();

    @Insert("insert into hotword values(111,#{json})")
    public  void insertHotword(@Param("json") String json);

    @Select("select hot from hotword where id = #{type}")
    public String getHotwordjson(@Param("type") String type);
}
public class HotwordService {
    @Autowired
    HotwordMapper hotwordMapper;
    /**
     * 获取数据库所有热词
     * @return
     */
    public List<String> getAllWord(){
        return hotwordMapper.getAllWord();
    }
    /**
     * 预存入数据
     * @param json
     */
    public void insertHotword(String json){
        hotwordMapper.insertHotword(json);
    }
    /**
     * 得到top10热词
     * @param type
     * @return
     */
    public String getHotwordjson(String type){
        return hotwordMapper.getHotwordjson(type);
    }
}
  • 将爬取到的存在数据库中的总数据进行分析,获取top10热词的json文件
/**
* 预存入热门领域数据
* @return
*/
@GetMapping("/hot2")
@ResponseBody
public void getWord2() throws JsonProcessingException {
    HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
    List<HotWord> list = new ArrayList<>();
    String a = new String();
    List<String> liststr = new ArrayList<>();
    liststr = hotwordService.getAllWord();
    String str = new String();
    for (int q = 0; q < liststr.size(); q++) {
        a = liststr.get(q);//String
        if(a==null){
            continue;
        }
        str = a.replace("\"", "")
                .replace("(","")
                .replace(")","");
        String[] chars = new String[2000];
        chars = str.split(",");
        for (int j = 0; j < chars.length; j++) {
            if(hashMap.containsKey(chars[j].toLowerCase())){
                hashMap.put(chars[j].toLowerCase(),hashMap.get(chars[j].toLowerCase())+1);
            }
            else {
                hashMap.put(chars[j].toLowerCase(),1);
            }
        }
    }
    List<HashMap.Entry<String, Integer>> sortedList = getSortedList(hashMap);
    HashMap<String,Integer> hashMap2 = new HashMap<String,Integer>();
    int cnt = 0;
    for(HashMap.Entry<String,Integer> entry:sortedList) {
        hashMap2.put(entry.getKey(),entry.getValue());
        cnt++;
        if (cnt >= 10)//仅需要输出前十位
            break;
    }
    ObjectMapper mapper = new ObjectMapper();
    String jsonlist = mapper.writeValueAsString(hashMap2).toString();
    hotwordService.insertHotword(jsonlist);
}
/**
* 返回前端热门领域数据
* @param type 顶会类型
* @return
*/
@GetMapping("/hot")
@ResponseBody
public String getWord(@RequestParam(value = "type") String type) {
    String str = hotwordService.getHotwordjson(type);
    return str;
}
  • 前端js接收返回的json数据并调用
function getChart(type) {
    var str1 = [];
    var str2 = [];
    var data2 = [];
    var cnt = 0;
    var cnt2 = 0;
    var cnt3 = 0;
    $.ajax({//AJAX请求接口
        type: "GET",
        contentType: "application/json;charset=UTF-8",
        url: "/hot",
        data: {type: type},
        success: function(re) {
            console.log(re);
            let res= JSON.parse(re);
            for(var key in res){
                str2[cnt2] = key;
                data2[cnt2] = res[key];
                cnt3+=data2[cnt2];
                cnt2++;
                for(var n = 0;n<res[key];n++) {
                    str1[cnt] = key;
                    cnt++;
                }
            }
            var text = str1.toString();
            //top10热词的数据data
            var data = text.split(/[,\. ]+/g)
                .reduce(function (arr, word) {
                    var obj = arr.find(function (obj) {
                        return obj.name === word;
                    });
                    if (obj) {
                        obj.weight += 1;
                    } else {
                        obj = {
                            name: word,
                            weight: 1
                        };
                        arr.push(obj);
                    }
                    return arr;
                }, []);
            //渲染词云图容器
            Highcharts.chart('container', {
                series: [{
                    type: 'wordcloud',
                    data: data
                }],
                title: {
                    text: ''
                }
            });
    ...

3、动态柱状图实现

  • 对得到的hashmap进行排序
/**
* 对全部热词进行排序
* @param hashMap 存放关键词-数量
* @return
*/
public static List<HashMap.Entry<String, Integer>> getSortedList(HashMap<String, Integer> hashMap) {
    List<HashMap.Entry<String, Integer>> list1 =
            new ArrayList<HashMap.Entry<String, Integer>>(hashMap.entrySet());
    Collections.sort(list1, new Comparator<Map.Entry<String,Integer>>() {
        public int compare(Map.Entry<String,Integer> hash1, Map.Entry<String,Integer> hash2){
            if(hash1.getValue().equals(hash2.getValue()))
                return hash1.getKey().compareTo(hash2.getKey());
            return hash2.getValue().compareTo(hash1.getValue());
        }
    });
    return list1;
}

/**
    * 对关键词数量及年份分别排序
    * @param hashMap 存放关键词-年份
    * @return
    */
public static List<HashMap.Entry<String, Integer>> getSortedList2(HashMap<String, Integer> hashMap) {
    List<HashMap.Entry<String, Integer>> list1 = new ArrayList<HashMap.Entry<String, Integer>>(hashMap.entrySet());
    Collections.sort(list1, new Comparator<Map.Entry<String,Integer>>() {
        public int compare(Map.Entry<String,Integer> hash1, Map.Entry<String,Integer> hash2){
            if(hash1.getKey().split(",")[1].equals(hash2.getKey().split(",")[1]))
                return hash2.getValue().compareTo(hash1.getValue());
            return hash1.getKey().split(",")[1].compareTo(hash2.getKey().split(",")[1]);
        }
    });
    return list1;
}
  • 解析数据库中的所有数据返回排序后的精简数据,方便后续返回给前端数据
/**
* 预存入动态柱状图所需第二个参数
* @throws JsonProcessingException
*/
@GetMapping("/json22")
@ResponseBody
public void getSecondJson2() throws JsonProcessingException {
    List<List<String>> json2 = new ArrayList<>();
    List<String> jsonson2 = new ArrayList<>();
    HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
    List<NameAndYear> keyandyear = new ArrayList<>();
    keyandyear = trendService.getYear();

    List<String> keywords = new ArrayList<>();
    List<String> publicationYear = new ArrayList<>();

    for (int i = 0;i<keyandyear.size();i++){
        String key = keyandyear.get(i).getKeywords();
        if (key == null) continue;
        else key = key.toLowerCase();
        String year = keyandyear.get(i).getPublicationYear();
        keywords.add(key);
        publicationYear.add(year);

    }

    String a = new String();
    String numm = new String();
    String str = new String();
    for (int q = 0; q < keywords.size(); q++) {
        numm = publicationYear.get(q);
        a = keywords.get(q);
        if(a==null){
            continue;
        }

        str = a.replace("\"", "").replace("(","").replace(")","");

        String[] chars = new String[2000];
        chars = str.split(",");
        String strr = new String();
        for (int j = 0; j < chars.length; j++) {
            strr = chars[j]+","+numm;
            if(hashMap.containsKey(strr)){
                hashMap.put(strr,hashMap.get(strr)+1);
            }
            else {
                hashMap.put(strr,1);
            }
        }
    }

    jsonson2.add("Income");
    jsonson2.add("Life Expectancy");
    jsonson2.add("Population");
    jsonson2.add("Country");
    jsonson2.add("Year");
    json2.add(jsonson2);

    List<HashMap.Entry<String, Integer>> count = getSortedList(hashMap);
    List<HashMap.Entry<String, Integer>> count2 = new ArrayList<>();
    count = getSortedList2(hashMap);//
    int size = count.size();
    HashMap<String ,Integer> yearnumhash = new HashMap<>();
    for(int l = 0;l<size;l++){
        String year = count.get(l).getKey().replace("\"", "").replace("(","").replace(")","").split(",")[1];
        if(yearnumhash.containsKey(year)){
            yearnumhash.put(year,yearnumhash.get(year)+1);
        }else {
            yearnumhash.put(year,1);
        }
        if(yearnumhash.get(year)<21){
            count2.add(count.get(l));
        }else {
            continue;
        }
    }
    for(int y = 0;y<count2.size();y++){
        List<String> jsonson = new ArrayList<>();
        String name = count2.get(y).getKey().replace("\"", "").replace("(","").replace(")","").split(",")[0];
        String year = count2.get(y).getKey().replace("\"", "").replace("(","").replace(")","").split(",")[1];

        jsonson.add(String.valueOf(count2.get(y).getValue()));
        jsonson.add("");
        jsonson.add("");
        jsonson.add(name);
        jsonson.add(year);
        json2.add(jsonson);
    }
    ObjectMapper mapper3 = new ObjectMapper();
    String jsonlist = mapper3.writeValueAsString(json2).toString();
    trendService.insertTrend2(jsonlist);
}

七、心路历程和收获

cyl(221801128):
在对json文件的处理与数据库的存储时碰到了一些困难,后来通过查询文档及CSDN教程学习;
在绘制词云图以及动态柱状图的对图表结构不太明白,后来通过研究源码明白各段数据的意义,最后在后端获取需要的json文件;
对前端布局设计还不是很熟练,还需要多加练习。
收获:学会了springboot、mybatis框架的应用,AJAX获取后端数据,前后端数据的交互以及远程数据库的连接。

cjx(221801113):
本次结对作业收获还是很大的,在一开始看到作业的时候还是有丶小慌的,因为之前都是编写前端部分,后端都没有接触过,还好这次作业完成的比较顺利,这次学习了使用springboot+mybatis,还学习了服务器部署,顺便学习了下怎么用java进行爬取数据,感觉收获还是特别大的,期间也踩了挺多坑的,尤其在项目刚开始在使用git-desktop的时候代码出现直接被覆盖,数据丢失的情况,幸亏之前有备份,后来每次要进行git提交时都备份了代码,在学习框架创建项目的时候也遇到许多问题,好在室友、百度都能为我解答,在进行编码的时候还是很顺利的,爬取部分花费了一些时间,一开始选择的页面虽然可以直接下载json文件,但文件里每篇论文都没有摘要,后面选择的页面(虽然没有关键词,但无伤大雅)在进行请求时忘记设置请求头,一直接收不到数据。这次作业感觉很好锻炼了我学习新知识,解决未知问题的能力,感觉还是很不错的。

八、评价结对队友

cyl(221801128)对cjx(221801113)的评价:这是我和cjx同学的第二次合作,jx同学是一个编码能力很强,对设计要求很严格的同学,对我起到十分大的约束作用。在我编码碰到难题时,jx同学也总是热心解答,帮我度过山重水复的难关,从而顺利完成这一次的结对编程。
cjx(221801113)对cyl(221801128)的评价:这次合作还是非常愉快的,yl同学虽然之前对这方面很陌生,但学习能力很强,交代的任务能很好的完成。而且在完成自己的任务后,也帮我解决了许多小问题,希望下次还能和他进行合作。

posted @ 2021-03-31 20:37  AAAdmin  阅读(121)  评论(8编辑  收藏  举报