結對第二次作業


這個作業屬於哪個課程 2021春軟工實踐|W班 (福州大學)
這個作業的要求在哪里 結對作業二
結對學號 221801312、221801337
這個作業的目標 頂會熱詞統計的實現
其他參考文獻

PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划
• Estimate • 估計這個任務需要多少時間 10 10
Development 開發
• Analysis • 需求分析 (包括學習新技術) 40 100
• Design Spec • 生成設計文檔 20 15
• Design Review • 設計復審 20 5
• Coding Standard • 代碼規范 (為目前的開發制定合適的規范) 10 15
• Design • 具體設計 20 10
• Coding • 具體編碼 2960 2780
• Code Review • 代碼復審 20 25
• Test • 測試(自我測試,修改代碼,提交修改) 60 40
Reporting 報告
• Test Repor • 測試報告 30 90
• Size Measurement • 計算工作量 10 10
• Postmortem & Process Improvement Plan • 事后總結, 並提出過程改進計划 15 20
合計 3225 3120

Github倉庫

倉庫地址

目錄結構

因為原本學號1&學號2的方式在我們前端使用的Vue中會出現運行BUG,經過查找發現是‘&’符號的問題,所以我們將其換成了‘_’。

.
└── 221801312_221801337
    ├── Java
    │   ├── 后端
    │   └── 數據處理
    └── web
        ├── public
        └── src

訪問地址

作品展示

首頁

底欄可以讓直接跳轉到我們的git主頁。因為只放git鏈接作為底欄會顯得有一點空洞,所以我們模仿了現在比較流行的地方排版方式,包括版權信息、隱私條款、關於項目等內容,但是因為時間有限且我們的項目並沒有具體上線,所以我們沒有准備這部分的內容。

image-20210331223707192

數據可視化

這部分主要分為近年熱詞旭日圖和Top10條形圖。

旭日圖我們以來源、年份、熱詞、相關論文從內向外排版,用戶可以點擊扇形部分進入具體信息,有的熱詞的相關論文比較多,為了避免排版錯誤,我們選擇了當某一扇形密度大於某一值時隱藏內容,用戶可以點擊扇形查看更具體的信息。

Top10熱詞條形圖主要展示了出現頻率最高的十個詞匯,用戶可以自己選擇以條形圖或折線圖的形式展現,還可以縮放橫縱坐標,更細致地對比熱詞熱詞之間的頻次,相關論文等信息;在每個熱詞項中有該包含該熱詞的部分論文,因為論文標題過長所以我們只選擇顯示部分長度,將輸入懸停在連接上可以出現完整的論文標題,用戶可以直接點擊論文標題跳轉進入到該論文的詳情頁。

image-20210331224251753 image-20210401234913418

模糊搜索

用戶在搜索框進行輸入是,下方會實時顯示於用戶輸入內容相關的關鍵詞,用戶可以通過點擊或者上下按鍵的方式選擇我們提示的關鍵詞,按回車鍵可以直接進行搜索。

image-20210331224707982

論文搜索

支持作者、標題、關鍵詞同步搜索。因為我們后端實現了預加載,縮短了用戶等待的查詢數據庫的時間,所以我們的加載頁面會更加流暢。

image-20210331224924364

論文篩選

用戶可以在頂部欄下方的按鈕打開篩選工具,支持用戶通過排序方式、時間性、來源過濾篩選搜索出來的論文列表,用戶選擇完之后可以重新搜索,頁面重新加載之后會保留篩選工具的顯示,方便用戶清除的查看自己篩選的論文方式。

image-20210331225232316

論文展示

我們主要是模仿搜索引擎式較為簡約的排版方式,沒有多余的分割線等元素,當一篇論文的標題過長時會被我們隱藏部分內容,用戶可以將鼠標懸停在論文標題鏈接上看到完整的標題。關鍵詞和作者鏈接,用戶可以直接點擊關鍵詞或作者的鏈接,會直接跳轉到我們對相應的關鍵詞或作者的搜索頁,方便用戶查看一篇論文時可以快速搜去關於該論文作者或關鍵詞的其他內容。

