结对编程(2/2)

作业所属课程 2021春/S班
作业要求 结对作业(2/2)
结对学号 221801118 & 221801136
作业目标 上一次结对作业中,大家都和小伙伴完成了原型的设计,那么现在再联合小伙伴动手实现原型中的部分功能吧。因为了解到大家基本都有一定的web基础,本次作业便要求大家采用web技术来实现原型中的功能。
其他参考文献 《构建之法》


 
 
 


一、作业描述

1. 基础功能
功能1:对已爬取的论文列表进行操作

  • 可对论文列表进行删除;
  • 可对论文列表进行查询详细信息(支持模糊查询,查询结果的展示、排序等功能可自行设计);
    功能2:分析已爬取到的论文信息,提取top10个热门领域或热门研究方向
  • 形成如关键词图谱之类直观的查看方式,点击某个关键词可展现相关的论文;
  • 可对多年间、不同顶会的热词呈现热度走势对比,以动图的形式呈现(这里将范畴限定在计算机视觉的三大顶会CVPR、ICCV、ECCV内)
    2. 附加功能
    功能3:获取待爬取论文列表及论文信息爬取
  • 支持用户输入单个论文题目,也支持批量导入论文列表;
  • 通过论文列表,爬取论文的摘要、关键词、原文链接;
    • 数据来源网站:CVPR、ICCV、ECCV
    • 至少爬取三年、三大顶会各300篇论文
    • 可编写爬虫代码实现,也可使用爬虫工具(如八爪鱼)爬取
      此功能为附加功能,实现此功能将获得附加分(详情见评分细则),如果技术上存在难度,可使用助教提供的数据来完成功能1和功能2

3. 本次项目需要部署到云服务器上,并且在博客中给出链接
4. 推荐基于Web来开发,使用Web框架,如常见的JSP、Servlet、spring系列、flask、php、express等,如果技术存在难度也可以直接使用纯前端来进行开发,数据写在代码中,持久化在storage。Web使用的持久化建议持久化在嵌入式数据库中,如sqllite或derby。采用的技术应该具有平台兼容性,不依托于具体的环境。本次作业以实现功能为主要目标,技术考量仅仅占一定的分数。切勿用原型工具生成代码,一经发现直接0分。
5. 可以扩展你想扩展的功能,扩展功能会被记入作为附加分(不超过编码部分的15%)。

 
 


二、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
• Estimate • 估计这个任务需要多少时间 40 50
Development 开发
• Analysis • 需求分析 (包括学习新技术) 450 530
• Design Spec • 生成设计文档 60 60
• Design Review • 设计复审 30 50
• Coding Standard • 代码规范 (为目前的开发制定合适的规范) 30 30
• Design • 具体设计 120 160
• Coding • 具体编码 3000 3600
• Code Review • 代码复审 60 50
• Test • 测试(自我测试,修改代码,提交修改) 240 290
Reporting 报告 120 100
• Test Report • 测试报告 40 50
• Size Measurement • 计算工作量 30 30
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 60 60
合计 4280 5020

 
 


三、Git仓库和代码规范

Git仓库地址 PairProject

 

代码规范地址 Codestyle

 
 


四、成品展示

 

  • GIF展示
    Gif

 

  • 首页显示登录界面
    登录
     
  • 注册界面,新用户注册
    注册
     
  • 点击论文搜索按钮,将模糊搜索到的论文详情展示到该列表里
    列表
     
  • Top10关键词,点击关键词可展示相关的论文
    top10
     
  • 对统计到的论文数据进行数据统计,并用图表展示
    图表
     
  • 用户个人页面,右侧展示用户收藏列表
    我的

 
 


五、结对讨论过程描述

 

由于都是隔壁舍友,都在当面讨论,线上讨论记录很少

讨论

聊天截图

 
 


六、设计实现过程

  • 功能结构
    功能结构图

 

  • 数据库表结构
    analyzed_thesis表图 user_like表图 user表图

 
 


七、代码说明

 

  • 后端代码

论文搜索
思路:根据用户输入的关键词,从前端传到后端进行模糊搜索后将结果返回至前端。主要在于对关键词的处理,将关键词中的分隔符转为'%',利用sql语句的like语句进行搜索。包括Top10关键词的搜索,点击相应的关键词,相当于只传入关键词这个模糊信息,同样用此函数可搜索。

