這個作業屬於哪個課程 | 2021春軟件工程實踐|S班 (福州大學) |
---|---|
這個作業要求在哪里 | 結對第二次作業 |
結對學號 | 221801112|221801116 |
這個作業的目標 | 1.根據網頁原型,搭建web網站 2.學習Github協作編程 3.學習網站搭建的前后端技術 4.將網站部署到雲服務器 |
其他參考文獻 | SpringBoot2入門 centos服務器使用教學 bootstrap官方文檔 |
一、git倉庫鏈接和代碼規范鏈接
1.1 github倉庫鏈接
1.2 代碼規范鏈接
1.3 項目鏈接
二、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 45 | 30 |
• Estimate | • 估計這個任務需要多少時間 | 45 | 30 |
Development | 開發 | 4335 | 4670 |
• Analysis | • 需求分析 (包括學習新技術) | 120 | 240 |
• Design Spec | • 生成設計文檔 | 50 | 40 |
• Design Review | • 設計復審 | 20 | 30 |
• Coding Standard | • 代碼規范 (為目前的開發制定合適的規范) | 15 | 15 |
• Design | • 具體設計 | 40 | 50 |
• Coding | • 具體編碼 | 4000 | 4200 |
• Code Review | • 代碼復審 | 30 | 35 |
• Test | • 測試(自我測試,修改代碼,提交修改) | 60 | 60 |
Reporting | 報告 | 90 | 110 |
• Test Repor | • 測試報告 | 30 | 30 |
• Size Measurement | • 計算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后總結, 並提出過程改進計划 | 30 | 50 |
合計 | 4470 | 4810 |
三、成品展示
3.1 登錄注冊
3.2 首頁搜索跳轉
3.3 論文列表跳轉
3.4 詞雲分析
3.5 點擊論文出現詳情頁(含有原文鏈接)
3.6 論文列表搜索
3.7 論文增加
3.8 論文刪除和修改
3.9 熱詞熱度分析走勢圖
3.10 論文爬取
四、結對討論過程描述
4.1 結對討論過程
因為我們兩個人的技術棧比較相似,本身都學習過一些后端開發的知識,所以在拿到選題的時候,我們就決定各自分別負責展開前端與后端的工作,以功能模塊划分我們的開發。結合Springboot框架和前端三套件,我們可以很快搭建出一個網站的模板。總的來說,我們的結對編程進度推進的還算順利,也沒有git提交代碼沖突,因為我們是舍友,遇到問題也能馬上討論解決方案。
4.2 結對討論截圖
五、設計實現過程描述
5.1 功能結構圖
5.2 功能模塊介紹
5.2.1 首頁導航
首頁點擊右上角登錄可以進入登錄/注冊界面,用戶登錄后用SESSSION來保存用戶信息。首頁搜索框搜索會導向論文列表頁面並且展示搜索結果。點擊熱詞分析框會彈出熱詞熱度走勢對比圖。
5.2.2 論文列表
論文列表以一個個卡片表格的形式展示已經爬取到的論文,並且在后端寫好了分頁,點擊分頁器頁碼按鈕會再次向后端發起請求,渲染表格展示新的數據。點擊論文卡片會以模態框的形式彈出論文詳情頁,可以在論文詳情頁對論文進行刪除,修改操作。論文表格上方有論文搜索框和論文增加按鈕,可以用關鍵詞或論文標題進行模糊搜索。增加論文后新論文在最后一頁最后一條。
5.2.3 熱詞分析
熱度對比圖,我們先是采用取得數據庫所有關鍵詞,按頂會不同和年份不同區分,再將他們排序輸出,最后計算與傳輸所耗費的時間過長,該方案被我們淘汰,最后我們選擇將分析所得的數據通過json的形式直接存入數據庫,每次使用直接取出即可,如果有新數據加入,會再次計算關鍵詞熱度。
熱詞分析部分首先通過動態柱狀圖,統計每個頂會不同年間,關鍵詞熱度變化,可切換不同頂會,查看其動態變化。
5.2.4 詞雲分析
在數據庫中取得所有關鍵詞列表,將他們排序分析,並將出現頻率最高的二十個詞展示在詞雲圖中,用戶可以通過點擊詞雲圖跳轉到帶有相應關鍵詞的論文列表。
5.2.5 在線爬取
通過用戶需要查找的關鍵詞,在線通過ieee搜索出相應的論文,然后修改請求頭截取ajax請求的方式獲得json文件,再利用程序提取json中的數據並進行分析處理,顯示到網站。
六、關鍵代碼和思路
6.1 在線爬取
通過HttpClient(設置請求頭,請求參數),獲取ieee網站響應數據,並對其處理分析。
public static String getJson(String path,String params)
{
try
{
trustAllHttpsCertificates();
} catch (Exception e)
{
e.printStackTrace();
}
HttpsURLConnection.setDefaultHostnameVerifier(hv);
HttpURLConnection httpURLConnection = null;
String data = "";
try {
httpURLConnection=show(path);
httpURLConnection.connect();
String p = params;
OutputStream out = httpURLConnection.getOutputStream();
out.write(p.getBytes());
out.flush();
//關閉
out.close();
int code = httpURLConnection.getResponseCode();
if (code == 200) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(httpURLConnection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
data += line + "\n";
}
//關閉
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 斷開連接
if (null != httpURLConnection) {
try {
httpURLConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return data;
}
public static HttpURLConnection show(String path) throws IOException
{
HttpURLConnection httpURLConnection = null;
URL url = new URL(path);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setConnectTimeout(6000);
httpURLConnection.setDoOutput(true);
httpURLConnection.setDoInput(true);
httpURLConnection.setUseCaches(false);
httpURLConnection.setInstanceFollowRedirects(true);
httpURLConnection.setRequestProperty("Content-Type", "application/json");
httpURLConnection.setRequestProperty("Accept", "application/json,text/plain,*/*");
httpURLConnection.setRequestProperty("Accept-Language", "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7");
httpURLConnection.setRequestProperty("Connection", "keep-alive");
httpURLConnection.setRequestProperty("Content-Length", "122");
httpURLConnection.setRequestProperty("Accept-Encoding", "gzip,deflate,br");
return httpURLConnection;
}
6.2 數據分析(熱詞分析)
通過將關鍵詞和年份拼接作為map的key ,數量作為value,對不同年份關鍵詞的數量進行統計,並按數量和年份進行排序得到最終數據並將數據存入數據庫,減少計算量。
public List<Map.Entry<String, Integer>> alalysePaperToGetTopKeyWords(Paper paper) {
List<Paper> paperList = userMapper.selPaperByConference(paper);
Map<String, Integer> keyMap = new HashMap<>();
for (int i = 0; paperList != null && i < paperList.size(); i++) {
System.out.println("獲取paper" + paperList.get(i).getPublicationYear() + "條數:" + i);
if (paperList.get(i).getKeywords() == null) {
continue;
}
String[] arr = paperList.get(i).getKeywords().replaceAll("\"", "").split(",");
if (paperList.get(i).getPublicationYear() == null)
paperList.get(i).setPublicationYear("0");
for (int j = 0; j < arr.length; j++)
arr[j] = arr[j] + "&&&&&" + paperList.get(i).getPublicationYear().replaceAll("\"", "");
for (int j = 0; arr != null && j < arr.length; j++) {
if (keyMap.get(arr[j]) == null) {
keyMap.put(arr[j], 1);
} else
keyMap.put(arr[j], keyMap.get(arr[j]) + 1);
}
}
List<Map.Entry<String, Integer>> list = sortMapByValue(keyMap);
List<Map.Entry<String, Integer>> answer = new ArrayList<>();
Map<String, Integer> tmp_count = new HashMap<>();
for (int i = 0, j = 0, k; i < list.size(); i++) {
String[] years = list.get(i).getKey().split("&&&&&");
if (years != null && tmp_count.get(years[1]) == null)
tmp_count.put(years[1], 0);
else if (years != null && tmp_count.get(years[1]) < TOP_TEN * 2) {
tmp_count.put(years[1], tmp_count.get(years[1]) + 1);
answer.add(list.get(i));
}
}
return answer;
}
private List<Map.Entry<String, Integer>> sortMapByValue(Map<String, Integer> map) {
//將hashMap轉化為list
List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());
//進行排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
if (o2.getValue() == null || o1.getValue() == null)
return 0;
String[] str1 = o1.getKey().split("&&&&&");
String[] str2 = o2.getKey().split("&&&&&");
if (!str1[1].equals(str2[1]))
return str1[1].compareTo(str2[1]);
return o2.getValue().compareTo(o1.getValue());
}
});
return list;
}
6.3 對已有的json文件(論文數據)分析處理存入數據庫:
通過本次作業助教提供的數據初始化數據庫,通過文件遞歸獲取字符流。
public List<Paper> getPaper() {
FileReader reader = null;
List<String> list = new ArrayList<>();
StringBuilder sb;
for (int i = 0; i < 3; i++) {
List<File> fileList = FileUtil.getFiles(path[i]);
for (File f : fileList) {
String tmp = null;
sb = new StringBuilder(INI_SIZE);
//讀取json文件
try {
reader = new FileReader(path[i] + "\\" + f.getName());
BufferedReader bufferedReader = new BufferedReader(reader);
while ((tmp = bufferedReader.readLine()) != null) {
sb.append(tmp);
}
list.add(sb.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Paper paper = onepaperJsonToPaper(sb.toString(), conference[i]);
//保存至數據庫
if (paper != null)
userMapper.insPaper(paper);
}
}
對獲取的流進行分析,得到相應對象 ,調用數據庫語句存入數據庫。
private Paper onepaperJsonToPaper(String str, String conference) {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = null;
try {
node = objectMapper.readTree(str);
} catch (IOException e) {
e.printStackTrace();
}
logger.debug(String.valueOf(node.get("abstract")));
Paper paper = new Paper();
paper.setConference(conference);
paper.setAbstrac(String.valueOf(node.get("abstract")));
paper.setPersistentLink(String.valueOf(node.get("htmlLink")));
paper.setPublicationTitle(String.valueOf(node.get("title")));
paper.setPublicationYear(String.valueOf(node.get("publicationYear")));
List<PaperAuthors> authorslist = paper.getAuthorsList();
List<Keywords> keywordsList = paper.getKeywordsList();
//關鍵詞對象。
for (int j = 0; node.get("keywords") != null && j < node.get("keywords").size(); j++) {
//System.out.println("獲得的關鍵詞:" + String.valueOf(node.get("keywords").get(j)));
Keywords keywords = new Keywords();
keywords.setType(String.valueOf(node.get("keywords").get(j).get("type")));
List<String> stringList = new ArrayList<>();
for (int k = 0; k < node.get("keywords").get(j).get("kwd").size(); k++) {
stringList.add(String.valueOf(node.get("keywords").get(j).get("kwd").get(k)));
//利用,分割,以string形式保存
if (paper.getKeywords() == null)
paper.setKeywords(String.valueOf(node.get("keywords").get(j).get("kwd").get(k)));
else
paper.setKeywords(paper.getKeywords() + "," + node.get("keywords").get(j).get("kwd").get(k).toString());
}
keywords.setKedList(stringList);
keywordsList.add(keywords);
}
paper.setKeywordsList(keywordsList);
//authorslist.add(new PaperAuthors(node.get("keywords")))
return paper;
}
6.4 論文列表增刪改查
利用mybatis框架對數據庫論文進行增刪查改
@Mapper
public interface LimitPaperMapper {
@Select("SELECT * FROM paper limit #{startPosition} ,#{pageSize}")
public List<Paper> getLimitPaper(@Param("startPosition") int startPosition, @Param("pageSize") int pageSize);
@Select("SELECT COUNT(*) FROM paper")
public Integer getCount();
@Select("SELECT * FROM paper WHERE persistentLink != 'null' and (keywords like concat('%', #{value}, '%')" +
"or publicationTitle like concat('%', #{value}, '%')) limit #{startPosition} ,#{pageSize}")
public List<Paper> searchByKeyWords(@Param("value") String keyword,@Param("startPosition") int startPosition, @Param("pageSize") int pageSize);
@Select("SELECT COUNT(*) FROM paper WHERE persistentLink != 'null' and (keywords like concat('%', #{value}, '%') " +
"or publicationTitle like concat('%', #{value}, '%'))")
public Integer getCuntS(@Param("value") String keyword);
@Delete("DELETE FROM paper WHERE paperId = #{id}")
public Integer deletePaper(@Param("id") int paperId);
@Update("UPDATE paper SET keywords = #{keywords},abstrac = #{abstrac},publicationTitle = #{publicationTitle}," +
"publicationYear = #{publicationYear}, persistentLink = #{persistentLink} where paperId = #{paperId}")
public Integer updatePaper(@Param("paperId") int paperId,@Param("keywords") String keywords,
@Param("abstrac") String abstrac,@Param("publicationTitle") String publicationTitle,
@Param("publicationYear") String publicationYear,@Param("persistentLink") String persistentLink);
@Insert("INSERT INTO paper (keywords,publicationTitle,abstrac,publicationYear,persistentLink) " +
"values (#{keywords},#{publicationTitle},#{abstrac},#{publicationYear},#{persistentLink})")
public Integer insertPaper(@Param("keywords") String keywords,@Param("abstrac") String abstrac,
@Param("publicationTitle") String publicationTitle,
@Param("publicationYear") String publicationYear,
@Param("persistentLink") String persistentLink);
}
6.5 后端論文數據接口
前端采用layui表格ajax發出請求,后端接收請求后返回數據
@Controller
public class LimitPaperController {
@Autowired
LimitPaperImpl limitPaperService;
@RequestMapping("/getLimitPaper")
@ResponseBody
public PaperResponsBody getLimitPaper(@RequestParam(defaultValue = "1") String start, @RequestParam(defaultValue = "8") String limit)
{
List<Paper> paperList = limitPaperService.getLimitPaper(Integer.parseInt(start),Integer.parseInt(limit));
for(Paper paper:paperList) {
if(paper.getPersistentLink().contains("https:")){
}else {
paper.setPersistentLink("https://ieeexplore.ieee.org"+paper.getPersistentLink());
}
}
PaperResponsBody paperResponsBody=new PaperResponsBody();
paperResponsBody.setCode("0");
paperResponsBody.setMsg("成功");
Integer count = limitPaperService.getCount();
paperResponsBody.setCount(count);
paperResponsBody.setData(paperList);
return paperResponsBody;
}
@RequestMapping("/searchByKeyWord")
@ResponseBody
public PaperResponsBody getLimitPaper(@RequestParam(defaultValue = "") String keywords,@RequestParam(defaultValue = "1") String start, @RequestParam(defaultValue = "8") String limit)
{
List<Paper> paperList = limitPaperService.searchByKeyWords(keywords,Integer.parseInt(start),Integer.parseInt(limit));
for(Paper paper:paperList) {
if(paper.getPersistentLink().contains("https:")){
}else {
paper.setPersistentLink("https://ieeexplore.ieee.org"+paper.getPersistentLink());
}
}
PaperResponsBody paperResponsBody=new PaperResponsBody();
paperResponsBody.setCode("0");
paperResponsBody.setMsg("成功");
Integer count = limitPaperService.getCountS(keywords);
paperResponsBody.setCount(count);
paperResponsBody.setData(paperList);
return paperResponsBody;
}
@PostMapping("/delete")
public String postDelete(HttpServletRequest request) {
String paperId = request.getParameter("paperId");
Integer result = limitPaperService.deletePaper(Integer.parseInt(paperId));
if(result == 1){
System.out.println("刪除成功");
}else{
System.out.println("刪除失敗");
}
return "paperlist";
}
@PostMapping("/update")
public String postUpdate(HttpServletRequest request) {
String paperId = request.getParameter("paperId");
String title = request.getParameter("textarea-title");
String year = request.getParameter("textarea-year");
String link = request.getParameter("textarea-link");
String abstrac = request.getParameter("textarea-abstract");
String key = request.getParameter("textarea-key");
Integer result = limitPaperService.updatePaper(Integer.parseInt(paperId),title,key,abstrac,link,year);
if(result == 1){
System.out.println("更新成功");
}else{
System.out.println("更新失敗");
}
return "paperlist";
}
@PostMapping("/insert")
public String postInsert(HttpServletRequest request) {
String title = request.getParameter("textarea-title");
String year = request.getParameter("textarea-year");
String link = request.getParameter("textarea-link");
String abstrac = request.getParameter("textarea-abstract");
String key = request.getParameter("textarea-key");
Integer result = limitPaperService.insertPaper(title,key,abstrac,link,year);
if(result == 1){
System.out.println("插入成功");
}else{
System.out.println("插入失敗");
}
return "paperlist";
}
}
七、心路歷程和收獲
7.1 心路歷程
221801112:
萬事開頭難,在本次完成作業的過程中,我們一開始決定使用已有的json數據來初始化數據庫,打開助教爬取的一條json數據,里面包含了成百上千的數據,如何找到我們需要的數據?感覺無從下手,其次關鍵詞等多個數據應該如何存入數據庫?但是在和隊友持續討論和資料查詢過程中,我們完成了數據庫的初始化。數據完成了,新的問題又出現了,前端如何展示?后端如何分析數據?由於我們都對后端感興趣,所以我們選擇按功能分配任務,獨立完成開發。
221801116:
在項目開發中,我負責將論文數據展示與排版,剛開始時我選擇layui原生表格來展示數據,但是在取得一部分表格后我發現論文的關鍵詞和摘要等部分的內容很龐大,使用原生表格給人的觀感很不好,於是只能舍棄這個方案重新找尋其他方法,最后在layui獨立模塊的啟發下,寫了一個新的卡片式表格,利用layui封裝好的數據請求,獲取論文數據。成功的將論文以卡片的形式展示出來。
7.2 收獲
221801112:
技術:學習並實踐mybatis +springboot+thymeleaf。 其他:在與隊友不斷交流的過程,提升了自己表達的能力。
221801116:
技術:學習並實踐mybatis +springboot+thymeleaf,layui模塊式前端開發,服務器項目部署。
八、評價結對隊友
221801112:
csk同學在這次作業中表現了強大的學習能力,和實踐能力。是我們項目的有力推動者,每當我編程出現問題,sk同學都會竭盡全力,幫助是解決問題。
221801116:
cx同學的編碼能力很強,在我還在考慮前端頁面展示的大概樣式時,他就已經開始對助教的數據進行分析處理,並且很快將處理好的數據投入使用。項目編程期間,cx同學很能吃苦,每天晚上都奮戰到很晚。