image-20210402000032957

接口展示

接口展示頁(使用swagger生成)

結對討論過程

​ 與之前線上不同,現在回到了學校,我們的討論也從線上交流轉換到了線下討論,可以一起debug,效率提高了不少

結對照片
QQ圖片2021033022204213
使用echarts遇到的bug

設計實現過程

數據導入程序

結構圖

image-20210330133157357

代碼說明

  • 使用FastJson來轉換json文件
  • 使用mybatis來把數據存入數據庫

PaperDao

  • Dao層,有保存查詢數據庫的方法

PaperHandler

  • 因為給出的paper數據格式有兩種,所以我定義了一個PaperHandler接口
  • 這個接口要求實現一個將JSONObject轉換成Paper類的toPaper方法
  • 這樣可以更好的復用其他的方法,把不同數據格式的差異給隱藏起來

Paper

  • 論文類型的實體類
  • 其中提供了兩個方法,可以返回兩種實現了PaperHandler接口的匿名類,分別對應兩種不同格式的json文件
public class Paper {
	...
	public static PaperHandler getDefultPaperHandler(){
        //函數式接口,返回一個匿名類,對應CVPR和ICCV會議的json格式
		return new PaperHandler() {
			@Override
			public Paper toPaper(JSONObject jo, String meetName, File file){
				Paper paper = new Paper();
                //使用FastJson解析數據,並放入Paper實體類中
				paper.setTitle(jo.getString("title"));
				paper.setAbstract(jo.getString("abstract"));
				if(paper.getAbstract()==null)paper.setAbstract("");
				List<String> authors=new ArrayList<>();
				final JSONArray ja = jo.getJSONArray("authors");
				for (int i = 0; i < ja.size(); i++) {
					authors.add(ja.getJSONObject(i).getString("name"));
				}
				paper.setAuthors(authors);
				if(jo.getJSONArray("keywords")!=null)
					paper.setKeywords(
							jo.getJSONArray("keywords")
									.getJSONObject(0).getJSONArray("kwd").toJavaList(String.class)
					);
				else
					paper.setKeywords(new ArrayList<>());
				paper.setMeet(meetName);
				final SimpleDateFormat year = new SimpleDateFormat("yyyy");
				Date date = null;
				try {
					date = year.parse(jo.getString("publicationYear"));
				} catch (ParseException e) {
					e.printStackTrace();
				}
				paper.setPublicationYear(date);
				paper.setUrl(jo.getString("pdfUrl"));
				return paper;
			}
		};
	}
    //同上,對應ECCV會議的json格式,這里不再贅述
	public static PaperHandler getECCVPaperHandler(){
        ...
	}
}

Core

  • 需要會議的文件夾路徑(path)、會議名(meetName)和會議對應的PaperHandler實現類來構造。
  • 在PapersearchApplicaiton中分別注入三個Core,之后分別執行Core的start()方法來導入json文件到數據庫
@SpringBootApplication()
public class PapersearchApplication {
	...
	//注入三個Core對象
	@Bean(name = "iccv")
	public Core getIccv(){
		return new Core("./論文數據/ICCV", "ICCV", Paper.getDefultPaperHandler());
	}
	@Bean(name = "cvpr")
	public Core getCvpr(){
		return new Core("./論文數據/CVPR", "CVPR", Paper.getDefultPaperHandler());
	}
	@Bean(name = "eccv")
	public Core getEccv(){
		return new Core("./論文數據/ECCV", "ECCV", Paper.getECCVPaperHandler());
	}
	...
}

前端設計

技術棧

  • 前端框架使用Vue3
  • UI框架使用ElementUI plus
  • 使用axios進行Http請求
  • 數據可視化使用ECharts5

頁面設計

首頁

