结对作业二——顶会热词统计的实现

这个作业属于哪个课程 2021软件工程实践|W班 (福州大学)
这个作业要求在哪里 结对第二次作业——顶会热词统计的实现
结对学号 221801129、221801322
这个作业的目标 实践Github协作开发、实现顶会热词统计
其他参考文献 Gin框架学习GormGinVue

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

PaperCrawler

PairProject

Go后端代码规范

前端代码规范

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 15 15
Development 开发
· Analysis · 需求分析(包括学习新技术) 480 600
· Design Spec · 生成设计文档 30 60
· Design Review · 设计复审 30 60
· Coding Standard · 代码规范(为目前的开发指定合适的规范) 30 30
· Design · 具体设计 60 60
· Coding · 具体编码 1800 2100
· Code Review · 代码复审 60 120
· Test · 测试(自我测试,修改代码,提交修改) 480 600
Reporting 报告
· Test Report · 测试报告 30 60
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 时候总结,并提出过程改进计划 30 60
Total 合计 3055 3775

成品展示

1、进行登录并回到首页

2、首页搜索后跳到结果页展示论文列表

3、对论文列表进行分页展示

4、点击top10关键字展示相关搜索列表

5、三大顶会热词趋势展示

6、按关键词搜索显示论文列表

7、论文列表收藏

8、论文列表删除

9、在结果页按论文题目搜索显示论文列表

10、按论文编号搜索显示论文列表

结对讨论过程描述

收到需求后,两人还是有点懵的,因为确实感觉会来不及完成。然后,因为在第一次作业的时候就有讨论一些了,所以一开始两人的实现思想就很接近了,后面进行打代码过程的时候交流就比较顺利了

1.对于接口数据交互的讨论

2.对于关键词TOP10接口的讨论

3.结对使用局域网,进行代码测试

设计实现过程

前端实现过程

前端使用html+css+js语言以及jquery+vue框架进行开发

  • 页面设计

    整体页面的样式和布局做到尽量还原原型设计。首页、搜索结果页、收藏夹页共用一个nav,论文列表、热词top10通过vue框架的v-for指令进行循环显示。刚进首页时隐藏显示登录框,登录成功或点击除登录框外其他地方时登录框淡出。三大顶会的趋势图通过标签页形式在一个空间内切换显示。

  • 前后端交互

    前后端交互主要用ajax技术实现,统一进行post请求,请求参数和返回参数均为json格式,在请求时加上请求头"Content-Type": "application/json"。在传入请求参数时通过JSON.stringify()将参数转成json格式,对不能在回调函数内处理的数据通过localStorage存储起来,然后其他地方取出数据并处理。在回调函数中将响应参数进行相应处理并转成json格式,便与后续其他地方对数据的处理和展示。

后端实现过程

后端使用go语言进行开发

  • 数据库设计

    根据用户的需求,建立了一个articlekeyword表。考虑到一篇文章有多个关键词,一个关键词又可以对应多篇文章,一开始想要设计一个多对多的关联表, 但是,一开始因为对gormAPI的不熟悉,存数据的过程中出现了很多问题无法解决。所以存论文数据的时候,将数据库的表修改成了一对多的模式,方便了存数据,不可避免造成了数据的冗余。(项目后期发现解决方法,受于代码结构,没能更改)

    因为设计了收藏夹功能,创建了userbookmark表,bookmark实现userarticle表的多对多关联

    • article表

    • keyword表

    • user表

    • bookmark表

  • 后端代码

    使用MVC模式进行开发,model层使用go的第三方库gorm进行数据库交互,controller层采用轻量级的gin框架,负责调用model层与前端进行数据交互

代码说明

前端代码

1、论文列表展示

            <div class="list">
                <div class="listContent" v-for="list in lists">
                    <div class="title lineBreak" :data-paper-uid="list.article_id">
                        <h2 :title="list.title">{{ list.title }}</h2>
                    </div>
                    <div class="abstract">
                        <span>摘要:</span>
                        <div>
                            <p class="wrapBreak" :title="list.abstract">
                                {{ list.abstract }}
                            </p>
                        </div>
                    </div>
                    <div class="listBottom">
                        <span>关键词:</span>
                        <div class="keyword lineBreak" v-for="key in list.keyword" :title="key">{{ key }}</div>
                    </div>
                    <img src="../img/Result/delete.svg" class="deleteSvg" :id="delete(index)" alt="delete" title="删除">
                    <img src="../img/Result/mark.svg" class="markSvg" alt="mark" title="收藏">
                </div>
                <button id="btn" @click="getLists()" style="border-width: 0;"></button>
            </div>

2、热词top10展示

                <ul id="rank">
                    <li v-for="topRank in topRanks">
                        <div class="num-box">
                            <span :id="num(topRank.index)">{{ topRank.index }}</span> 
                        </div>
                        <div class="name-box lineBreak" :title="topRank.name">
                            <span :class="name(topRank.index)" @click="rankClick(topRank.name)">{{ topRank.name }}</span>
                        </div>
                        <div class="total-box lineBreak" :title="topRank.count">
                            <span>{{ topRank.count }}</span>
                        </div>
                    </li>
                </ul>

