軟工實踐結對作業二
這個作業屬於哪個課程 | 2021春軟件工程實踐 W班 (福州大學) |
---|---|
這個作業要求在哪里 | 軟工實踐結對作業二 |
這個作業的目標 | 詳細閱讀作業要求、采用web技術實現原型功能、撰寫博客 |
其他參考文獻 | 鄒欣老師的博客園講義 |
git倉庫鏈接和代碼規范鏈接
部署后的項目地址
項目接口文檔
https://www.showdoc.com.cn/1294949416702524?page_id=6549970937759390
PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 30 | 30 |
• Estimate | • 估計這個任務需要多少時間 | 30 | 30 |
Development | 開發 | 2520 | 2800 |
• Analysis | • 需求分析 | 240 | 240 |
• Discuss | • 結對討論 | 120 | 120 |
• Study | • 學習對應技術 | 900 | 1200 |
• Design Spec | • 生成設計文檔 | 120 | 180 |
• Design Review | • 設計復審 | 60 | 60 |
• Design | • 界面設計 | 180 | 240 |
• encode | • 具體編碼實現 | 900 | 1260 |
Reporting | 報告 | 210 | 210 |
• Report | • 寫總結 | 120 | 120 |
• Size Measurement | • 計算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 60 | 60 |
合計 | 2760 | 3040 |
成品展示
主頁搜索:
描述:在主頁輸入搜索詞搜索(返回按關鍵詞和標題)
原文鏈接:
描述:在搜索結果點擊查閱原文可跳轉到官網的原文地址
下載論文:
描述:擴展了下載功能,將以爬取的部分論文的pdf傳到服務器,以破石頭請求獲取文件下載流實現下載
實現收藏夾:
描述:利用cookie保存自己想保存的關鍵字,點擊可實現跳轉搜索
實現庫搜索:
分析界面:
描述:分析各類的論文近幾年的熱詞,並提供所有熱詞的頻度分析和快捷搜索
結對討論過程描述
·第一次看到題目的時候,我們先約在咖啡廳里面討論,首先先對項目進行了需求分析,分析並確定了要使用的語言和框架以及還需掌握的知識,而且一起制定了共同的代碼規范。
·再需求分析后,后端代碼實現的過程中,一起討論了前端可能需要的api返回數據和格式
·在線上一般會用qq聊天,方便的時候會使用qq電話📞和屏幕共享💻
·在遇到問題時,我們會同享代碼,一起分析問題所在,同時,我們會對彼此的代碼和所實現的功能進行復審,以減少出現程序故障的情況。
設計實現過程(前后端分離)
前端:
- 使用了bootstrap開發框架。
- 通過AJAX來獲取json數據
- 通過js來進行主要功能的實現
- 設置了主頁、庫頁、分析頁、收藏頁、結果頁,實現了搜索、顯示刪除、可視化分析、增加刪除收藏,顯示搜索結果等功能
后端:
- 使用spring boot + Mybatis框架。
- 封裝了實體類Paper,和keywordanalysis來對數據進行傳遞。
- 在dao層中,設計了各種PaperMapper,keyWordMapper接口,定義了訪問數據庫的方法,主要用於實現數據持久化。
- 在service層中,通過調用dao層的方法,定義了服務keywordService和paperService,實現了業務模塊的應用邏輯。
- 在controller中,通過調用service層,定義了控制類keywordController和paperController,來實現具體業務模塊流程的控制
。
功能模塊圖:
代碼說明
實現動態添加圖表JS代碼
function addall_canvas() {
var div1 = document.createElement("div");
div1.style.height = "720px";
div1.style.width = "540px";
div1.style.float = "left";
div1.innerHTML = "<canvas id= \"allChart1\"></canvas><canvas id= \"allChart2\"></canvas>";
document.getElementById("allyearall").appendChild(div1);
}
實現收藏的增加、刪除的主要JS代碼 (初始化與其類似)
addbtn.onclick = function add_div() {
var love = prompt("請輸入收藏關鍵字:","");
if(love != null){
var div = document.createElement("div");
div.className = "form-group";
div.id = "ltags" + detail_div;
if(love != null){
var index = detail_div;
div.innerHTML = '<button type="button" class="btn btn-primary btn-lg btn3d" onclick="totags(this)"><span class="glyphicon glyphicon-tag">'+love+"</button><button type=\"button\" class=\"close\" aria-label=\"Close\" onclick='deleteTag(this,"+index+")'><span aria-hidden=\"true\">×</span></button>";
}
document.cookie="lovetag"+detail_div+"="+love+";path=/";
document.getElementById("lovetag").appendChild(div);
detail_div++;
}
}
function deleteTag (obj,index){
detail_div--;
var dallcookies = document.cookie;
// Get all the cookies pairs in an array
var dcookiearray = dallcookies.split(';');
for(var i = index; i<detail_div; i++){
var dname = dcookiearray[i].split('=')[0];
var dvalue = dcookiearray[i+1].split('=')[1];
document.cookie = dname+"="+dvalue+";path=/";
}
document.cookie = "lovetag"+detail_div+"=;path=/";
obj.parentNode.parentNode.removeChild(obj.parentNode);
}
前端獲取數據代碼
{
if(window.XMLHttpRequest){
xmlhttp1 = new XMLHttpRequest();
}else{
xmlhttp1 = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlhttp1.onreadystatechange = function() {
if(xmlhttp1.readyState == 4){
if(xmlhttp1.status == 200) {
var str = xmlhttp1.responseText;
var arr = eval('('+str+')');
jsonlength = arr.length;
for(var i = 0; i < jsonlength; i++){
var temp = i%10 ;
if( arr[i].type == "CVPR"){
if( temp == 0){
CVPRyear[chartnum1] = arr[i].publishyear;
add_canvas("CVPR");
}
var publishyear = arr[i].publishyear;
CVPRKeyword[publishyear][temp] = arr[i].keyword;
CVPRfrequency[publishyear][temp] = arr[i].frequency;
}
}
}
}
}
xmlhttp1.open("POST",url,true);
xmlhttp1.setRequestHeader("Access-Control-Allow-Origin","*");
xmlhttp1.send("{\"type\":\"CVPR\"}");
}
后端主要代碼(以實體類paper為例,代碼為美觀易於理解做了緊湊處理)
paper實體類:
public class paper {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof paper)) return false;
paper paper = (paper) o;
return Objects.equals(id, paper.id);
}
private Integer id;
private String typeandyear;
private String releasetime;
private String link;
private String title;
private String abstractcontext;
private String keyword;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getTypeandyear() {returntypeandyear;}
public void setTypeandyear(String typeandyear) {
this.typeandyear = typeandyear == null ? null : typeandyear.trim();
}
public String getReleasetime() {
return releasetime;
}
public void setReleasetime(String releasetime) {
this.releasetime = releasetime == null ? null : releasetime.trim();
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link == null ? null : link.trim();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title == null ? null : title.trim();
}
public String getAbstractcontext() {
return abstractcontext;
}
public void setAbstractcontext(String abstractcontext) {
this.abstractcontext = abstractcontext == null ? null : abstractcontext.trim();
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword == null ? null : keyword.trim();
}
@Override
public String toString() {
return "paper{" +
"id=" + id +
", typeandyear='" + typeandyear + '\'' +
", releasetime='" + releasetime + '\'' +
", link='" + link + '\'' +
", title='" + title + '\'' +
", abstractcontext='" + abstractcontext + '\'' +
", keyword='" + keyword + '\'' +
'}';
}
}
PaperMapper:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.geiyepa.demo.mapper.paperMapper">
<resultMap id="BaseResultMap" type="com.geiyepa.demo.entity.paper">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="typeandyear" jdbcType="VARCHAR" property="typeandyear" />
<result column="releasetime" jdbcType="VARCHAR" property="releasetime" />
<result column="link" jdbcType="VARCHAR" property="link" />
<result column="title" jdbcType="LONGVARCHAR" property="title" />
<result column="abstractContext" jdbcType="LONGVARCHAR" property="abstractcontext" />
<result column="keyword" jdbcType="LONGVARCHAR" property="keyword" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.geiyepa.demo.entity.paperWithBLOBs">
<result column="title" jdbcType="LONGVARCHAR" property="title" />
<result column="abstractContext" jdbcType="LONGVARCHAR" property="abstractcontext" />
<result column="keyword" jdbcType="LONGVARCHAR" property="keyword" />
</resultMap>
<sql id="Base_Column_List">
id, typeandyear, releasetime, link
</sql>
<sql id="Blob_Column_List">
title, abstractContext, keyword
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from paper
where id = #{id,jdbcType=INTEGER}
</select>
<select id="getPaper" parameterType="java.lang.Integer" resultType="com.geiyepa.demo.entity.paper">
SELECT
<include refid="Base_Column_List" />
FROM paper WHERE id = #{id}
</select>
.......
Service:
@Service
@Component
public class paperService implements paperMapper{
@Autowired
private paperMapper paperMapper;
@Override
public int deleteByPrimaryKey(Integer id) {
return 0;
}
@Override
public List<paper> selectLikeKeyword(String keyword) {
return paperMapper.selectLikeKeyword(keyword);
}
...
}
Controller 的搜索接口:
@ResponseBody
@RequestMapping(value = "/searchPaper" , method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public JSONArray search(@RequestBody String JSONBody){
JSONObject object = JSONObject.parseObject(JSONBody);
String searchKeyword = (String) object.get("searchKeyword");
List<paper> paperList1 = paperService.selectLikeKeyword("%"+ searchKeyword +"%");
List<paper> paperList2 =paperService.selectLikeWord("%"+ searchKeyword +"%");
for (paper p2:paperList2
) {
int flag = 0;
for (paper p1:paperList1
) {
flag = 0;
if(p1.equals(p2)) flag = 1;
}
if(flag ==0 ) paperList1.add(p2);
}
JSONArray array= JSONArray.parseArray(JSON.toJSONString(paperList1));
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
Date date = new Date(System.currentTimeMillis());
System.out.println(formatter.format(date) + " ====> 搜索文章 ##搜索關鍵詞:" + searchKeyword + " ##搜索結果數:" + paperList1.size());
return array;
}
代碼描述與實現思路:
cmy主要負責后端代碼的編寫和服務器的架設,項目部署。如上,后端使用Springboot + Mybatis框架。以paper為例,首先使用Mybatis的generator生成Bean,Dao,Mapper的代碼,在根據實際需要稍作修改,創建PaperService類注入paperMapper,因為本次項目的邏輯簡單直接調用dao的接口就行,接着編寫controller 注入paperService,格式化數據實現接口功能。
cq主要負責前端的頁面顯示和圖表制作,使用bootstrap開發框架+原生js,保證美觀的同時加快開發效率。按照接口文檔的描述發起破石頭請求,將獲得的String類型的響應體格式化為JSON,利用DOM和bootstrap的各類class的模板動態添加div到頁面。
心路歷程和收獲
陳起:
在此次的結對編程中,因為我的項目經驗比較少,實現的過程中有的概念會比較模糊,在此之前,對於一些框架也不了解,所以在項目前期,我比較沒有頭緒,大部分時間都在進行學習新的技術的過程中,但是后面投入實踐中,我的隊友總是會很耐心的跟我解釋,讓我加深理解了什么是框架,也學會不少新的知識,提高了自身能力。因為是兩個人結對合作,所以也鍛煉了書寫規范的代碼。這次結對合作,我們在磨合的過程中鍛煉並成長,在合作的過程中學習並進步,對我們而言,是一次很有幫助的實踐。
陳明煜:
在此次的結對編程中,雖然我之前有過類似的項目經驗,但是在這次的實踐過程中還是有我之前沒有接觸過或者是不夠熟練的技術,於是在項目前期,我的大部分時間也都在進行學習新的技術,所以我會主動去承擔比較復雜的部分,同時在需要的時候主動去給我的隊友一些幫助;這一次的實踐,使我對springboot這一框架的使用熟練度大大提升,同時學會了Mybatis使用generator自動生成代碼,理解的逆向工程的思路,提高了自身能力,也學到了新的知識,加深了的不只是友誼,更是對知識的掌握,不得不說,這次結對編程是一次很有幫助的實踐。
評價結對隊友
陳起對陳明煜的評價:
陳明煜是一個不錯的隊友,不管是前端還是后端,都是比較熟練的項目經驗比較多的,而我的項目經驗比較少,結對過程中有的概念會不了解,這時他總是會很耐心的跟我解釋,使我們的工作完美完成。工作認真負責,細心,學習能力很強,接受新事物快,理解能力強,完成工作任務,然后也很感謝耐心回答我的問題,有機會的話希望能配合的像和這次一樣的好。
陳明煜對陳起的評價:
陳起是一位很優秀的隊友,雖然他之前的項目經驗可能比較少,但是他很謙虛,會比較積極主動學習新知識,項目中有什么不會的地方,也會立即直接地提出來一起討論解決,同時遇到新事物也會有自己的思考,有時會想出很好的點子,在編程過程中遇到問題,我們都能一起配合解決,與他合作是一次非常愉快的合作體驗。