因為是模仿Google和百度的搜索引擎式的簡約風格,所以首頁主體就是LOGO和搜索框,其中的難點應該是在於通過搜索框輸入的內容進行模糊搜索,彈出建議搜索內容。這部分我通過ElementUI庫的’建議搜索框‘組件實現,當用戶輸入內容時從后端請求數據,彈出建議選項。用戶可以直接用鍵盤選擇建議選項,回車查詢。

//模糊搜索
const querySearch = (queryString, cb) => {
    var data={
        data:{searchWord:queryString,limit:7}
    };
    //console.log(data.data.searchWord);
    cb([]);
    var list= postHeader(data,true);
    list.then(
        function(value){
            keywordList=value.data.searchWord;
            for(var i=0;i<keywordList.length;i++){
                keywordList[i].value=keywordList[i].keyword;
            }
            cb(keywordList);
            //restaurants.value=keywordList;
        })

};
近年熱詞

這部分數據可視化因為在原型設計時,是通過直接向Axure中導入JS代碼實現,所以我們的代碼基本不需要什么改動,主要是數據的請求和填入;但是當我們填入數據之后,發現大部分的關鍵詞和論文名都太長,如果只是原本的旭日圖會出現信息顯示錯位的問題,所以我們使用了其他類型的旭日圖,當關鍵詞超出一定長度時使用省略號表示,最外圈用論文標題向外顯示,可以讓我們的圖表更加具有視覺沖擊力。

Top10熱詞

與近年熱詞相同,這部分的JS代碼我們在原型設計時就已經實現,這部分的難點主要是將每個關鍵詞的相關論文鏈接寫入圖表,讓用戶可以直接點擊相關論文的標題進入論文頁,我們通過慢慢拼接html代碼和鏈接實現,這部分頁出現的標題名稱太長的問題,我們的解決辦法還是超出部分用省略號表示,同時利用a標簽的titile屬性,可以讓用戶把鼠標放在標題上時提示出標題的全稱。

底部欄

這部分主要是設計了一些版權信息還有我們兩人的github地址等等,並沒有什么難點,但是因為這是我最先開始做的一部分,那時對Vue和ElementUI都還相當生疏,所以還是花費了部分時間。

論文列表

因為我們是搜索引擎式的風格,所以我們加入的“共搜索到XX條記錄”、藍色標題等元素,讓我們的頁面開起來更像百度。這部分是整個項目最復雜的地方,所以也是最耗時的一部分。

  • 首先關於論文列表刪除,在原型設計時我們也有談到,因為我們是搜索引擎式的風格,我們認為讓用戶刪除論文是不合理且很危險,所以我們沒有制作可以刪除論文列表的功能。
  • 這部分第一個問題就是數據請求問題,最初我們還是打算用POST來請求數據,但是這樣就會出現用戶刷新頁面之后數據全部丟失的情況,我們觀察的百度的請求方式,利用GET請求,把搜索詞、頁碼、排序方式、來源、時間性等數據記錄在GET中,能夠讓頁面數據不丟失。
  • 還有就是分頁的問題,我們為了更快地加載頁面,所以每次只請求一頁的9條數據,當用戶點擊頁數的時候重新請求數據,起初我們出現了頁面能夠成功請求但底部的頁碼按鈕因為刷新頁面被重置為1的問題,這部分我們的解決辦法是通過當前頁面的URL中的’page‘字段的值來重新設定頁碼數。
  • 我在每項論文的排版也出現了不小的問題,主要還是因為我前端的基礎較為薄弱,導致很多布局、屬性等都要一邊搜一邊寫;這里出現的問題就是論文名字太長,關鍵詞、作者數量不定的問題,論文名字還是使用超出長度省略號,鼠標懸停提示完整標題的形式。關鍵詞和作者部分,我通過限定好寬度和高度,超出部分自動隱藏,在填入數據時,利用v-for直接把取到的所有數據填入,超出部分即可自動隱藏。
  • 在整個項目中,最大的問題就是異步請求的問題了,因為axios的請求只能異步實現,但當頁面渲染好時數據還沒有請求到,出現的數據無法渲染的問題,在這個地方我卡了很長時間,試過讓Axios同步實現、用AJAX等方式,但因為我們的項目是通過Vue3編寫的,所以很多信息都比較少,都沒有找到比較好的解決辦法。后來在我的對友的幫助下,還是采用比較簡單粗暴的形式解決了(把數據處理都放進請求的回調函數中);再后來,我們使用Vue2的語言,才成功將數據雙向綁定,解決的異步的問題。
  • 這部分主要的問題還是我個人對VUE的不熟練,導致花費了大量的時間在“怎么實現”和“怎么會這樣”中切換,非常感謝我的對友陪我解決了很多問題。