3、展示论文列表的js代码

function showList(urlStr,searchVal){
    PostHandle(urlStr, JSON.stringify(searchVal), function(data){
        if(data.code == 200){
            //console.log(data.data.articlelist[0].article_id);
            var articlelist = data.data.articlelist;
            var len = articlelist.length;
            var lists = [];
            for(var i = 0 ; i < len ; i++){
                var keywords = [];
                var keylen = articlelist[i].Keywords.length;
                if(keylen <= 3){
                    for(var j = 0 ; j < keylen ; j++){
                        keywords[j] = articlelist[j].Keywords[j].name;
                    }
                } else {
                    for(var k = 0 ; k < 3 ; k++){
                        keywords[k] = articlelist[k].Keywords[k].name;
                    }
                }
                lists[i] = {
                    title: articlelist[i].title,
                    abstract: articlelist[i].abstract,
                    article_id: articlelist[i].article_id,
                    keyword: keywords
                };
            };
            localStorage.setItem("lists",JSON.stringify(lists));
            localStorage.setItem("totalPage",data.data.pagetotal);
            $("#btn").trigger("click");
        } else {
            alert(data.code + " " + data.msg);
        }
    });
}
function getList(){
    return JSON.parse(localStorage.getItem("lists"));
}

var app1 = new Vue({
    el: '.list',
    data: {
        lists: getList()
    },
    methods:{
        getLists : function(){
            this.lists = getList();
        }
    }
});

4、分页点击展示论文列表

    methods: {
        btnClick: function(data){
            if(data != this.cur){
                this.cur = data;
            }
            console.log(this.cur+'页');
            //var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
            var urlStr = "http://192.168.0.110:8000/list";
            var searchVal = {
                pagenum: this.cur,
                type: 1,
                searchval: localStorage.getItem("searchVal")
            };
            showList(urlStr,searchVal);

        },
        pageClick: function(){
            console.log('现在在'+this.cur+'页');
            //var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
            var urlStr = "http://192.168.0.110:8000/list";
            var searchVal = {
                pagenum: this.cur,
                type: 1,
                searchval: localStorage.getItem("searchVal")
            };
            showList(urlStr,searchVal);
        },
    },

5、热词top10展示的js

    //var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/rank";
    var urlStr = "http://192.168.0.110:8000/rank";
    var rankVal = {
        type:"rank"
    };
    PostHandle(urlStr, JSON.stringify(rankVal), function(data){
        if(data.code == 200){
            var topList = data.data.top_list;
            var topRanks = [];
            for(var i = 0 ; i < 10 ; i++){
                topRanks[i] = {
                    name: topList[i].name,
                    count: topList[i].count,
                    index: i+1
                };
            }
            var app = new Vue({
                el: '#rank',
                data: {
                    topRanks: topRanks,
                },
                methods: {
                    num: function(index){
                        return "num" + index;
                    },
                    name: function(index){
                        return "name" + index;
                    },
                    rankClick :function(data){
                        //var urlStr = "https://mock.mengxuegu.com/mock/60634842f2e38f3a2f6ba3ec/example_copy/list";
                        var urlStr = "http://192.168.0.110:8000/list";
                        var searchVal = {
                            pagenum: 1,
                            type: 2,
                            searchval: data
                        };
                        showList(urlStr,searchVal);
                    }
                }
            });
        } else {
            alert(data.code + " " + data.message);
        }
    });

