結對作業二


這個作業屬於哪個課程 2021春軟件工程實踐 | W班 (福州大學)
這個作業要求在哪里 結對第二次作業——頂會熱詞統計的實現
結對學號 221801329|221801316
這個作業的目標 1. 學會web項目開發 2.感受結對編程中前后端分離開發 3. 學習前后端開發知識 4. 學會部署項目至雲服務器
其他參考文獻
Github倉庫地址 PairProject

一、項目鏈接

項目地址:PaperSearcher

用戶:ldy,密碼:123456登錄完一定要退出!

Github地址:點這兒

前后端代碼規范:別點錯了

二、PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划
• Estimate • 估計這個任務需要多少時間 20 15
Development 開發
• Analysis • 需求分析 (包括學習新技術) 60 120
• Design Spec • 生成設計文檔 20 15
• Design Review • 設計復審 10 7
• Coding Standard • 代碼規范 (為目前的開發制定合適的規范) 20 40
• Design • 具體設計 20 15
• Coding • 具體編碼 2520 3240
• Code Review • 代碼復審 30 15
• Test • 測試(自我測試,修改代碼,提交修改) 90 120
Reporting 報告
• Test Repor • 測試報告 30 45
• Size Measurement • 計算工作量 10 6
• Postmortem & Process Improvement Plan • 事后總結, 並提出過程改進計划 15 10
合計 2845 3648

三、項目介紹

總體介紹

項目分為登錄、主頁兩個頁面

  • 未登錄無法訪問主頁,並沒有設計注冊頁面
  • 登錄主頁后,左側邊欄是主頁、論文列表、論文分析、收藏夾
  • 主頁右側邊欄為裝飾效果,除收藏夾按鈕登出按鈕無其他交互

模塊介紹

主頁

簡單展示論文、只是為了美觀

正上方的搜索欄無論點擊哪個選項卡都可以使用

論文列表

論文列表用於展現查詢出來的論文信息,每個論文分為標題、作者、部分摘要、關鍵詞來展示

標題的右上角為收藏按鈕,用戶可以點擊自己喜歡的論文添加進收藏夾中方便查閱

下拉到底下為分頁塊,若查詢到的論文數量不足分頁只會顯示一頁;未查詢到則顯示“No Result”

論文分析

該板塊由兩張圖表組成

上方的圖表是涵括近五年來三大頂會熱門話題所發表論文數,用戶可以點擊對應扇形區域展開圖表查看詳細數據

下方的圖表涵括近五年來三大頂會熱門關鍵詞發表論文總數趨勢,在該圖標的上方為餅圖,展現了某年10個關鍵詞在總和中的比例,下方的曲線圖則展示五年來發表論文數的趨勢變化,鼠標懸停在某一結點,上方餅圖則會對應顯示該年論文比例

收藏夾

收藏夾是為了方便用戶查看自己需要保存記錄的論文而創立,考慮到用戶不能直接對總論文列表進行刪改操作,將刪除論文彈性划分為取消收藏這一功能,與論文列表結構一致,但只能看到自己所收藏的論文

四、結對過程

YK:這給了數據以后看起來一兩天就能做完,后端也就查詢,整理格式,收藏,總的來說都是CRUD沒啥東西的感覺,前端我寫我就盡快寫一下和原型長得差不多的吧
DY:ok,但是感覺一兩天不夠
YK:先試試看
YK:我查了一點資料,主頁雛形差不多出來了,你可以給我接口了
DY:???太快了,再等一會(開始摸魚)
小故事到這里結束!!!

我們兩個人是前后端分離開發,有需要的時候向對方請求接口/數據驗證/結果驗證;
YK負責前端,也不會vue也不會js,都是從0手擼html和css,一邊學一邊找資料,實際上發現寫的效率還是挺高的,就是沒有及時和后端對接,讓DY偷偷摸了幾天的魚
DY在隨后的一兩天是進行數據庫設計,並對助教打包好的json數據進行解析,制定代碼規范;在json解析的過程中遇到了很多坑,比如json末尾居然是分號關鍵詞、摘要太長,摘要、作者是空的,於是我們決定把含有空字段的數據做了一定處理:摘要太長→字符串截取,做一定非空判斷
因為我們是舍友,在宿舍討論比較方便,轉頭就可以瘋狂提問/要接口