// GetThesisList 模糊搜索、查找相应文章
func GetThesisList(c *gin.Context) {
	param := thesisSearchParam{}
	_ = c.ShouldBindJSON(&param)

	source := param.Source
	year := param.Year
	keyword := param.Keyword
	page := param.Page
	yearStr := strconv.Itoa(year)

	keyword = strings.ReplaceAll(keyword, "\n", "%")
	keyword = strings.ReplaceAll(keyword, "\r", "%")
	keyword = strings.ReplaceAll(keyword, "\t", "%")
	keyword = strings.ReplaceAll(keyword, " ",  "%")
	keyword = "%" + keyword + "%"

	var selectStr string = "select * from analyzed_thesis where "
	if source != "" {
		selectStr += "Source = '" + source + "' "
	}
	if yearStr != "" {
		if source != "" {
			selectStr += "and "
		}
		selectStr += "Year = '" + yearStr + "' "
	}
	if keyword != "" {
		if !(source == "" && yearStr == "") {
			selectStr += "and "
		}
		selectStr += "Keyword like '" + keyword + "' "
	}
	if source == "" && yearStr == "" && keyword == "" {
		length := len(selectStr) - 6
		selectStr = selectStr[0:length]
	}
	if page >= 1 {
		page --
	} else {
		page = 0
	}
	page *= 4
	pageStr := strconv.Itoa(page)
	selectStr += " limit " + pageStr + " , 4"
	selectStr += " ;"


	//var Keys []string

	rows, _ := database.DB.Raw(selectStr).Rows()
	var ID int
	var Source string
	var Year int
	var Title string
	var Author string
	var Keyword string
	var Abstract string
	var thesisArr []AnalyzedThesis
	for rows.Next() {
		temp := AnalyzedThesis{}
		_ = rows.Scan(&ID, &Source, &Year, &Title, &Author, &Keyword, &Abstract)
		temp.ID = ID
		temp.Source = Source
		temp.Year = Year
		temp.Title = Title
		temp.Author = Author
		temp.Keyword = Keyword
		temp.Abstract = Abstract
		thesisArr = append(thesisArr,temp)

		//Keys = append(Keys, Keyword)
	}

	//c.JSON(http.StatusOK, Keys)
	c.JSON(http.StatusOK, thesisArr)
}

 
用户注册
思路:用户输入要注册的用户名和密码,传到后端判断该账户名是否已存在,若存在,则注册失败;否则,注册成功,用户表中增加相应的记录。(登录功能类似)

// UserRegister 用户注册
func UserRegister(c *gin.Context) {
	param := loginParam{}
	_ = c.ShouldBindJSON(&param)
	username := param.Username
	password := param.Password

	rows, _ := database.DB.Raw("select * from user where username = ? ", username).Rows()
	var i int
	if rows.Next() {
		i++
	}

	if (i == 0 && len(username) <= 60 && len(password) >= 6) {
		affected := database.DB.Exec("insert into user values(?, ?)", username, password).RowsAffected
		if(affected == 1){
			c.JSON(http.StatusOK, gin.H{"ifSucceed":true})
		} else {
			c.JSON(http.StatusOK, gin.H{"ifSucceed":false})
		}
	} else {
		c.JSON(http.StatusOK, gin.H{"ifSucceed":false})
	}
}

 
用户添加收藏
思路:判断该用户是否已经收藏该论文,若用户收藏表中有对应记录,则表示已收藏;若没收藏记录,则增加收藏记录。(删除收藏功能类似)

// UserAddLike 用户增加收藏
func UserLikeAdd(c *gin.Context) {
	param := userLikeParam{}
	_ = c.ShouldBindJSON(&param)
	username := param.Username
	thesisID := param.ThesisID

	//先判断用户和论文是否都存在
	user, _ := database.DB.Raw("select * from user where username = ? ", username).Rows()
	var i int
	if user.Next() {
		i++
	}
	thesis, _ := database.DB.Raw("select * from analyzed_thesis where ID = ? ", thesisID).Rows()
	var j int
	if thesis.Next() {
		j++
	}
	//判断该用户是否已收藏该文章
	rows, _ := database.DB.Raw("select * from user_like where username = ? and thesis_id = ? ", username, thesisID).Rows()
	var k int
	if rows.Next() {
		k++
	}

	if i>0 && j>0 && k==0 {
		x := database.DB.Exec("insert into user_like(username, thesis_id) values(?,?)", username, thesisID).RowsAffected
		if( x==1 ) {
			c.JSON(http.StatusOK, gin.H{"ifSucceed":true})
		} else {
			c.JSON(http.StatusOK, gin.H{"ifSucceed":false})
		}
	} else {
		c.JSON(http.StatusOK, gin.H{"ifSucceed":false})
	}
}

 
返回用户收藏列表
思路:根据用户名,搜索用户收藏表(user_like),取出用户收藏的所有文章ID,再根据ID返回所有的收藏论文。