后端代码

  1. 返回所有顶会的热词TOP10列表

    func GetTopKeywordList() (results []KeyNum) {
    	var tmp1, tmp2 interface{}
    	rows, _ := db.Raw("SELECT name, count(*) FROM papercrawler.crawler_keyword group by name order by count(*) desc").Limit(10).Rows()
    	defer rows.Close()
    	for rows.Next() {
    		rows.Scan(&tmp1, &tmp2)
    		count, _ := strconv.Atoi(string(tmp2.([]byte)))
    		name := string(tmp1.([]byte))
    		results = append(results, KeyNum{
    			Name:  name,
    			Count: count,
    		})
    	}
    	return
    }
    
  2. 返回数据用于渲染趋势图(两段代码结合使用)

    //相关结构体
    type Chart struct {
    	Forum     string     `json:"forum"`
    	Years     []string   `json:"years"`
    	KeyValues []KeyValue `json:"key_values"`
    }
    
    type KeyValue struct {
    	Name   string `json:"name"`
    	Counts []int  `json:"counts"`
    }
    
    type KeyYearCount struct {
    	Name  string `gorm:"column:name"`
    	Year  string `gorm:"column:year"`
    	Count int    `gorm:"column:count(*)"`
    }
    
    //获取某个顶会TOP关键词
    func GetTOPByForum(forum string, size int) (topkeys []TopKey) {
    	db.Raw("SELECT forum, name, count(*) FROM papercrawler.crawler_keyword where forum = '" +
    		forum + "' group by name order by count(*) desc").Limit(size).Find(&topkeys)
    	return
    }
    
    //获取图表所需数据
    func GetChart(forum string, topkeys []TopKey) (chart Chart) {
    
    	var keyyearcounts []KeyYearCount
    	for _, topkey := range topkeys {
    		db.Raw("SELECT name, year, count(*) FROM crawler_keyword WHERE forum = '" + topkey.Forum + "' " +
    			"and name = '" + topkey.Name + "'").Group("year").Find(&keyyearcounts)
    
    		var keyvalue KeyValue
    		keyvalue.Name = keyyearcounts[1].Name
    		if forum == "ECCV" {
    			for i := 0; i < 11; i++ {
    				keyvalue.Counts = append(keyvalue.Counts, 0)
    			}
    			for _, keyyearcount := range keyyearcounts {
    				index, _ := strconv.Atoi(keyyearcount.Year)          //获取对应的年份
    				keyvalue.Counts[(index-2000)/2] = keyyearcount.Count //给对应年份赋值
    			}
    		} else {
    			for i := 0; i < 21; i++ {
    				keyvalue.Counts = append(keyvalue.Counts, 0)
    			}
    			for _, keyyearcount := range keyyearcounts {
    				index, _ := strconv.Atoi(keyyearcount.Year)      //获取对应的年份
    				keyvalue.Counts[index-2000] = keyyearcount.Count //给对应年份赋值
    			}
    		}
    		chart.KeyValues = append(chart.KeyValues, keyvalue)
    	}
    
    	chart.Forum = forum
    	if forum == "ECCV" {
    		for i := 2000; i <= 2020; i = i + 2 {
    			chart.Years = append(chart.Years, strconv.Itoa(i))
    		}
    	} else {
    		for i := 2000; i < 2021; i++ {
    			chart.Years = append(chart.Years, strconv.Itoa(i))
    		}
    	}
    	return
    }
    

心路历程和收获

  • 221801129
    • 之前虽然有过一次web开发经验,但是是使用JAVA原生的servlet作为后端的开发,重复的代码部分无法有效的复用,深刻地体会到原生的开发效率之低,所以这次便下定决心要使用框架进行一次前后端分离的开发
    • 在结对作业之前,先进行了组队,由于团队的后端开发采用go语言和gin框架,便趁此机会学习并应用,为后面的团队开发打下基础
    • 在学习和实践过程中,虽然老师延长了期限,但是因为先进行了框架和语言的学习,导致最后代码时间有点赶。所以感觉在有实践项目时,最好采用自己熟悉的框架和语言进行开发,然后在课余时间进行相关的学习,加强自己对框架的理解和运用
    • 两人结对,因为前后端分离,一开始两人都是在各做各的,缺少交流,有点串行的合作,导致效率不是很高,也进而导致了一些功能没有实现
    • 虽然又很多遗憾,但是还是学到了宝贵的后端知识
  • 221801322
    • 之前有过一些web项目经历,但由于自身web基础不够扎实,所以实现起来效果不是特别好,所以就趁这次结对巩固下web基础并学习一些新知识。
    • 结对的过程中经历了一次团队实践,所以发现了并纠正了自己前后对接中出现的问题。这也使得后面结对编程的前后端对接不会出现类似的bug,使结对进行得更加顺利。
    • 结对项目中使用了一些vue框架,虽然方便了代码编写,但也由于自身对vue的了解不深,使得相关的bug更容易出现并且更难修好。
    • 结对过程中比较少给对方催进度,虽然这样每个人编写代码的自由度大,但也容易使得每个人的进度变慢,影响到了最后的实现。

评价结对队友

  • 221801129 to 221801322

    这次的结对整体而言还是挺ok的,队友在前端方面有能力照着原型图构造页面,在讨论接口的时候能给出一些合理的建议,交流沟通虽然有些分歧,但是最后总能达成共识。同时,队友能够在时间紧迫的情况下,还能比较冷静地debug。这次结对,虽有遗憾,两人都有一定责任,但是我对于整体还是持积极的态度(生活不易)。希望我俩在这次结对后,都有反思,都能有进步

  • 221801322 to 221801129

    这次结对总体还是比较满意的,沟通过程也较为顺利。队友的后端能力很可靠,所以自己能够更专注于前端工作。在构造接口时,队友能够结合前端的需求改进相应的api,为前端的页面编写提供了思路。队友进行后端开发很不容易,而且还得学新框架,所以也没有去特意催进度。希望在这次结对后,都能有所收获,并将结对经验应用到今后的学习生活中。

posted @ 2021-07-10 11:35  大萌神  阅读(57)  评论(0编辑  收藏  举报