圖為在宿舍討論

YK平時在實驗室,就采用線上QQ聯系來對接,遇到問題的時候我們會把具體出錯的地方告訴對方

圖為線上討論


五、實現過程

最終我們是確定了如下結構

前端設計

分為登錄、主頁兩個頁面

登錄

登錄頁面就與平時見到的差不多,沒什么技術性在里頭

主頁

主頁最基本的功能就是要獲取論文信息、收藏夾、熱點趨勢等
想嘗試一下選項卡式的布局,在左側邊欄設置了四個選項卡,其中的切換都是依靠js完成的(從后台獲取數據添加到innerHTML中),直到團隊作業我學了vue以后才知道全靠js是很不規范的一種方案

論文列表、收藏夾

論文列表展現的是用戶所可以看到的信息,我參考了百度搜索的布局樣式,設計了白底藍灰字色的樣式,一篇論文對應一個block,看起來比較簡潔美觀,信息依靠模板樣式展現,查詢則是根據用戶輸入、選擇字段來向后端發起不同路由的請求

論文分析

論文分析頁面展現的是一些熱點關鍵詞的趨勢變化,需要直截了當,旭日圖能較好的看到全局的數量分布情況,其可交互性很強,用戶可以直接選擇自己需要了解的方向去查看,我很喜歡這樣的設計,所以就用上了
另一個圖是表達熱點趨勢變化的圖,我和DY挑了很久才挑出這個餅圖+曲線圖的樣式,它的可交互性也很強
圖中所有數據都是向后端請求后所生成的,數據格式的規定是參考了echarts和highcharts的格式

交互彈窗

交互彈窗我是用了以前在西二后端考核時候接觸到的swal,個人感覺他的風格比較簡潔

后端設計

數據庫設計

本次項目中,主要是對論文數據進行存儲和操作。首先設計了一張論文表,里面存儲論文的字段信息,如標題,摘要,鏈接等等。
由於一篇論文對應着多個關鍵詞,多個作者,一個關鍵詞或者作者也對應着多篇論文,它們之間存在着多對多的關系,因此還需設計兩張論文-關鍵詞、論文-作者的關聯表。
我們還對項目進行了功能拓展,增加了用戶模塊和收藏夾模塊,因此設計了用戶表和用戶-論文關聯表,從而達到注冊登錄以及增加/移除收藏的功能。
具體表結構見如下ER圖

代碼設計

本次項目采用了MVC模式,使用了springboot框架,Controller層負責提供接口與前端交互,Service層負責業務邏輯處理,Dao層負責數據持久化,參與數據庫的交互,Pojo下存放着實體類

  • 數據解析
    對文件進行讀取,遍歷每一個.json文件,利用JSONObject解析json,指定所需要的數據的key值,將得到的數據分裝入實體類中,最終存入數據庫。
    當然,本次不同會議的.json文件中key值不完全一致,既存在英文也存在中文,需要寫分支對其按不同情況處理
  • 論文搜索
    與前端協商好交互的數據格式,根據傳入的參數不同,實現不同搜索功能。如按關鍵詞模糊查詢,按作者模糊查詢...實現方式主要是sql語句的編寫
    在查詢的時候前端提供偏移量和頁面大小,后端根據兩個參數查詢指定的內容並且返回總條目數和數據,從而實現分頁功能
  • 論文分析
    主要編寫sql語句,對近五年的三大會議的Top10關鍵詞進行統計。與前端確定所需要展示的圖表並且分析圖表中data數據的結構,按照結構將后端查詢的數據整合,返回給前端。
    主要使用的是JSONObject和JSONArray,兩者嵌套使用,可以靈活地向前端提供數據。
  • 用戶模塊和收藏夾模塊
    該模塊為我們本次項目的拓展功能,用戶根據用戶名和密碼,實現簡單的注冊和登錄,由於時間比較緊迫,沒有考慮安全和加密等方面的功能。用戶登錄后在論文搜索的結果頁面中,可以點擊收藏按鈕,對喜歡的論文進行收藏。
    前端將該論文的id和該用戶的id傳給后端,后端對其進行存儲,建立用戶與論文的管理。在收藏夾頁面,聯合查詢論文表和用戶-收藏表,可以查看該用戶收藏的論文列表。

