| 這個作業屬於哪個課程 | 軟件工程2021春軟件工程W班(FZU) |
|---|---|
| 結對學號 | 221801424 221801435 |
| 作業要求 | 作業鏈接 |
| 作業目標 | 論文查詢網站,完成基礎功能,並實現一到兩個附加功能 |
| 參考文獻 | Flask官方文檔 Angular官方文檔 |
作業描述
基礎功能
功能1:對已爬取的論文列表進行操作
功能2:分析已爬取到的論文信息,提取top10個熱門領域或熱門研究方向等統計分析效果展示
項目部署到雲服務器上
附加功能
功能3:獲取待爬取論文列表及論文信息爬取【附加分:15%】
擴展功能:擴展基礎功能以外的功能【附加分:0%~15%】
代碼規范鏈接
PSP表格
| PSP2.1 | Personal Software Process Stages | 預估耗時(h) | 實際耗時(h) |
|---|---|---|---|
| Planning | 計划 | 2 | 2.5 |
| Estimate | 估計這個任務需要多少時間 | 0.5 | 0.4 |
| Development | 開發 | 4*24 | 4*24+12 |
| Analysis | 需求分析 (包括學習新技術) | 2*24 | 2*24 |
| Design Spec | 生成設計文檔 | 2 | 2.5 |
| Design Review | 設計復審 | 2 | 1.5 |
| Coding Standard | 代碼規范 (為目前的開發制定合適的規范) | 1 | 1.2 |
| Design | 具體設計 | 1 | 1.5 |
| Coding | 具體編碼 | 2*24 | 2*24+12 |
| Code Review | 代碼復審 | 2 | 2.5 |
| Test | 測試(自我測試,修改代碼,提交修改) | 1 | 1.5 |
| Reporting | 報告 | ||
| Test Repor | 測試報告 | 1 | 1 |
| Size Measurement | 計算工作量 | 1 | 1.5 |
| Postmortem & Process Improvement Plan | 事后總結, 並提出過程改進計划 | 1.5 | 2 |
| 合計 | 206 | 234.1 |
成品展示
功能一:登錄注冊


因為本網站自帶論文保存與收藏功能,用戶可以根據自己的需要在用戶列表收藏論文,並對列表進行刪除和筆記等操作,而這些功能需要在用戶登錄的情況下才可以使用,因此,本gif專門展示一下登錄注冊和修改個人信息的一整個流程。

功能二:通過搜索框搜索論文,並添加到收藏列表



用戶可以通過論文列表模塊的搜索框模糊搜索論文的作者、標題、關鍵字,網站將根據搜索框輸入內容查詢論文並顯示在列表中,用戶可以選擇自己感興趣的論文並將其添加到“收藏列表”(當然前提是先登錄成功),需要時還可以刪除收藏列表里的論文。

功能三:趨勢分析





用戶可以通過“趨勢分析->走勢圖”來查看近幾年計算機視覺領域研究方向的熱度走勢圖,其中:
折線圖展示近幾年三大頂會cvpr、iccv和eccv的投稿論文數量,為用戶選擇會議提供一些數據上的支持;
柱狀圖展示了三年里最熱門的三個領域在三大頂會的論文投稿數量;
餅狀圖展示了本年度計算機視覺領域各個方向論文投稿數量的相對占比;
旭日圖詳細列出了本年度三大頂會論文投稿的熱門方向,並給出了每個方向的投稿論文數量。

功能四:論文筆記


用戶在“論文收藏”列表點擊論文的“note”按鈕之后,網站右邊會彈出一個抽屜,用戶可以在其中記錄自己對於這篇論文的看法,例如這篇文章用了XX的方法,取得了XX的效果。

功能五:
用戶可以通過直方圖直觀的看到本年最熱的研究方向。