// UserShowLike 返回用户收藏列表
func UserLikeShow(c *gin.Context) {
	param := showUserLikeParam{}
	_ = c.ShouldBindJSON(&param)
	username := param.Username

	//取出该用户所有收藏文章的ID号
	idRows ,_ := database.DB.Raw("select thesis_id from user_like where username = ?", username).Rows()
	var thesisIdArr []int
	var tempID int
	for idRows.Next() {
		_ = idRows.Scan(&tempID)
		thesisIdArr = append(thesisIdArr, tempID)
	}

	//根据收藏ID数组查找论文
	rows, _ := database.DB.Raw("select * from analyzed_thesis where ID in ?", thesisIdArr).Rows()
	var ID int
	var Source string
	var Year int
	var Title string
	var Author string
	var Keyword string
	var Abstract string
	var thesisArr []thesisSearch.AnalyzedThesis
	for rows.Next() {
		temp := thesisSearch.AnalyzedThesis{}
		_ = rows.Scan(&ID, &Source, &Year, &Title, &Author, &Keyword, &Abstract)
		temp.ID = ID
		temp.Source = Source
		temp.Year = Year
		temp.Title = Title
		temp.Author = Author
		temp.Keyword = Keyword
		temp.Abstract = Abstract
		thesisArr = append(thesisArr,temp)
	}

	c.JSON(http.StatusOK, thesisArr)
}
  • 前端代码
    通过searchInput内输入的关键字向后端发送user以及关键字信息,在接受后端发来的数据,分别加入到相应的容器中显示。
//关键词查询后显示论文
    $("#searchBtn").click(function (){
       var keyword=$("#searchInput").val();
       // var year=$("#searchInput2").val();
       // var source=$("#searchInput3").val();
       var page=$("#page").val();
       //如果为空
       if(keyword==''){
           alert("请输入查找内容");
       }
       //不为空则从后端获得论文数据
        else {
            $.ajax({
                url:"http://127.0.0.1:8080/list/search",
                method:"post",
                data:JSON.stringify({
                    "keyword":keyword,
                    // "year":year,
                    // "source":source,
                    "page":page
                },),
                contentType:"application/json",
                success:function (data){
                    //显示论文数据
                    $.each(data,function(index,data){
                        $(".num").eq(index).text(data.id);
                        $(".link").eq(index).text(data.link);
                        $(".title").eq(index).text("标题:"+data.title);
                        $(".keyword").eq(index).text("关键词:"+data.keyword);
                        $(".summary").eq(index).text("摘要:"+data.abstract);
                        $("a").eq(index).attr('href',data.link);
                    })
                },
                error:function (){
                    alert("页面丢失");
                },
            })
       }
    });

点击收藏按钮时,向后端发送input,user信息,在mine界面加载的时候回向后端去请求这些数据。

//收藏点击添加
    $(".collect").eq(0).click(function (){
        var id = $("#lb1").val();
        var user = localStorage.getItem("username")==null ? "":localStorage.getItem("username");
        // var div = $("<div style='width: 100%;height: 20px;float: left;'></div>")
        //     .text("<a href='"+link+"'>"+title+"</a>");
        // $("#empty").append(div);
        $.ajax({
            url:"http://127.0.0.1:8080/list/like/add",
            method: "post",
            data:JSON.stringify({
                "id":id,
                "user":user
            }),
            contentType:"application/json",
            success:function (data){
                if (data.ifSucceed==true)
                alert("收藏成功");
            },
            error:function (){
                alert("页面丢失");
            }
        });

    });

登录时会向后端发送账号密码信息,在后端判断是否存在用户后得到返回信息,显示登录成功与否。

//登录
    $(".btn").eq(0).click(function (){
        var username=$("#user").val();
        var password=$("#password").val();
        //不为空向后端发送ajax
        if(username!=""&&password!=""){
            $.ajax({
                url:"http://127.0.0.1:8080/user/login/check",
                method:"post",
                data:JSON.stringify({
                    "username":username,
                    "password":password
                }),
                contentType:"application/json",
                success:function (data){
                    if(data.ifCorrect==true){
                        //登录成功
                        localStorage.setItem("username",username)
                        alert("登录成功");
                    }

                    else{
                            //登陆失败
                        alert("登录失败,请检查登录信息");
                    }
                },
                error:function (){
                    //后端未收到ajax或者没传过去
                    alert("网页丢失,请刷新");
                }
            });

        }

        else{
            alert("请输入内容");
        }
    });