六、關鍵代碼說明

前端

登錄、登錄狀態檢測、登出

//login.js
function login() {
    let username = document.getElementById('username').value;
    let password = document.getElementById('password').value;
    //采用axios來進行網絡連接,操作比ajax方便
    instance.post('/login', {
            username: username,
            password: password
        })
        .then(res => {
            if (res.data.userId !== -1) {
                //采用session存儲登錄信息,為隨后的判定登錄做准備
                window.sessionStorage.setItem('username', username);
                window.sessionStorage.setItem('isLogin', true);
                window.sessionStorage.setItem('userId', res.data.userId);
                swal("登錄成功!", "即將為您跳轉至主頁……", "success");
                window.setTimeout(3000);
                window.location.href = 'index.html';
            } else {
                swal("用戶名或密碼錯誤!", "請重新登錄", "error")
            }
        })
}

//paperList.js
//獲取session判斷用戶是否登錄,若未登錄則返回登錄界面
let isLogin = window.sessionStorage.getItem('isLogin');
if (!isLogin) window.location.href = 'login.html';

//登出為清除session並跳轉至登陸界面
function logout() {
    sessionStorage.clear();
    window.location.href = 'login.html';
}

論文列表實現

//因為是論文列表和收藏夾用同一套代碼,就封裝成函數了
function setList(data, pageNum, type) {
    if (data.length === 0) {
        panel.innerHTML = panel.innerHTML + "<p style=\"text-align:center;color: rgb(127, 127, 127);\">No result</p>";
    } else {
        let list = data.paper;
        for (let k in list) {
            //定義一個臨時變量方便操作
            let element = list[k].data;
            //有的摘要太多了,砍掉一些內容
            let abstractStr = element['abstractContent'].slice(0, 100) + "...";
            let authorStr = "";
            let keywordStr = "";
            //拼接作者信息,最多只顯示5個
            for (let t in element.author) {
                authorStr += element.author[t] + ';';
                if (t >= 3) {
                    break;
                }
            }
            //去除最后的分號
            authorStr = authorStr.slice(0, -1);
            //拼接關鍵詞信息,最多只顯示5個
            for (let t in element.keywords) {
                keywordStr += element.keywords[t] + ';';
                if (t >= 3) {
                    break;
                }
            }
            //同樣的去分號
            keywordStr = keywordStr.slice(0, -1);
            //標記是否收藏
            let sytle = "like";
            let src = '../img/gary-star.svg'
            //若該條論文被收藏,則收藏圖標亮起
            if (list[k].isLike === 1) {
                src = '../img/orange-star.svg'
            }
            //寫入頁面
            panel.innerHTML = panel.innerHTML +
                "<div class=\"paper-list\" id=" +
                element.id +
                "><a href=" +
                element.link +
                " class=\"paper-title\">" +
                element.title +
                "</a>" +
                "<p class=\"paper-author\">" +
                authorStr +
                "</p> <p> <span class=\"paper-abstract-title\">[Abstract]</span>" +
                "<span class=\"paper-abstract-detial\">" +
                abstractStr +
                "</span></p>" +
                "<p><span class=\"paper-keyword\">[Keyword]</span>" +
                "<span class=\"paper-keyword-list\">" +
                keywordStr +
                "</span></p>" +
                "<img src=" + src + ' onclick=like(' +
                element.id + ')' + ' id=Like' +
                element.id + ' class=' + sytle + '>' +
                '</div>'

        }
        //分頁部分
        initPagination(pageNum, Math.floor(data.total / 10) + 1, type);
    }
}