結對討論過程描述
結對過程綜述
拿到題目之后,我們首先做了功能的分析,確定了功能的模塊可以大致分為論文搜索(爬取信息)、論文管理(論文列表以及文件導入)、趨勢分析(包括走勢圖和熱詞統計)、用戶信息和用戶收藏,之后按照結對作業一的原型設計規划了大致的UI界面。因為cold之前有學過flask后端以及部署,所以后端框架確定使用Flask來進行開發,前端我們考慮了許久,為了能夠加快開發速度,降低代碼耦合,我們選擇了具有組件化開發模式的Angular作為前端開發框架。因其組件化的特點,我們可以每人完成不同的組件,最后只需要在一個公共的組件上進行整合即可。之后,我們大約花費了3、4天的時間學習新的框架,剩余的時間用於編碼和博客寫作,期間我們互相參考了對方的代碼,互相改bug,使得項目的推進變得順利起來。
討論過程截圖
在項目開始時,我們討論要如何處理助教給的論文數據,並和相應的數據庫表對應,我們首先在網頁上用jsonview工具展示json數據,分析數據格式,最后再編寫python處理json文件,並將其結果導入到數據庫當中。
在項目初期,討論github如何協作使用,並一起學習了github的如何進行fetch,fork,和設置upstream,其中cold找到了一篇十分具有參考價值的博客。
討論功能塊要如何划分,我們首先參考的是第一次結對作業,結合了助教給我們的意見之后,我們決定將首頁的旭日圖移動到趨勢展示頁面,做到一個頁面只完成一個功能。我們也針對原型所不能表現出來的功能要如何實現做了細致的討論,例如,用戶筆記要如何實現,是用抽屜還是彈出一個輸入框?最后我們還選擇了ant design作為我們的前端UI輔助開發工具。
討論前后端的接口如何統一,為了實現前后端分離,我們需要確定一套統一的接口api,用於在前端和后端傳遞數據。
設計實現過程
功能結構圖

數據庫設計圖