注册与登录同理,向后端发送用户账号密码,后端载入成功返回成功信息。

 //注册
    $(".btn").eq(1).click(function (){
        var username=$("#user").val();
        var password=$("#password").val();
        //不为空向后端发送ajax
        if(username!=""&&password!=""){
            $.ajax({
                url:"http://127.0.0.1:8080/user/register/check",
                method:"post",
                data:JSON.stringify({
                    "username":username,
                    "password":password
                }),
                contentType:"application/json",
                success:function (data){
                    if(data.ifSucceed==true){
                        //注册成功
                        alert("注册成功");
                    }

                    else{
                        //注册失败
                        alert("注册失败,请重新注册");
                    }
                },
                error:function (){
                    //后端未收到ajax或者没传过去
                    alert("网页丢失,请刷新");
                }
            });

        }

        else{
            alert("请输入内容");
        }
    });

 
 


八、心路历程和收获

 
221801118的心路历程和收获

  对此次作业的心路历程得回溯到第一次结对作业,由于我之前项目经验不多,所以当时第一次结对作业制作原型的时候就开始了对第二次结对作业的编程实现产生了深深的恐惧和担忧。
结对作业二开始的前期,我花费了大量的时间去了解,并考虑要选择哪种开发方式以及哪种框架,前后端要不要分离开发,前后端交接又要怎么实现。后来想起,我的队友之前刚好对前端的技术有所涉猎,而我对后端也比较有兴趣,所以选择了前后端分离的开发方式。我后端在舍友大佬的推荐下选择了go语言开发。这次编程的所有东西都是陌生的,所有都要从头学,从go语言语法开始学起,压力感十足,因为压根不知道后面的编程会怎样。学完了语法,就学着用go将助教爬来的数据整理分析并存入数据库以备后续的编程使用,也算是学习新语言的入门训练。
接着是对框架的学习和选择,由于之前对这些东西了解和学习不足,导致我这第一次接触开发web的框架变得十分困难。选择了gin框架进行开发,但是在没有学习过框架的基础上两天内吸收并学会自己用框架写出一个能用的东西实在是有难度,我开发到一半后面进行不下去了,于是抛弃了写了一半的东西,从新开始,决定按照自己的理解对其进行改造,把它改成一个没那么优雅,但自己起码能理解并实现作业需求的东西。做完这些东西再回过头来,发现自己对这个框架倒是有点理解了。这就好比你不能要求一个连球都运不明白的人去下一个漂亮的快攻,而是先要让他学会运着球慢慢地、“丑陋地”跑动。
最后是前后端的交接,我之前对此一直没有准确的概念,只是模糊知道。这次到最后要和前端交接的时候,才慢慢的对这个有了概念,并经过对代码的一系列修改以适应对接,才学会了前后端的交接。
总的来说,我认为在这过程中最可怕的就是各个“选择阶段”,选择哪种方式,哪种技术,哪种语言,烧脑又烧心,这就是个折寿的过程,真正选择下去后,后续的工作至少做得比前面舒心,但这可能终归是因为自己项目经验不足导致的。虽然整个过程比较艰难,压力也大,但终归是学到了东西,也得咬着牙说值得!

 
221801136的心路历程和收获

  这次的实践作业无疑是对自学能力以及实际动手能力的一个很好的检验。从开始时懵懵懂懂,到后面逐渐有了头绪,我们也是学习了很多,走了很多
弯路。我们选择了前后端分离的方式,各自学习各自对应的任务所需要的知识。也是遇到了很多问题,只能去上网查,去问周围的同学。通过这次
实践作业我认识到了,做好前端工作远不止写一个漂亮的页面那么简单,里面大有学问。在以后的学习作业中,我会更加努力去完成,不断地完善自己。

 
 


九、评价结对队友

 
221801118评价队友221801136

  22136同学和我是老熟人、老队友了,他的学习态度认真,跟我的合作默契,交流顺畅。遇到问题的时候就相互探讨解决。刚好我们两个一个做后端一个做前端,形成良好的搭配。我提出界面显示的修改要求时,他也总是耐心和我交流并做出修改。

 
221801136评价队友221801118

  22118是一位负责的队友。一开始他也没有后端经验,也不会go语言。但是他及时去学习,去请教周围的同学。这种学习能力和执行力令我敬佩。
在我们对接交互中,有问题出现他也在耐心去找是哪里出错了。总之两个人还是有较好的默契,希望以后可以合作得更好。

 
 


posted @ 2021-03-31 22:54  wuhan18  阅读(125)  评论(5编辑  收藏  举报