結對第二次作業——頂會熱詞統計的實現


這個作業屬於哪個課程 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,為前端的頁面編寫提供了思路。隊友進行后端開發很不容易,而且還得學新框架,所以也沒有去特意催進度。希望在這次結對后,都能有所收獲,並將結對經驗應用到今后的學習生活中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM