| 這個作業屬於哪個課程 | 2021軟件工程實踐|W班 (福州大學) |
|---|---|
| 這個作業要求在哪里 | 結對第二次作業——頂會熱詞統計的實現 |
| 結對學號 | 221801129、221801322 |
| 這個作業的目標 | 實踐Github協作開發、實現頂會熱詞統計 |
| 其他參考文獻 | Gin框架學習、Gorm、Gin、Vue |
Github倉庫鏈接和代碼規范鏈接
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語言進行開發
-
數據庫設計
根據用戶的需求,建立了一個
article和keyword表。考慮到一篇文章有多個關鍵詞,一個關鍵詞又可以對應多篇文章,一開始想要設計一個多對多的關聯表, 但是,一開始因為對gormAPI的不熟悉,存數據的過程中出現了很多問題無法解決。所以存論文數據的時候,將數據庫的表修改成了一對多的模式,方便了存數據,不可避免造成了數據的冗余。(項目后期發現解決方法,受於代碼結構,沒能更改)因為設計了收藏夾功能,創建了
user和bookmark表,bookmark實現user和article表的多對多關聯-
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);
}
});
后端代碼
-
返回所有頂會的熱詞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 } -
返回數據用於渲染趨勢圖(兩段代碼結合使用)
//相關結構體 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,為前端的頁面編寫提供了思路。隊友進行后端開發很不容易,而且還得學新框架,所以也沒有去特意催進度。希望在這次結對后,都能有所收獲,並將結對經驗應用到今后的學習生活中。