User對應用戶表,User_article對應用戶收藏的文章表,與User表為1對多關系;Note表對應用戶收藏文章的筆記,與User表為1對多關系,User_article 為1對1關系;Meeting_article為論文數據集所對應的表。
設計過程
主頁
進入主頁時我們為用戶提供一個簡潔的搜索輸入框,輸入框的旁邊設置了下拉菜單用來選擇是按標題、關鍵詞、還是作者來搜索,其中下拉菜單的值可以用angular的ngModel動態綁定,在用戶輸入查詢字符串並點擊Search按鈕之后,前端用angular自帶的HttpClient向后端發送請求,后端利用mysql的%符號來進行模糊搜索,最后返回給前端符合查詢條件的文章列表。考慮到符合條件的論文列表較多,前端最后用分頁來展示所有數據(前端在請求后端數據時,還需要傳入頁的號碼和頁的大小,這個也可以通過angular的動態數據綁定做到)。
趨勢分析
這里我們直接利用開源的圖表顯示框架echarts做數據渲染,在用戶進入該模塊時,前端向后端請求符合echarts格式的json數據,后端返回數據后由前端對數據進行組裝(即設置echarts所需要的option對象),最后顯示在頁面上即可。
用戶收藏列表
添加操作,只需要在搜索返回的論文列表上提供一個star按鈕,並設置點擊事件,當用戶點擊論文的star按鈕之后,發送用戶id和論文id到后端,后端將收藏信息存入數據庫。當用戶訪問收藏列表時,
前端傳用戶的id給后端,后端返回用戶收藏的論文列表。
論文筆記
這里我們使用了ant design自帶的NgDrawerComponent,在用戶點擊"note"按鈕時,前端發送文章id和用戶id給后端,后端接受后返回過去用戶在該篇論文上做的筆記,前端彈出抽屜之后將過去的筆記先顯示在抽屜的textarea中,等用戶編輯完畢,點擊主頁面,抽屜收回,前端獲取新的筆記內容,並將其發送給后端,后端保存進數據庫。
代碼說明
前端實現側邊欄路由跳轉
const routes: Routes = [
{ path: 'welcome', loadChildren: () => import('./pages/pageSearch/welcome.module').then(m => m.WelcomeModule) },
{ path: 'page-trend/tendency',component:TendencyComponent},
{ path: 'page-trend/top-trend',component:TopTrendComponent},
{ path: 'article-manage/article-import',component:ArticleImportComponent},
{ path: 'article-manage/article-list',component:ArticleListComponent},
{ path: 'search/home',component:SearchComponentComponent},
{ path: 'user/info',component:UserInfoComponent},
{ path: 'user/star',component:UserStarComponent},
{ path: 'user/login',component:LoginComponent},
{ path: 'user/register',component:RegisterComponent},
{ path: '', pathMatch: 'full', redirectTo: 'search/home' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
前端實現請求公用服務
import { Injectable } from '@angular/core';
import { promise } from 'selenium-webdriver';
import { HttpClient,HttpHeaders} from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class RequestService {
private headers:any=new HttpHeaders({'Content-Type':'application/json'})
constructor(public http:HttpClient) { }
getData(api:any,body:any){
return new Promise((solve)=>{
this.http.post(api,body,this.headers).subscribe((data)=>{
solve(data);
});
});
}
}
前端實現存儲公用服務
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class StorageService {
constructor() { }
set(key:any,value:any){
localStorage.setItem(key,JSON.stringify(value))
}
get(key:any){
let temp:any=localStorage.getItem(key)
return JSON.parse(temp)
}
remove(key:any){
localStorage.removeItem(key)
}
}
上傳文件后端處理
@auth.route("/upload_file",methods=["GET","POST"])
#@token_check_required
def upload_file():
try:
data=request.files.get('file')
# print(data.read())
filename = secure_filename(data.filename)
# 生成文件保存路徑
save_path = r"F:\寒假作業2\結隊作業2\Team_work\static\userupload\%s"%filename
# 保存文件
data.save(save_path)
List=[]
with open(save_path,'r')as f:
List=f.read().split(';')
f.close()
return responseBody(data={'keyList':List})
except Exception as e:
print(e)
return responseError(Responses.PARAMETERS_ERROR)
文章實體類
class Article:
meeting = ['CVPR', 'ECCV', 'ICCV']
def __init__(self):
self.title = "暫無"
self.time = "xxxx-xx-xx"
self.meeting_name = "暫無會議名稱"
self.auth_name = "暫無作者"
self.abstract = "暫無摘要"
self.keyword = "暫無關鍵詞"
self.address = "暫無論文地址"
self.pretime="2000"
def set_title(self, title):
self.title = title
def set_time(self, time):
if(list(time)[0]!='2'):
time=self.pretime
self.time=time+"-01-01"
self.time = datetime.datetime.strptime(self.time, '%Y-%m-%d')
self.pretime=time
def set_meeting_name(self, meeting_index):
self.meeting_name = self.meeting[meeting_index]
def set_auth_name(self, auth_name):
self.auth_name = auth_name
def set_abstract(self, abstract):
self.abstract = abstract
def set_keyword(self, keyword):
self.keyword = keyword
def set_address(self, address):
self.address = address
#判斷是否存在鍵
def judge_in(key,Json):
if(key in Json):
return True
else:
return False
處理文件函數
def get_response_dict(status, message):
return {
"status": status,
"message": message
}
class Responses:
OPERATION_SUCCESS = get_response_dict(200,"操作成功")
# 獲取目錄信息失敗
SEARCH_CATS_ERROR = get_response_dict(1001, "獲取目錄信息失敗")
# 未找到班級
NO_CLASS_FOUND = get_response_dict(1002, "未找到班級!")
# 未找到token信息
NO_TOKEN = get_response_dict(1003, "為找到token信息!")
INVALID_TOKEN = get_response_dict(1004, "無效的token!")
TOKEN_EXPRIRED = get_response_dict(1005, "您的會話已過期!")
NO_USER_FOUND = get_response_dict(1006, "該用戶不存在!")
INCORRECT_PASSWORD = get_response_dict(1007, "密碼錯誤!")
PARAMETERS_ERROR = get_response_dict(1008, "參數錯誤!")
NOT_SAME_PASSWORD = get_response_dict(1009,"兩次密碼不一致!")
AUTHORIZATION_ERROR = get_response_dict(1010,"權限不足!")
NO_RECORD_FOUND = get_response_dict(1011,"未找到記錄!")
EXIST_ACCOUNT=get_response_dict(1012,"賬號已存在!")
EXIST_NAME=get_response_dict(1013,"用戶名已存在")
SAVE_FILE_FAIL=get_response_dict(1014,"保存文件失敗")
EXIST_STAR=get_response_dict(1015,"該論文已收藏")
工具方法
# 返回格式
def responseBody(status=200, data=None, message=""):
return jsonify(status=status, data=data, message=message)
def responseError(info_dict):
return jsonify(status=info_dict.get("status"),
message=info_dict.get("message"))
def responseSuccess(info_dict):
return jsonify(status=info_dict.get("status"),
message=info_dict.get("message"))
def token_check_required(func):
@wraps(func)
def wrap_function():
try:
token = session.get("token")
if token is None:
return responseError(Responses.NO_TOKEN)
user_id = validate_token(token)
session["user_id"] = user_id
func()
except BadSignature:
return responseError(Responses.INVALID_TOKEN)
except SignatureExpired:
return responseError(Responses.TOKEN_EXPRIRED)
return wrap_function
def generate_token(user_id, expire=60 * 60):
serializer = TimedJSONWebSignatureSerializer(expires_in=expire, secret_key=secret_key)
return serializer.dumps(user_id)
def validate_token(token):
serializer = TimedJSONWebSignatureSerializer(secret_key=secret_key)
user_id = serializer.loads(token)
return user_id
心路歷程和收獲
心路歷程和收獲
fino(221801435)的心路歷程
剛開始看到這個題目搭配上這個截止日期的時候,我的第一反應是這肯定是做不完的(雖然后面延長了截止日期)。但是之后經過了一定的需求分析,划分出個別功能模塊,我發現其實有一些功能的寫法都是類似的(比如搜索頁面列表和用戶收藏列表,又比如對於數據的請求和數據的展示),之后我們利用框架來開發,由於我的隊友對於flask后端和部署已經非常熟練了,我們前端用的又是基於組件低耦合的Angular,所以后面的開發過程還算順利,而且我也對這種組件化開發模式有了更深入的了解,期間還復習了一下echarts的用法。
cold(221801424)的心路歷程
拿到項目要求是比較震驚的,要在短期內學習技術並實現項目是一件比較難的事情(后面有加長時間)。首先我們對題目要求進行了分析,並將其划分成幾個功能塊,划分之后感覺思路會清晰了許多。討論后,后端用flask進行編寫並且我和fino一起學習前端框架angular進行前端的設計,在這期間我們也遇到過angular模塊導入失敗、echarts數據渲染失敗等問題,但是兩個人可以共同思考,相互彌補,這使得解決bug比自己編寫時候更加的迅速。
隊友評價
fino(221801435)對cold的評價
cold擁有豐富的項目經驗,特別是后端開發和部署的知識,在這次項目上幫了大忙。他的編程思路也是我值得學習的,例如模塊之間如何搭配,前后端的消息發送如何處理。有時我寫的有錯誤的地方,他也能及時發現並指出我的錯誤,這使得我的開發效率高了很多。
cold 對 fino 的評價
fino隊友是一名非常可靠優秀的隊友,與他一起學習技術學到了很多。他在課程學習上是年段前列,他的學習方法讓我受益匪淺,在問題解決上能夠給我一些特別的思路並往往能夠取得不錯的解決效果,編寫代碼效率很高,有條不紊。搭建前端模塊時候也能做到分模塊化,讓項目結構更加清楚簡潔,這是我以前比較忽略的,感謝隊友!