//高級搜索,篩選論文列表,通過拼接URL重新加載頁面
searchAgain(){ 
    var newURL=window.location.pathname;
    newURL+="?searchWord=";
    newURL+=window.location.search.match('((?<=searchWord=).*?(?=&|$))')[0];
    if(this.orderBy!=null)
        newURL+="&orderBy="+this.orderBy;
    if(this.time!=null)
        newURL+="&time="+this.time;
    if(this.from!=null)
        newURL+="&meetId="+this.from
    newURL+="&page=0";
    //console.log(newURL);
    window.location.href=newURL;
}
//頁面加載時通過URL請求輸入,通過正則表達式查詢URL參數來實現用戶通過篩選工具刷新后篩選工具窗口繼續
//存在
created(){
    this.getData();
    this.currentPage=parseInt(window.location.search.match('((?<=page=).*?(?=&|$))')[0])+1 + "";
    console.log(this.currentPage);
    if(window.location.search.match('((?<=time=).*?(?=&|$))')!=null)
        this.time=window.location.search.match('((?<=time=).*?(?=&|$))')[0];
    if(window.location.search.match('((?<=orderBy=).*?(?=&|$))')!=null)
        this.orderBy=window.location.search.match('((?<=orderBy=).*?(?=&|$))')[0];
    if(window.location.search.match('((?<=meetId=).*?(?=&|$))')!=null)
        this.from=window.location.search.match('((?<=meetId=).*?(?=&|$))')[0];
    if(this.time!=null || this.from!=null || this.orderBy!=null)
        this.showTool=true;
},

后端設計

技術棧

  • 項目使用了spring boot為項目框架
  • dao層用了mybatis
  • 使用redis來緩存歷史查詢和圖表數據,其中查詢緩存時間為5分鍾,圖標信息緩存時間為1天
  • 使用swagger來生成接口文檔,方便前端調試

結構圖

  • Spring Bean關系依賴圖
image-20210329202044483 image-20210329202116538

數據庫設計

image-20210329113814358
數據庫er圖

代碼說明

使用Redis來緩存數據

問題描述

​ 由於類似旭日圖、柱狀圖的圖表展示需要的數據查詢量大,需要的查詢時間較長,但是同時圖表展示對數據的時效性要求並不高,所以應該使用緩存來提升查詢速度、降低對數據庫的壓力。

RedisConfigure類

/**
 *	通過注入自定義的CacheManager類來配置緩存
 */
@Configuration
public class RedisConfigure  {
    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Bean(name="cacheManger")
    public CacheManager initRedisCacheManager(){
        HashMap<String ,RedisCacheConfiguration> configMap = new HashMap<>();
		//為不同的數據設置不同的ttl,在性能和數據時效性之間保持一個平衡
        configMap.put("similarWord",getMyConfigure().entryTtl(Duration.ofMinutes(20)));
        configMap.put("searchPaper",getMyConfigure().entryTtl(Duration.ofMinutes(20)));
        configMap.put("top10",getMyConfigure().entryTtl(Duration.ofDays(1)));
        configMap.put("sunburst",getMyConfigure().entryTtl(Duration.ofDays(1)));
		//用構造器構造CacheManager
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(getMyConfigure())
                .withInitialCacheConfigurations(configMap)
                .build();
        return redisCacheManager;
    }
    //返回一個自定義的默認配置
    private RedisCacheConfiguration getMyConfigure(){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //將存入redis緩存時使用的序列化器改為支持json的序列化器,提升緩存數據的可讀性
        config=config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
        config=config.entryTtl(Duration.ofMinutes(5));
        return config;
    }
}

