這個作業屬於哪個課程 | 2021春軟件工程實踐S班 |
---|---|
這個作業要求在哪里 | 結對第二次作業 |
結對學號 | 221801314 &221801321 |
這個作業的目標 | 編程實現頂會熱詞統計、撰寫博客 |
其他參考文獻 | CSDN、簡書、博客園... |
Github倉庫地址
PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 60 | 60 |
• Estimate | • 估計這個任務需要多少時間 | 60 | 60 |
Development | 開發 | 2020 | 2350 |
• Analysis | • 需求分析 (包括學習新技術) | 720 | 900 |
• Design Spec | • 生成設計文檔 | 30 | 60 |
• Design Review | • 設計復審 | 30 | 30 |
• Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 60 | 50 |
• Design | • 具體設計 | 180 | 200 |
• Coding | • 具體編碼 | 850 | 960 |
• Code Review | • 代碼復審 | 30 | 50 |
• Test | • 測試(自我測試,修改代碼,提交修改) | 120 | 100 |
Reporting | 報告 | 120 | 160 |
• Test Repor | • 測試報告 | 60 | 80 |
• Size Measurement | • 計算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 30 | 50 |
合計 | 2200 | 2570 |
項目訪問鏈接
成品展示
主界面
主要由頁頭、輪播圖、搜索框、(論文列表)、頁腳組成。
頁頭下拉欄
輪播圖
搜索功能
在搜索框中輸入關鍵詞,可以搜索出相關文章(包含標題、作者、論文來源、發表時間、摘要以及關鍵詞),點擊論文標題還可跳轉至原文連接。
搜索結果分頁展示
頁腳上方放置頁數條,可點擊進行切換頁數查看不同論文。
高級搜索
在搜索框旁的高級搜索按鈕可展開篩選論文的發表年份或者是論文來源。
熱詞分析界面
Top10
展示熱度前十的關鍵詞,並可以點擊柱形進行相關關鍵詞搜索
-
Top10關鍵詞相關查詢
熱點圖
展示09-16年的部分關鍵詞熱度變化
結對討論過程描述
煒:
剛開始拿到題目的時候我們對於分工討論了很久,因為我們對於前后端的工作都不太熟悉,一開始曾想過兩個一起做前端,之后再做后端,但是這個方案存在許多問題,於是我們之后進行了前后端的分工。對於數據庫的設計,是由海翔進行設計后兩人一起討論,前端的界面設計由我大致根據前一次的原型設計進行改造,並且和海翔溝通界面設計,兩人共同敲定需求與功能,並分開進行前后端相關的開發。在查找資料的時候,特別是前后端聯調階段,我們經常碰見交互的錯誤碼,我們兩人一同查詢資料,並討論可能產生bug的地方,最后逐步解決bug。
海翔:
拿到題目后,我與煒哥積極溝通,確認了彼此分工,煒哥使用Vue框架寫前端,我使用Spring boot 寫后端。之后我選擇去B站、CSDN等平台尋找Spring boot相關教程,遇到問題積極溝通。及時與前端溝通,確定前端所需數據和接口。同時學習jar包部署至服務器的相關操作。
設計實現過程
前端
選擇使用vue框架,進行組件化的開發,並采用axios進行前后端交互,同時引入echarts圖表進行數據可視化操作
使用流程圖
后端
選擇使用Spring Boot框架,使用分層思想,分為controller(前后端交互)、entity(實體類)、mapper(用於寫接口和.xml數據庫配置文件)、service(實現業務邏輯層)、untils(工具類)、config(放swagger配置等)層,整合Mybatis,簡便了數據庫操作。之后根據前端所需數據,編碼每一層操作,實現相關接口。
其中abstracts、keyword、authors字端長度設為5000,以便存得下數據,id為自動遞增
代碼說明
代碼規范
前端
發送get請求查詢title
獲取 標題、當前頁數、年份、會議 等查詢條件,合並成url,向后端發送get請求獲取數據,再接收查詢到的論文列表papers。
關鍵代碼
//get請求查詢title
queryPaper() {
var page = this.page - 1;
var newUrl = "";
newUrl += "?address=";
newUrl += page;
newUrl += "&source=";
newUrl += this.meeting;
newUrl += "&title=";
newUrl += this.title;
newUrl += "&years=";
newUrl += this.year;
window.location.href = newUrl;
var myUrl = "/paper/title";
myUrl += newUrl;
console.log(myUrl);
this.ctx.$http.get(myUrl).then((data) => {
this.papers = data;
console.log(this.papers);
// window.location.reload();
});
},
發送get請求查詢圖表數據
查詢數據庫圖表數據,放進echart
關鍵代碼
const querySearch = () => {
ctx.$http.get("/word/top", {}).then((data) => { //get請求獲取數據
console.log(data);
for (let i = 0; i < data.length; i++) { //存入數據
let array = new Array();
array.push(data[i].keyword);
array.push(data[i].num);
myArray.push(array);
}
//需要獲取到element,所以是onMounted的Hook
let myChart = echarts.init(document.getElementById("top10"));
// 繪制圖表
myChart.setOption({
dataset: [
{
// dimensions: ["name", "age", "profession", "score", "date"],
dimensions: ["keyword", "num"],
source: myArray, //存入數據
},
});
myChart.on("click", function (params) {
..
});
window.onresize = function () {
//自適應大小
myChart.resize();
};
});
};
父組件向子組件傳參
將查詢到的論文,通過父組件向子組件傳參供子組件使用
使用v-bind綁定數據對象,v-for遍歷對象數組,逐個傳參到子組件中。
子組件中使用props接收父組件傳參。
關鍵代碼
//父組件
<template>
<Paper :paper="item" v-for="item in papers" /> //v-bind綁定論文列表對象
</template>
<script>
data() {
return {
papers: [],
};
},
methods: {
//get請求查詢title
queryPaper() {
....
this.ctx.$http.get(myUrl).then((data) => {
this.papers = data; //傳入查詢結果
console.log(this.papers);
// window.location.reload();
});
},
</script>
//子組件
<template>
<div>
<div>
<a :href="item.link"> //v-bind綁定屬性
{{ item.title }} //模板語法
</a>
</div>
...
<div>
<h2>摘要:</h2>
<div>
<span>{{ item.abstracts }}</span> //模板語法
</div>
<h3>關鍵詞:</h3>
<div>
<span>{{ item.keyword }}</span> //模板語法
</div>
</div>
</div>
</template>
<script>
props: ["paper"], //接收參數
data() {
return {
item: this.paper, //將父組件傳來的參數重命名,防止誤修改產生警告
};
},
</script>
子組件向父組件傳參
各個搜索子組件傳參給父組件,所有搜索條件聯合搜索。
關鍵代碼
//部分子組件
<script>
export default defineComponent({
setup(props, {emit}) {
const year = ref("");
const meeting = ref("");
function getCond() {
emit("year",year.value);
emit("meeting", meeting.value);
}
return {
year,
meeting,
getCond,
};
},
});
</script>
//父組件
<script>
export default defineComponent({
data() {
return {
title: "",
year: "",
meeting: "",
page: 1,
};
},
methods: {
//獲取輸入框組件title
getTitle(val) {
console.log(val);
this.title = val;
},
//獲取高級搜索組件條件
//年份
getYear(val) {
console.log(val);
this.year = val;
},
//會議
getMeeting(val) {
console.log(val);
this.meeting = val;
},
//獲取分頁組件當前頁面
getPage(val) {
if (val === undefined) {
val = 1;
}
console.log(val);
var title = this.getQueryString("title");
var meeting = this.getQueryString("meeting");
var year = this.getQueryString("years");
var myUrl = this.setUrl(val, title, year, meeting);
this.page = val;
window.location.href = myUrl;
},
},
});
</script>
后端
組合查詢的實現
根據文章標題/會議/年份查找論文,利用Mybatis的
關鍵代碼
<select id="selectTitle" resultMap="Paper" >
select * from papers where 1=1
<if test="title != null and title != ''">
and title like CONCAT('%', #{title}, '%')
</if>
<if test="source != null and source != ''">
and source = #{source}
</if>
<if test="years != null and years != ''">
and years = #{years}
</if>
limit #{address}, 5
</select>
接口層
int createPaper(Paper paper);
List<Paper> queryAll();
List<Paper> selectTitle(String title,String source, String years, Integer address);
List<Paper> selectSource(String source);
List<Paper> selectYears(String years);
List<Paper> selectKeyword(String keyword, Integer address);
List<Paper> selectKeywordYear(String years, String keyword);
TOP10熱詞的統計
遍歷從數據庫讀出的Paper類,用Map存儲Key和Value,然后使用Map自帶的排序函數根據Value排序,並取前10個。
關鍵代碼
wordsMap = new HashMap<>();
Map<String,Integer> result = new LinkedHashMap<>();
List<Keyword> keywordList = new LinkedList<>();
for(Paper paper : papers)
{
for (String keyword : paper.getKeyword().split(","))
{
if (keyword.length() < 3)
{
continue;
}
if (wordsMap.containsKey(keyword))
{
wordsMap.put(keyword, wordsMap.get(keyword) + 1);
}
else
{
wordsMap.put(keyword, 1);
}
}
}
wordsMap.entrySet().stream().sorted(Map.Entry.<String, Integer> comparingByValue().reversed()).limit(10)
.forEachOrdered(x->result.put(x.getKey(),x.getValue()));
根據年份查詢關鍵詞熱度
接收前端傳來的相關信息,把結果封裝為一個新的類返回給前端。
關鍵代碼
public List<YearWord> selectKeywordYear(@RequestBody WordYear wordYear)
{
String []years = wordYear.getYears();
String []keyword = wordYear.getKeyword();
List<YearWord> yearWords = new LinkedList<>();
for (String word : keyword)
{
YearWord yearWord = new YearWord();
yearWord.setKeyword(word);
int [] num = new int[years.length];
for (int i = 0; i < years.length; i++)
{
num[i] = paperService.selectKeywordYear(years[i], word).size();
}
yearWord.setNum(num);
yearWords.add(yearWord);
}
return yearWords;
}
解決前端測試接口CORS
設置過濾器。
關鍵代碼
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
}
Swagger相關配置
使用swagger進行Api測試,書寫相關配置文檔。
關鍵代碼
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("yangyu")
.enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.yangyu.esearch.controller"))
.build();
}
//配置Swagger信息
private ApiInfo apiInfo(){
Contact contact = new Contact("yangyu","https://www.cnblogs.com/yangyu-huang/",
"1849588816@qq.com");
return new ApiInfo(
"yangyu的SwaggerAPI文檔",
"即使再小的帆也能遠航",
"v1.0",
"https://www.cnblogs.com/yangyu-huang/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>()
);
}
}
心路歷程與收獲
煒:作業發布前看往屆的作業,以為只要兩人寫一個類似后端處理的程序,沒想到是要兩人共同實現前后端一個完整的系統(雖然功能較簡單)。然后我這次負責的是前端,但是也只是剛入門的階段(經過github實訓后也成長了一些),但對於一些具體的流程還不是特別清楚。但隨着和海翔的討論,確定需求,商量解決方案,原本空盪的vue框架也逐漸變得豐滿起來,界面也逐漸美觀起來。在這次過程中,我也對vue框架更加熟悉,能夠搭建一個豐富的網頁,但對於vue的生命周期,以及vue2,3之間的一些特性和改動以及axios異步請求的方面還是需要進一步學習深入。總而言之,這次的結對作業,讓我的學習能力提升了不少,也熟練了關於vue的使用,收獲頗豐。
海翔: 在看到此次結對作業要對上一次結對作業的實現時,我一開始是比較擔心自己能不能成功實現這個項目,怕拖累隊友。因為我對框架是屬於聽過,但是沒用過的狀態。於是我第一時間去B站、CSDN尋找相關的教程,在一步步的摸索中,成功的搭建了Spring Boot框架,學會了使用yml文件進行配置,學會了整合Mybatis,學會了分層編程的思想(controller層、entity層、mapper層......),學會了前后端分離編程,發現Spring Boot框架確實很好用,收獲頗多。
隊友評價
煒to海翔:海翔是一個非常認真,可靠的隊友,能夠快速地上手使用后端框架以及一些在線接口部署的方案,讓我在進行前端設計的時候能夠放心的進行相關的接口的調試,還願意積極和我交流有關接口的設計以及傳輸數據的具體設計,有時候碰到請求錯誤的時候我們也積極討論研究可能存在的問題,每次都能順利地排除錯誤,順利,穩步地推進項目,甚至有時候請求超時的時候,海翔也和我認真地分析原因,最后順利找到問題,讓我們網頁的響應速度成倍提升,我們對於這樣的改進都非常開心。非常期待之后與海翔編程的機會。
海翔to煒:煒是一個有責任心,專業能力極強、態度認真、擅長學習新知識的隊友。在商量好編程方式為前后端分離后,他負責前端,我負責后端。他熱情學習前端Vue框架,實現了一個美觀、交互性好的前端界面。並且及時與我交流溝通,分析並解決遇到的困難,及時告訴我前端所需的數據,讓我能及時根據前端需求修改Controller層的Api。結對合作使得彼此之間能夠及時地交流遇到的問題,及時地解決問題,大大提高了開發的效率。期待與煒的下一次合作。