這個作業屬於哪個課程 | 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
框架,使用了axios
、echarts
、view-design
、vue-router
、vue-woedcloud
、vuescroll
、vuex
插件。
功能結構圖
界面結構圖
設計概述
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同學合作。