關於自調用緩存失效

​ 由於spring boot的緩存注解是通過AOP織入的,所以在類內的自調用不是調用代理對象,如:

public class MyService{
    @Cacheable
    	public Map<String,Object> searchPaperCount(...){...}
	@Cacheable
    	public Map<String,Object> searchPaper(...){
            ...
            res.put(this.searchPaperCount(...));//自調用,緩存失效
            ...
        }
}

​ 解決方法則是在類內注入當前類的接口或者直接注入當前類,然后調用注入的對象即可。

public class MyService{
    @Autowired
    MyService service;
        @Cacheable
    	public Map<String,Object> searchPaperCount(...){...}
	@Cacheable
    	public Map<String,Object> searchPaper(...){
            ...
            res.put(service.searchPaperCount(...));//調用了代理類,緩存生效
            ...
        }
}

結果

image-20210330003638824
第一次查詢
image-20210330003656082
第二次查詢
image-20210330161315760
redis中緩存的數據

​ 這里測試了sunburst接口,雖然這個接口的查詢量大但是對數據的時效性要求不高,所有可以使用緩存。由上面的兩次查詢可以看到,由於第二次查詢是從redis讀取緩存,相比於從數據庫查詢,查詢時間從477ms下降到了83ms,時間只有原來的\(\frac{1}{5}\),極大提升了用戶體驗。

預查詢

問題描述

​ 由於我們是標題、作者和關鍵詞聯表查詢,導致查詢速度比較慢,在沒有緩存的情況下查詢要800ms左右。為了提升用戶體驗,加入了預查詢功能:在用戶查詢一頁后,后端自動在新線程中查詢下一頁並放入緩存。

關鍵代碼

public class PaperSearchController {
	...
	@GetMapping("search_paper")
	@Operation(summary = "搜索文章接口")
	@ResponseBody
	public Map<String,Object> searchPaper(@Schema(defaultValue = "3D") String searchWord,
										  @Nullable Integer orderBy,
										  @Nullable @Schema(defaultValue = "5") Integer time,
										  @Nullable  Integer meetId,
										  @Schema(defaultValue = "0") Integer page,
										  HttpServletRequest http){
		if(page==null)page=0;
        //用戶當前查詢項
		Map<String,Object>resp=
            searchService.searchPaper(searchWord,orderBy,time,meetId,9,page);
		Integer finalPage = page;
        //將下一頁放入線程池中查詢,並將結果放入緩存
		threadPoolExecutor.execute(new Runnable() {
			@Override
			public void run() {
				searchService.searchPaper(searchWord,orderBy,time,meetId,9,finalPage +1);
			}
		});
        //返回用戶當前查詢結果
		return resp;
	}
}

結果

image-20210331165212244
查詢第一頁的數據
image-20210331165719078
查詢第二頁的數據

​ 通過預查詢,通過預測用戶查詢下一頁的需求,降低了查詢時間,提升了用戶體驗。

使用Aspect來包裝數據

問題描述

​ 請求和響應的數據都有統一的格式,下面是一個響應的數據格式

{
    timestamp:"當前時間戳",
    staus:"狀態碼",
    data:"實際返回的數據",
    message:"消息",
    path:"請求的URI"
}

​ 如果在每個Controller中都對數據的格式進行解析和封裝,會造成代碼的冗余,並且不便於后續維護。所以使用Aop來處理數據格式

ControllerAspect類

@Component
@Aspect
public class ControllerAspect {
    //設置切點
    @Pointcut("execution( * com.jdzy.papersearch.controller.*.*(..))")
    public void pointCut(){}