分頁實現

//分頁框部分實現
function initPagination(currentPage, totalPage, type) {
    console.log(totalPage)
    panel = document.getElementById('main-panel');
    let start;
    let end;
    //將頁數控制在8頁以內
    if (totalPage < 8) {
        start = 1;
        end = totalPage;
    } else {
        start = currentPage - 4;
        end = currentPage + 3;
        if (start < 1) {
            start = 1;
            end = start + 7;
        }
        if (end > totalPage) {
            end = totalPage;
            start = end - 7;
        }
    }
    //添加分頁欄
    let str = '<nav aria-label="Page navigation">' +
        '<ul class="pagination">';
    //判斷是列表還是收藏夾,在li中添加不同函數
    if (type === 'like') {
        for (let i = start; i <= end; i++) {
            if (currentPage == i - 1) {
                var li = "<li class=\"active\"><a onclick=getLikeList(" + (i - 1) + ")>" + i + "</a></li>";
            } else {
                var li = "<li><a onclick=getLikeList(" + (i - 1) + ")>" + i + "</a></li>";
            }
            str += li;
        }
    } else if (type === 'list') {
        for (let i = start; i <= end; i++) {
            if (currentPage == i - 1) {
                var li = "<li class=\"active\"><a onclick=getPaperList(" + (i - 1) + ")>" + i + "</a></li>";
            } else {
                var li = "<li><a onclick=getPaperList(" + (i - 1) + ")>" + i + "</a></li>";
            }
            str += li;
        }
    }
    str += '</ul></nav>'
    panel.innerHTML = panel.innerHTML + str;

收藏實現

function like(data) {
    //根據id獲取元素
    let ID = 'Like' + data;
    let star = document.getElementById(ID);
    let src = star.getAttribute('src');
    let router = '';
    //根據收藏圖標判斷收藏/取消收藏路由
    if (src === '../img/gary-star.svg') {
        router = '/addLike'
    } else {
        router = '/deleteLike'
    }
    //請求部分
    instance.get(router, { params: { userId: sessionStorage.getItem('userId'), paperId: data } })
        .then(res => {
            if (router == '/addLike') {
                swal("收藏成功!", "點擊繼續", 'success')
            } else {
                swal("取消收藏成功!", "點擊繼續", 'success')
            }
            //請求結束以后需要修改按鈕狀態
            star.setAttribute('src', (src == '../img/gary-star.svg') ? '../img/orange-star.svg' : '../img/gary-star.svg');
        })
}

后端

service層關鍵代碼——旭日圖實現

@Override
    public List<JSONObject> queryTop10ByYear() {
        String []meets=new String[]{"CVPR","ECCV","ICCV"};
        Integer []years=new Integer[]{2016,2017,2018,2019,2020};
        List<JSONObject> data=new ArrayList<>();
        //0級數據
        Map<String,String> param0=new HashMap<>();
        JSONObject jsonObject0 =new JSONObject();
        jsonObject0.put("id","0.0");
        jsonObject0.put("parent","");
        jsonObject0.put("name","頂會五年總計");
        data.add(jsonObject0);
        //一級數據
        for(int i=0;i<3;i++){
           JSONObject jsonObject1=new JSONObject();
           jsonObject1.put("id","1."+i);
           jsonObject1.put("parent","0.0");
           jsonObject1.put("name",meets[i]);
           data.add(jsonObject1);
        }
        //二級數據
        int k=0;
        for(int i=0;i<3;i++){
            for(int j=0;j<5;j++){
                JSONObject jsonObject2=new JSONObject();
                jsonObject2.put("id","2."+k);
                jsonObject2.put("parent","0.0");
                jsonObject2.put("parent","1."+i);
                jsonObject2.put("name",String.valueOf(years[j]));
                data.add(jsonObject2);
                k++;
            }
        }
        //三級數據
        k=0;
        int n=0;
        for(int i=0;i<3;i++){
            for(int j=0;j<5;j++){
                //獲得第i個會議,第j年的前10關鍵詞及其數量
                List<Keyword> keywordMapList=paperMapper.queryTop10ByYear(years[j],meets[i]);
                //如果查詢不到記錄
                if(keywordMapList.size()==0){
                    for(int m=0;m<10;m++){
                        JSONObject jsonObject3=new JSONObject();
                        jsonObject3.put("id","3."+n);
                        jsonObject3.put("parent","2."+k);
                        jsonObject3.put("name", "nothing");
                        jsonObject3.put("value",1);
                        data.add(jsonObject3);
                        n++;
                    }

                }else{
                    //查詢得到記錄
                    for (Keyword keyword : keywordMapList) {
                        JSONObject jsonObject3=new JSONObject();
                        jsonObject3.put("id","3."+n);
                        jsonObject3.put("parent","2."+k);
                        jsonObject3.put("name", keyword.getName());
                        jsonObject3.put("value",keyword.getCount());
                        data.add(jsonObject3);
                        n++;
                    }
                }

                k++;
            }
        }
        return data;
 public List<Paper> cvprJsonParse() {
        List<Paper> paperList=new ArrayList<>();
        String dir=System.getProperty("user.dir");
        System.out.println(dir);
        File file=new File(dir+"c/main/resources/論文數據/1");
        if(file.exists()){
            File []child=file.listFiles();
            for(int i=0;i<child.length;i++){
                Paper paper=new Paper();
                String json=jsonRead(child[i]);
                json=json.replace(";","");
                JSONObject jsonObject=JSONObject.parseObject(json);
                String title=jsonObject.getString("title");
                String abstractContent=jsonObject.getString("abstract");
                if (abstractContent==null)abstractContent="暫無";
                if(abstractContent.length()>=150){
                    abstractContent=abstractContent.substring(0,150);
                }
                String link=jsonObject.getString("doiLink");
                String meet="CVPR";
                Integer year=Integer.valueOf(jsonObject.getString("publicationYear"));
                List<String> keywordList=new ArrayList<>();
                JSONArray keywords= jsonObject.getJSONArray("keywords");
                if(keywords!=null){
                    for(int j=0;j<keywords.size();j++){
                        JSONObject keyword=keywords.getJSONObject(j);
                        JSONArray jsonArray=keyword.getJSONArray("kwd");
                        for(int k=0;k<jsonArray.size();k++){
                            keywordList.add(jsonArray.getString(k));
                        }
                    }
                }
                else {
                    keywordList.add(" ");
                }
                List<String> authorList=new ArrayList<>();
                JSONArray authors=jsonObject.getJSONArray("authors");
                if(authors!=null){
                    for(int j=0;j<authors.size();j++){
                        JSONObject author=authors.getJSONObject(j);
                        authorList.add(author.getString("name"));
                    }
                }
                else{
                    authorList.add(" ");
                }
                paper.setTitle(title);
                paper.setAbstractContent(abstractContent);
                paper.setAuthor(authorList);
                paper.setKeywords(keywordList);
                paper.setLink(link);
                paper.setMeet(meet);
                paper.setYear(year);
                paperList.add(paper);
                System.out.println(paper.toString());
            }
        }
        else{
            System.out.println("文件不存在");
        }
        return paperList;
    }

Dao層主要代碼

    <select id="queryPaper" resultType="com.fzu.pojo.Paper">
        select id,title,abstract_content as "abstractContent",meet,`year`,link from paper_search.paper limit #{start},#{rows}
    </select>
    <select id="countAll" resultType="java.lang.Integer">
        select distinct count(*) from paper_search.paper
    </select>
    <select id="queryKeywords" resultType="java.lang.String">
        select keyword from paper_search.paper_keyword where paper_id=#{paperId}
    </select>
    <select id="queryAuthors" resultType="java.lang.String">
        select author from paper_search.paper_author where paper_id=#{paperId}
    </select>
    <select id="queryPaperByKeyword" resultType="com.fzu.pojo.Paper">
        select distinct a.id,a.title,a.abstract_content as "abstractContent",a.meet,a.year,a.link from paper a,paper_keyword b
        where b.keyword like '%${keyword}%' and a.id=b.paper_id limit #{start},#{rows}
    </select>
    <select id="queryByTitle" resultType="com.fzu.pojo.Paper">
        select distinct a.id,a.title,a.abstract_content as "abstractContent",a.meet,a.year,a.link from paper a
        where a.title like '%${title}%' limit #{start},#{rows}
    </select>
    <select id="queryPaperByAuthor" resultType="com.fzu.pojo.Paper">
        select distinct a.id,a.title,a.abstract_content as "abstractContent",a.meet,a.year,a.link from paper a,paper_author b
        where b.author like '%${author}%' and a.id=b.paper_id limit #{start},#{rows}
    </select>
    <select id="countAllByKeyword" resultType="java.lang.Integer">
        select distinct count(*) from paper a,paper_keyword b
        where b.keyword like '%${keyword}%' and a.id=b.paper_id
    </select>
    <select id="queryTop10ByYear" resultType="com.fzu.pojo.Keyword">
        select keyword as name,count(*) as `count`  from(select b.keyword from paper a,paper_keyword b where
        a.id=b.paper_id and a.year=#{year} and a.meet =#{meet}) as tmp group by keyword order by count(*) desc limit 10
    </select>

七、心路歷程和收獲

221801329(LYK)的收獲

不用框架寫前端還是挺吃力的,也只能用用課內知識去還原自己的原型,自己原型寫的多牛逼,實現的時候就哭的有多慘,這次我沒有遇到很大的問題,主要是和后端交互的時候需要規划好返回數據的結構,不然每次調試起來都要花費很多時間

221801316(LDY)的收獲

本次結對使我對springboot有了進一步的學習,也對MVC模式有了更深刻的理解。在這次項目中,我使用了之前未使用過的MyBatis框架,相比於之前的項目中直接使用的JDBC,代碼量減少了不少,並且它提供了數據映射功能,支持對象與數據庫字段的關系映射,方便了不少。
本次結對所需要實現的功能其實並不復雜,整體的邏輯思路還算清晰,但是許多細節的地方卻仍然需要注意。在完成了整個項目后,我也對本次編程過程進行了回顧和思考,其實大部分的時間並不是花在代碼的編寫,而是花在代碼bug的修改,可能是某一處無意的變量名寫錯,或者是
特殊情況如null時應該進行的處理,這些都是我們在專注的同時也可能忽略的東西,希望自己以后編程能夠更加嚴謹,並且要養成每個功能模塊完成后進行單元測試的習慣。除此之外對我還認識到了項目架構和需求分析的重要性,在實踐的過程中,由於前期對收藏夾功能保持着非必選的態度,因此
設計時沒有考慮到用戶模塊,在多數接口定義及實現完成之后,才想到需要添加用戶和收藏夾的功能,進而引發對已有的接口進行修改。總的來說,本次項目使我認識到了自己仍存在着許多不足,雖然基本的功能都能輕松實現,但是對功能結構的規划和代碼的優化,還有很大進步的空間。
希望接下來的階段里,自己能夠學習更多技術,積累更多的經驗,提高自己編程的效率!

八、隊友互評

221801329(LYK)對221801316(LDY)的評價

DY認真的時候效率還是很高的,就是摸魚的時候是真的摸,前期我主頁列表都寫好了他一個接口還沒放出來,把我急死了,不過后期兩個人一起認真寫確實能很快解決很多問題,下次還會和他合作,不過我想寫后端了,這次是因為兩個人都是后端我選了前端QWQ

221801316(LDY)對221801329(LYK)的評價

YK態度非常積極,執行力強,總是能在我松懈的時候提醒我要跟緊進度,希望在接下來的階段中能夠增強溝通交流,更加契合。


免責聲明!

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



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