項(xiàng)目業(yè)務(wù):
? ?項(xiàng)目背景:
? ? ? ? 本項(xiàng)目主要針對(duì)與需要找工作的人員砰逻、招聘人才的公司以及培訓(xùn)機(jī)構(gòu)
????????首先項(xiàng)目為找工作的人員提供合適的培訓(xùn)機(jī)構(gòu)中的課程進(jìn)行學(xué)習(xí),可以進(jìn)行線上以及線下學(xué)習(xí)吏口,并且可以瀏覽平臺(tái)中發(fā)布的招聘信息
????????然后為公司提供發(fā)布招聘信息、讓員工學(xué)習(xí)指定的培訓(xùn)機(jī)構(gòu)課程等服務(wù)冰更,進(jìn)行崗前培訓(xùn)等服務(wù)
????????最后為培訓(xùn)機(jī)構(gòu)提供了發(fā)布課程产徊,讓各種人員進(jìn)行學(xué)習(xí)等服務(wù)
????????為實(shí)現(xiàn)多方聯(lián)動(dòng)、互利共贏蜀细,從而開(kāi)發(fā)了這套人力資源管理系統(tǒng)
項(xiàng)目架構(gòu):
首先項(xiàng)目整體分為6大模板:
1舟铜、課程中心:管理發(fā)布課程,課程的搜索服務(wù)
2审葬、崗位中心:管理發(fā)布崗位信息深滚,崗位的搜索服務(wù)
3奕谭、用戶中心:系統(tǒng)用戶的課程和申請(qǐng)和訂單管理
4涣觉、鑒權(quán)中心:用戶注冊(cè)登錄以及權(quán)限控制
5、機(jī)構(gòu)管理中心:機(jī)構(gòu)入駐與管理
6血柳、系統(tǒng)管理中心:數(shù)據(jù)字典官册,員工角色權(quán)限信息的維護(hù)
然后項(xiàng)目整體采用前后端分離架構(gòu),前端使用vue技術(shù)棧难捌,后端使用基于spirngboot+springcloud的微服務(wù)架構(gòu)膝宁。
項(xiàng)目技術(shù):
1、springboot搭建單個(gè)服務(wù)根吁,springcloud治理服務(wù)
2员淫、Eureka注冊(cè)中心,用于服務(wù)的發(fā)現(xiàn)與注冊(cè)
3击敌、Ribbon/Feign介返,服務(wù)間的負(fù)載均衡調(diào)用
4、Hystrix斷路器,保證微服務(wù)的健壯圣蝎,防止雪崩
5刃宵、Zuul網(wǎng)關(guān),微服務(wù)的統(tǒng)一入口
6徘公、配置中心牲证,將配置文件統(tǒng)一管理
項(xiàng)目中遇到技術(shù)點(diǎn)及問(wèn)題:
跨域問(wèn)題:
瀏覽器自動(dòng)發(fā)送兩次請(qǐng)求,第一次發(fā)送option請(qǐng)求做詢問(wèn)关面,第二次發(fā)送正是請(qǐng)求坦袍,后端配置跨域過(guò)濾器,通過(guò)過(guò)濾器往響應(yīng)頭添加允許訪問(wèn)的請(qǐng)求響應(yīng)頭就可以解決此問(wèn)題等太,項(xiàng)目中在網(wǎng)關(guān)配置了過(guò)濾器
然后跨域問(wèn)題就是因?yàn)椴煌琁p键闺、端口互相訪問(wèn)造成的,只有前端在發(fā)送ajax請(qǐng)求才會(huì)出現(xiàn)跨域問(wèn)題
解決方案:
在網(wǎng)關(guān)中配置允許跨域的地址以及允許的請(qǐng)求方式和允許請(qǐng)求頭消息等
租戶入住業(yè)務(wù):
如果操作的domain涉及到了關(guān)聯(lián)查詢澈驼,就要在那個(gè)domain里面加入新的關(guān)聯(lián)對(duì)象辛燥,并且在關(guān)聯(lián)對(duì)象上面打上注解@TableField(exist=false),用于在生成sql時(shí)忽略該字段
后臺(tái)覆寫(xiě)service的實(shí)現(xiàn)進(jìn)行保存
首先租戶注冊(cè)參數(shù)包含其他表的字段缝其,所以需要單獨(dú)將租戶注冊(cè)頁(yè)面的所有字段封裝到實(shí)體類中
然后再service實(shí)現(xiàn)類上添加 @Transactional注解挎塌,表示事務(wù)管理,操作多張表時(shí)要么都成功内边,要么都失敗榴都,
在進(jìn)行對(duì)租戶信息保存時(shí),首先需要向租戶表插入數(shù)據(jù)漠其,mybatis默認(rèn)會(huì)返回新增數(shù)據(jù)的主鍵Id嘴高,并且需要設(shè)置租戶的狀態(tài)以及注冊(cè)時(shí)間為當(dāng)前的時(shí)間
然后在對(duì)員工表進(jìn)行操作,需要將租戶注冊(cè)頁(yè)面的用戶名以及密碼插入到員工表中和屎,還需要將租戶的Id 設(shè)置為員工表中的租戶id
再然后每一個(gè)租戶的管理員購(gòu)買(mǎi)不同的套餐具有不同的權(quán)限拴驮,所以需要設(shè)置租戶表中租戶管理員的Id,將當(dāng)前注冊(cè)用戶名ID設(shè)置為租戶管理員ID柴信,設(shè)置時(shí)可以先查詢到數(shù)據(jù)后再進(jìn)行修改套啤,這樣可以防止修改的時(shí)候數(shù)據(jù)丟失問(wèn)題
最后因?yàn)樽鈶糍?gòu)買(mǎi)了套餐,所以還需要對(duì)租戶和套餐的中間表進(jìn)行設(shè)置随常,中間表包括租戶ID潜沦、套餐ID,以及超時(shí)時(shí)間绪氛,可以直接獲取租戶以及套餐的ID設(shè)置進(jìn)中間表中唆鸡,由于租戶表本身是不具有套餐ID的,所以需要從封裝的Vo實(shí)體類中獲取套餐的ID枣察,最后設(shè)置超時(shí)時(shí)間争占,獲取當(dāng)前的時(shí)間然后+1年袄琳,然后在獲取毫秒值后設(shè)置到中間表中
Fastdfs的架構(gòu)原理:
FastDFS?是用c語(yǔ)言編寫(xiě)的一款開(kāi)源的分布式文件系統(tǒng)。充分考慮了冗余備份燃乍、負(fù)載均衡唆樊、線性擴(kuò)容等機(jī)制,并注重高可用刻蟹、高性能等指標(biāo)逗旁,很容易搭建一套高性能的文件服務(wù)器集群提供文件上傳、下載等服務(wù)舆瘪。
FastDFS?架構(gòu)包括?Tracker server?和?Storage server片效。客戶端請(qǐng)求?Tracker
server?進(jìn)行文件上傳英古、下載淀衣,通過(guò)?Tracker server?調(diào)度最終由?Storage server?完成文件上傳和下載。
Tracker
server?作用是負(fù)載均衡和調(diào)度召调,通過(guò)?Tracker
server?在文件上傳時(shí)可以根據(jù)一些策略找到?Storage server?提供文件上傳服務(wù)膨桥。可以將?tracker?稱為追蹤服務(wù)器或調(diào)度服務(wù)器唠叛,只嚣,tracker?也可以實(shí)現(xiàn)集群。每個(gè)?tracker?節(jié)點(diǎn)地位平等艺沼。收集?Storage?集群的狀態(tài)册舞。
Storage
server?作用是文件存儲(chǔ),客戶端上傳的文件最終存儲(chǔ)在?Storage?服務(wù)器上障般,Storageserver?沒(méi)有實(shí)現(xiàn)自己的文件系統(tǒng)而是利用操作系統(tǒng)的文件系統(tǒng)來(lái)管理文件调鲸。可以將storage稱為存儲(chǔ)服務(wù)器挽荡,Storage?分為多個(gè)組藐石,每個(gè)組之間保存的文件是不同的。每個(gè)組內(nèi)部可以有多個(gè)成員徐伐,組成員內(nèi)部保存的內(nèi)容是一樣的贯钩,組成員的地位是一致的,沒(méi)有主從的概念办素。
使用fastdfs實(shí)現(xiàn)文件上傳的流程
客戶端上傳文件后存儲(chǔ)服務(wù)器將文件ID返回給客戶端,此文件?ID?用于以后訪問(wèn)該文件的索引信息祸穷。文件索引信息包括:組名性穿,虛擬磁盤(pán)路徑,數(shù)據(jù)兩級(jí)目錄雷滚,文件名需曾。
首先組名就是文件上傳后所在的storage組的名稱,在文件上傳成功后由storage服務(wù)器返回
然后虛擬磁盤(pán)路徑就是storage配置的虛擬路徑,與磁盤(pán)選項(xiàng)?store_path*對(duì)應(yīng)呆万。如果配置了store_path0?則是?M00商源,如果配置了?store_path1?則是?M01,以此類推谋减。
再然后就是數(shù)據(jù)兩級(jí)目錄:storage?服務(wù)器在每個(gè)虛擬磁盤(pán)路徑下創(chuàng)建的兩級(jí)目錄牡彻,用于存儲(chǔ)數(shù)據(jù)文件。
最后是文件名出爹,與文件上傳時(shí)不同庄吼。是由存儲(chǔ)服務(wù)器根據(jù)特定信息生成,文件名包含:源存儲(chǔ)服務(wù)器严就,IP地址总寻、文件創(chuàng)建時(shí)間戳、文件大小梢为、隨機(jī)數(shù)和文件拓展名等信息
課程類型樹(shù)菜單的遍歷問(wèn)題:
首先在項(xiàng)目中查詢無(wú)限級(jí)別的課程類型的時(shí)候渐行,最開(kāi)始采用的是遞歸的方式,當(dāng)時(shí)發(fā)現(xiàn)每一次遞歸都要查詢數(shù)據(jù)庫(kù)铸董,效率低殊轴,對(duì)數(shù)據(jù)庫(kù)的操作太頻繁了,并且如果遞歸的深度太深袒炉,可能會(huì)導(dǎo)致棧溢出旁理,所以后面優(yōu)化采用了循環(huán)+Map的方式
?? 而使用循環(huán)+map的方式可以有效率的查詢到數(shù)據(jù),并且對(duì)數(shù)據(jù)庫(kù)的操作減少了很多我磁。
獲取無(wú)限級(jí)別的類型樹(shù)業(yè)務(wù)
首先創(chuàng)建一個(gè)list集合用于存放所有課程的一級(jí)類型并查詢出所有的課程類型
然后將查詢出來(lái)的所有類型的ID作為KEY孽文,類型封裝的對(duì)象作為Value存入map
再然后遍歷所有的課程類型后進(jìn)行判斷,如果是一級(jí)類型夺艰,將值存入list中芋哭,如果不是一級(jí)類型,則獲取課程類型的父ID(這里如果使用的是list郁副,就需要再次遍歷【嵌套循環(huán)】减牺,效率較低,所以使用了map存谎,就可以直接通過(guò)類型的ID獲取對(duì)應(yīng)類型的父類型拔疚,效率較高)
最后將當(dāng)前類型添加到父類型的子類型集合中,最后返回這個(gè)list集合
課程類型是以什么數(shù)據(jù)類型緩存到reids中
可以選擇字符串類型和序列化
首先java中的對(duì)象如果要存入redis中可以選擇String(字符串)既荚,將java對(duì)象轉(zhuǎn)換成json字符串稚失,保存到redis中,從redis查詢出來(lái)再將json字符串轉(zhuǎn)換為java對(duì)象
然后可以選擇序列化(byte[] 字節(jié)數(shù)組)恰聘,如果對(duì)象比較大句各,并且里面有很多屬性之外的方法要保存吸占,json字符串是不合適的,所以可以選擇對(duì)象序列化成字節(jié)數(shù)組保存到redis中凿宾,從redis中查詢出字節(jié)數(shù)據(jù)矾屯,再反序列化成java對(duì)現(xiàn)象就可以使用了
可以選擇字符串,如果json字符串比較長(zhǎng)初厚,可以將繼續(xù)序列化成byte[]字節(jié)數(shù)組
將課程類型緩存到redis中的原因
課程類型很少修改件蚕,經(jīng)常查詢將課程類型,所以需要緩存到redis中惧所,就是為了減輕數(shù)據(jù)庫(kù)的壓力骤坐,而不需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行頻繁的操作,并且可以提高訪問(wèn)速度下愈,增強(qiáng)用戶的體驗(yàn)
緩存穿透
首先緩存穿透是因?yàn)槟承┤巳绻l(fā)送大量的請(qǐng)求訪問(wèn)id不存在得用戶信息纽绍,在緩存中查詢不到,會(huì)直接查詢數(shù)據(jù)庫(kù)势似,如果并發(fā)量很高拌夏,這種現(xiàn)象就是直接穿透了redis緩存,大量請(qǐng)求之間訪問(wèn)數(shù)據(jù)庫(kù)會(huì)使數(shù)據(jù)庫(kù)拉跨履因,造成數(shù)據(jù)庫(kù)宕機(jī)
然后解決方案就是使用布隆過(guò)濾器亦或是如果緩存不存在這個(gè)數(shù)據(jù)障簿,則繼續(xù)訪問(wèn)數(shù)據(jù)庫(kù),當(dāng)數(shù)據(jù)庫(kù)也查詢不到此數(shù)據(jù)時(shí)栅迄,則存儲(chǔ)一個(gè)null值再redis中站故,并設(shè)置過(guò)期時(shí)間,同時(shí)使用同步的方式避免高并發(fā)情況下還是有很大一部分請(qǐng)求會(huì)直接訪問(wèn)數(shù)據(jù)庫(kù)的情況毅舆。
緩存擊穿
首先緩存擊穿是因大量的請(qǐng)求同時(shí)訪問(wèn)某一個(gè)KEY西篓,而這個(gè)KEY由于剛剛過(guò)期或者是redis剛剛啟動(dòng)緩存中還沒(méi)有此KEY,造成大量的請(qǐng)求訪問(wèn)數(shù)據(jù)庫(kù)的情況
再項(xiàng)目中使用了同步代碼塊和雙重校驗(yàn)鎖的形式進(jìn)行對(duì)數(shù)據(jù)的查詢憋活,可以有效地避免大量的請(qǐng)求同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)的情況岂津。
緩存雪崩
首先緩存雪崩是因在同一時(shí)間大量的緩存失效,導(dǎo)致查詢這些緩存的請(qǐng)求都會(huì)訪問(wèn)數(shù)據(jù)庫(kù)悦即,造成數(shù)據(jù)庫(kù)壓力過(guò)大吮成,和緩存擊穿不同的是,緩存擊穿是熱點(diǎn)數(shù)據(jù)失效辜梳,某幾個(gè)經(jīng)常訪問(wèn)的key粱甫,而緩存雪崩是大量的key同一時(shí)間失效,比如是在項(xiàng)目啟動(dòng)時(shí)冗美,從數(shù)據(jù)庫(kù)中加載所有的用戶信息魔种,設(shè)置了相同的過(guò)期時(shí)間,時(shí)間到后粉洼,這些用戶信息都會(huì)過(guò)期节预,查詢用戶的請(qǐng)求都會(huì)訪問(wèn)數(shù)據(jù)庫(kù)
解決方案就是讓過(guò)期時(shí)間均勻分布(過(guò)期時(shí)間和隨機(jī)值配合,在一段時(shí)間內(nèi)一次過(guò)期)
使用ElasticSearch的原因:
首先前臺(tái)搜索課程的時(shí)候属韧,會(huì)通過(guò)課程名稱等進(jìn)行模糊匹配安拟,如果課程非常多,like '%xx'會(huì)導(dǎo)致索引失效宵喂,造成全表掃描糠赦,并且效率較低
然后再高并發(fā)場(chǎng)景下,如果所有的課程的搜索都是訪問(wèn)數(shù)據(jù)庫(kù)锅棕,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)拙泽,所以不適合再高并發(fā)場(chǎng)景中使用
所以會(huì)使用ELASTICSEARCH,簡(jiǎn)單說(shuō)Elasticsearch使用Lucene作為內(nèi)部引擎裸燎,但是在使用它做全文搜索時(shí)顾瞻,只需要使用統(tǒng)一開(kāi)發(fā)好的API即可,而不需要了解其背后復(fù)雜的Lucene的運(yùn)行原理德绿。并且可以讓數(shù)據(jù)都保存在ES中荷荤,前臺(tái)用戶搜索數(shù)據(jù)時(shí)只能從ES中搜索,這樣就對(duì)數(shù)據(jù)庫(kù)的好處是十分大的移稳,并且ES的分布式特點(diǎn)支持海量的數(shù)據(jù)搜索
課程的上下線業(yè)務(wù):
上線:
首先客戶端發(fā)送請(qǐng)求蕴纳,執(zhí)行課程上線,請(qǐng)求到達(dá)后臺(tái)后執(zhí)行查詢數(shù)據(jù)庫(kù)个粱,將上線的課程數(shù)據(jù)中查詢出來(lái)后古毛,調(diào)用ES的批量保存接口,將數(shù)據(jù)保存到ES中都许,ES中只會(huì)保存上線的課程稻薇,并且修改數(shù)據(jù)庫(kù)中的上線時(shí)間和狀態(tài),用于前端顯示
下線:
首先前端發(fā)送課程下線的請(qǐng)求到達(dá)后端時(shí)調(diào)用ES接口梭稚,將數(shù)據(jù)從ES中刪除颖低,并且與數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行同步,修改數(shù)據(jù)庫(kù)中的下線時(shí)間和狀態(tài)弧烤,修改和刪除只能操作下線的課程忱屑,如果該課程處于上線的狀態(tài),則應(yīng)該先進(jìn)行下線的操作暇昂。
使用靜態(tài)化頁(yè)面時(shí)遇到的問(wèn)題:
使用feign進(jìn)行文件的上傳下載莺戒,首先寫(xiě)了一個(gè)配置類用于文件上傳編碼,因?yàn)槲募蟼鞅仨毷褂玫骄幋a急波,并且在類上添加了@configuration从铲,使這個(gè)編碼配置類變成整個(gè)項(xiàng)目都在使用這個(gè)編碼了,導(dǎo)致在調(diào)用Feign接口時(shí)澄暮,map的編碼出現(xiàn)問(wèn)題了名段,解決方法就是把@configuration去掉阱扬,這樣就只是把編碼單獨(dú)配置給了Feign作為文件上傳和下載的配置
問(wèn)題二:
使用feign上傳靜態(tài)化頁(yè)面后返回的fileId為空,將托底數(shù)據(jù)注釋后發(fā)現(xiàn)報(bào)錯(cuò)為time out and not fallback伸辟,初步判斷為消費(fèi)者調(diào)用服務(wù)超時(shí)麻惶,修改消費(fèi)者熔斷配置后,解決問(wèn)題
靜態(tài)化頁(yè)面業(yè)務(wù)流程
首先前端調(diào)用課程首頁(yè)頁(yè)面靜態(tài)化接口信夫,然后課程服務(wù)將模板打包上傳到fastdfs后返回fileId窃蹋,再然后課程服務(wù)準(zhǔn)備課程類型的data數(shù)據(jù)后調(diào)用頁(yè)面靜態(tài)化接口,傳入返回的fileId以及準(zhǔn)備的數(shù)據(jù)静稻,根據(jù)模板壓縮包的fileId從fastdfs中下載模板的壓縮包并解壓后設(shè)置data中的staticRoot數(shù)據(jù)(模板的根路徑)警没,然后調(diào)用工具類的方法進(jìn)行頁(yè)面靜態(tài)化的生成后將文件上傳到fastdfs中,fastdfs會(huì)返回文件的fileId振湾,將fileId相應(yīng)給課程服務(wù)然后發(fā)送到rabbitMQ中杀迹,使用部署的java項(xiàng)目(agent)監(jiān)聽(tīng)rabbitmq的隊(duì)列,獲取fileId載根據(jù)fileId下載靜態(tài)化頁(yè)面放入前端項(xiàng)目中恰梢,并且在增刪改課程時(shí)同步重新生成課程首頁(yè)
短信驗(yàn)證碼業(yè)務(wù)
首先獲取請(qǐng)求中的uuid和imageCode佛南,判斷圖形驗(yàn)證碼是否正確,如果錯(cuò)誤則返回圖形驗(yàn)證碼錯(cuò)誤消息嵌言,如果正確則進(jìn)行判斷redis中的短信驗(yàn)證碼是否存在嗅回,如果不在證明沒(méi)有發(fā)送過(guò)或者前一次發(fā)送的短信驗(yàn)證碼已經(jīng)超時(shí)了,重新生成短信驗(yàn)證碼并保存到redis中摧茴,然后調(diào)用短信發(fā)送接口將驗(yàn)證碼發(fā)送绵载,如果redis中的短信驗(yàn)證碼已存在,則獲取上一次發(fā)送的時(shí)間苛白,然后判斷是否已經(jīng)超過(guò)重新發(fā)送的時(shí)間娃豹,如果沒(méi)有超過(guò)則是非法請(qǐng)求,不再發(fā)送短信驗(yàn)證碼购裙,如果時(shí)間超過(guò)則將上一次發(fā)送的時(shí)間設(shè)置為當(dāng)前時(shí)間再調(diào)用短信發(fā)送接口將驗(yàn)證碼發(fā)送出去
單點(diǎn)登錄的解決方案
有三種方案懂版,可以依賴于一些權(quán)限框架,比如說(shuō)shiro、security作為認(rèn)證框架躏率,還可以使用單點(diǎn)登錄的服務(wù)器CAS躯畴,最后可以自己設(shè)計(jì)
單點(diǎn)登錄業(yè)務(wù)實(shí)現(xiàn)
?????? 首先前端用戶訪問(wèn)需要認(rèn)證的服務(wù)時(shí)進(jìn)行判斷是否登錄,如果沒(méi)有登錄則調(diào)用授權(quán)中心給前端用戶響應(yīng)一個(gè)登錄頁(yè)面進(jìn)行登錄薇芝。
??? 然后前端用戶發(fā)送登錄請(qǐng)求后進(jìn)入網(wǎng)關(guān)中蓬抄,網(wǎng)關(guān)對(duì)請(qǐng)求進(jìn)行過(guò)濾放行登錄請(qǐng)求,然后路由到授權(quán)中心進(jìn)行登錄的操作夯到,登錄成功后將用戶信息(access_token)存儲(chǔ)到redis中,用戶信息以key嚷缭,value的形式,,key設(shè)置為隨機(jī)值阅爽,value為用戶的信息路幸,然后將access_token保存到cookie中,前端就可以通過(guò)cookie獲取到redis中的用戶信息了优床。
??? 最后登錄成功后瀏覽器在進(jìn)行跳轉(zhuǎn)的其他頁(yè)面時(shí)劝赔,頁(yè)面判斷瀏覽器的cookie中的access_token是否存在誓焦,并且驗(yàn)證access_token是否正確胆敞,如果正確則將頁(yè)面返回給前端用戶,如果不存在或者錯(cuò)誤則再次調(diào)用授權(quán)中心將登陸頁(yè)面返回給前端用戶
??? 如果是訪問(wèn)后端的單點(diǎn)登錄杂伟,過(guò)濾器攔截請(qǐng)求移层,獲取請(qǐng)求中的access_token,判斷redis中的access_token是否存在赫粥,如果存在則說(shuō)明登陸過(guò)观话,放行,如果不存在則表示沒(méi)有登錄過(guò)越平,進(jìn)行攔截
網(wǎng)關(guān)的作用
首先網(wǎng)關(guān)作為微服務(wù)的統(tǒng)一入口频蛔,為所有請(qǐng)求統(tǒng)一做過(guò)濾
然后實(shí)現(xiàn)單點(diǎn)登錄的時(shí)候,是在網(wǎng)關(guān)中配置過(guò)濾器辫秧,當(dāng)前端發(fā)送請(qǐng)求時(shí)香伴,可以查看請(qǐng)求中的access_token规阀,并且可以驗(yàn)證access_token的正確性,以實(shí)現(xiàn)達(dá)到單點(diǎn)登錄的目的