    //定義切面
    @Around("pointCut()")
    public Map<String,Object> getData(ProceedingJoinPoint jp){
		//獲取被代理函數的傳入參數
        Object[] args = jp.getArgs();
        HttpServletRequest httpReq = (HttpServletRequest) args[args.length-1];
        if(args[0]!=null&&args[0].getClass().getInterfaces()[0]==Map.class){
            Map<String,Object> data = (Map<String, Object>) args[0];
            data= (Map<String, Object>) data.get("data");
            args[0]=data;
        }
        Object resp = null;
        try {
            //執行連接點,並獲取返回值
            resp = jp.proceed(args);
        } catch (Throwable throwable) {
            log.error("Around失敗");
            throwable.printStackTrace();
        }
		//通過自己實現的HttpTools來包裝返回值
        return HttpTools.buildSuccessResp(resp, httpReq.getRequestURI());
    }
}

結果

  • 織入前返回的數據

image-20210330112038819

  • 織入后返回的數據

image-20210330112154003

關鍵詞補全和文章搜索的接口實現

關鍵代碼

  • PaperSearchService
@Service
public class PaperSearchService {
    //各種dao層注入
	@Autowired
	KeywordDao kDao;
	@Autowired
	PaperDao pDao;
	@Autowired
	AuthorDao aDao;
	@Autowired
	PaperSearchService service;

   /**
	 * 關鍵詞補全接口 
	 * @param word:用戶已輸入的關鍵詞
	 * @param limit:限制返回的條數
	 */
	@Cacheable(value = "similarWord",
			key="'word_'+#word+'_'+#limit")
	public List<Keyword> searchSimilarWord(String word,Integer limit){
		//通過' '和'+'來分割輸入的關鍵詞
		String[] split = word.split(" |\\+");
        //通過LinkedHashSet來保證在去重的同時保證插入的優先級
		HashSet<Keyword> set = new LinkedHashSet<>();
        //優先搜索分割之前的關鍵字,以求最大匹配
		List<Keyword> similarKeyword = kDao.findSimilarKeyword(word,limit);
		set.addAll(kDao.findSimilarKeyword(word,limit));
        //循環查找分割后的單詞,直到超出返回條數的限制
		for (String s : split) {
			if(set.size()>=limit){
				break;
			}
			set.addAll(kDao.findSimilarKeyword(s,limit));
		}
		List<Keyword> strings = new ArrayList<>(set);
		limit=Math.min(limit,strings.size());
        //由於subString無法序列化,所以構造一個新List來返回
		ArrayList<Keyword> res = new ArrayList<>(strings.subList(0, limit));
		return res;
	}
    
    /**
	 * 文章搜索接口,可同時查詢標題和作者
	 * @param searchWord 關鍵詞
	 * @param orderBy 排序方式,最相關或最新發布
	 * @param time 近n年文章
	 * @param meetId 會議id
	 * @param limit 返回條數限制
	 * @param page 分頁時當前的頁數
	 */
	@Cacheable(value = "searchPaper",
			key="'paper_'+#searchWord+'_'+#orderBy+'_'+#time+'_'+#meetId+'_'+#page")
	public Map<String,Object> searchPaper(String searchWord,Integer orderBy,Integer time,Integer meetId,Integer limit,Integer page){
		Map<String, Object> resp = new HashMap<>();
		List<Map<String,Object>> paperList= new ArrayList<>();
		Date years = null;
        //通過傳入的time來生成查詢的時間條件
		if(time!=null){
			int year = Calendar.getInstance().get(Calendar.YEAR);
			year-=time;
			try {
				years = new SimpleDateFormat("yyyy").parse(String.valueOf(year));
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
        //如果頁數和限制條數不為空,就處理
		Integer pages=null;
		if(limit!=null&&page!=null){
			pages=limit*page;
		}
        //傳入dao查詢
		List<Paper> papers = pDao.findPaperByWord(searchWord, orderBy, years, meetId, limit, pages);
		for (Paper paper : papers) {
			Map<String, Object> node = new HashMap<>();
            //通過文章id查對應的所有作者
			List<Author> authors = new ArrayList<>(aDao.findAuthorByPaperId(paper.getId(),15));
            //通過文章id查對應的關鍵詞
			List<Keyword> keywords = new ArrayList<>(kDao.findKeywordByPaperId(paper.getId(),null));
            //封裝數據
			...
		}
		resp.put("paperList",paperList);
        //獲取當前查詢結果的總條數
		resp.put("count",service.searchPaperCount(searchWord,years,meetId));
		return resp;
	}
    /**
     * 用來統計當前搜索條件的總數據條數,方便前端分頁
     * 同樣使用redis緩存,保證對應關鍵字的第一次請求會查詢數據庫
     */
	@Cacheable(value = "searchPaper",
			key="'paper_'+#searchWord+'_'+#publicationYears+'_'+#meetId")
	public Integer searchPaperCount(String searchWord,Date publicationYears,Integer meetId){
		return pDao.findPaperCountByWord(searchWord,publicationYears,meetId);
	}
}
測試接口
  • mybatis配置文件PaperMapper.xml
...
<mapper namespace="com.jdzy.papersearch.dao.PaperDao">
    ...
    <!--searchPaper中調用的尋找文章的sql語句-->
	<select id="findPaperByWord" resultType="com.jdzy.papersearch.pojo.Paper">
		select * from paper
		where (title like concat("%",concat(#{searchWord},"%"))
		or paper.id in (<!--作者聯表查詢-->
		    select paper_id
		    from author,paper_author
		    where author.name like concat("%",concat(#{searchWord},"%"))
		    and author.id=paper_author.author_id
		)
		or paper.id in (<!--關鍵詞聯表查詢-->
		    select paper_id
			from keyword,paper_keyword
			where keyword.keyword like concat("%",concat(#{searchWord},"%"))
			and keyword.id=paper_keyword.keyword_id
		))
		<if test="publicationYears!=null">
			and publication_year>=#{publicationYears}
		</if>
		<if test="meetId!=null">
			and meet_id=#{meetId}
		</if>
		<if test="orderBy==1">
			order by publication_year desc
		</if>
		<if test="page!=null and limit!=null">
			limit #{page},#{limit}
		</if>
	</select>
	...
</mapper>

部署方式

  • 前端通過npm打包成靜態網頁文件
  • 后端通過maven打包成jar后直接在服務器上運行
  • 通過nginx反向代理來解決跨域問題並啟用負載均衡

nginx配置文件

C799

  • redis部署方式為主從模式,提升讀寫性能

master服務器

2021-03-30_12-05

slave服務器

2021-03-30_12-07

心路歷程及收獲

  • 221801312

因為寒假學習了一點Vue,但是並沒有經過項目實戰,所以打算經過這次嘗試一下,但還是不出所料的漏洞百出,甚至還出現了讓我做后端的對友來幫助我解決前端的問題。在這次項目之前,我還沒有經歷過前后端交互的過程,通過這次結對,我大概了解了一些前后端交互的問題。最大的收獲應該就是我自認為我正式入門了VUE,希望以后能夠更加精進自己的技術。

  • 221801337

這次作業是第一次把我寒假學習的知識運用到實戰中。但是因為寒假學習中,我都是有照着書一步步實現的,所以這次基本上沒有遇到什么問題。這次的收獲就是對零散的知識的一次整合運用。

評價結對隊友

  • 221801312

正如我在心路歷程寫的一樣,我的后端隊友幫助我解決了一部分前端的問題,盡管他並沒有學過Vue;我的對友是一個行動力很強的人,遇到的問題總是會第一時間去解決,他在各種問題的處理、學習方式以及面對BUG的態度等等等都有很多我要學習的地方。除了這些,最重要的還是他在技術的方面的強大,我完全不需要擔心在后端數據庫請求時的效率問題,因為他就是一個精益求精的人。

  • 221801337

得益於回到了學校,我們可以一起線下討論、debug,在討論的過程中,感受到隊友是一個行動力很強的人


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM