0.黑馬頭條-學習目標
學習目標
- 能夠描述黑馬頭條項目有哪些業務
- 能夠了解黑馬頭條項目使用到什么技術
- 能夠了解黑馬頭條數據庫設計過程
- 能夠掌握初始化工程的搭建
- 能夠掌握接口開發的流程
- 能夠完成頻道管理的功能開發
- 能夠掌握使用接口swagger、postman、knife4j
今日重點
- 能夠了解黑馬頭條數據庫設計過程
- 能夠掌握初始化工程的搭建
- 能夠掌握接口開發的流程
- 能夠完成頻道管理的功能開發
- 能夠掌握使用接口swagger、postman、knife4j
1.項目介紹
1.1.背景及概述
1.1.1.背景
隨着智能手機的普及,人們更加習慣於通過手機來看新聞。由於生活節奏的加快,很多人只能利用碎片時間來獲取信息,因此,對於移動資訊客戶端的需求也越來越高。黑馬頭條項目正是在這樣背景下開發出來。黑馬頭條項目采用當下火熱的微服務+大數據技術架構實現。本項目主要着手於獲取最新最熱新聞資訊,通過大數據分析用戶喜好精確推送咨詢新聞。
1.1.2.概述
黑馬頭條項目是對在線教育平台業務進行大數據統計分析的系統。碎片化、切換頻繁、社交化和個性化現如今成為人們閱讀行為的標簽。黑馬頭條對海量信息進行搜集,通過系統計算分類,分析用戶的興趣進行推送從而滿足用戶的需求。
1.1.3.項目術語定義
- 項目:泛指黑馬頭條整個項目或某一項目模塊
- 工程:泛指黑馬頭條某一項目的源碼工程
- 用戶:泛指黑馬頭條APP用戶端用戶
- 自媒體人:泛指通過黑馬自媒體系統發送文章的用戶
- 管理員:泛指黑馬頭條管理系統的使用用戶
- App:泛指黑馬頭條APP
- WeMedia:泛指黑馬頭條自媒體系統
- Admin:泛指黑馬頭條管理系統
1.2.功能需求說明
1.2.1.功能架構圖
1.2.2.App主要功能大綱
- 頻道欄:用戶可以通過此功能添加自己感興趣的頻道,在添加標簽時,系統可依據用戶喜好進行推薦
- 文章列表:需要顯示文章標題、文章圖片、評論數等信息,且需要監控文章是否在APP端展現的行為
- 搜索文章:聯想用戶想搜索的內容,並記錄用戶的歷史搜索信息
- 個人中心:用戶可以在其個人中心查看收藏、關注的人、以及系統設置等功能
- 查看文章:用戶點擊文章進入查看文章頁面,在此頁面上可進行點贊、評論、不喜歡、分享等操作;除此之外還需要收集用戶查看文章的時間,是否看我等行為信息
- 實名認證:用戶可以進行身份證認證和實名認證,實名認證之后即可成為自媒體人,在平台上發布文章
- 注冊登錄:登錄時,驗證內容為手機號登錄/注冊,通過手機號驗證碼進行登錄/注冊,首次登錄用戶自動注冊賬號。
1.2.3.自媒體端功能大綱
- 內容管理:自媒體用戶管理文章頁面,可以根據條件進行篩選,文章包含草稿、已發布、未通過、已撤回狀態。用戶可以對文章進行修改,上/下架操作、查看文章狀態等操作
- 評論管理:管理文章評論頁面,顯示用戶已發布的全部文章,可以查看文章總評論數和粉絲評論數,可以對文章進行關閉評論等操作
- 素材管理:管理自媒體文章發布的圖片,便於用戶發布帶有多張圖片的文章
- 圖文數據:自媒體人發布文章的數據:閱讀數、評論數、收藏了、轉發量,用戶可以查看對應文章的閱讀數據
- 粉絲畫像:內容包括:粉絲性別分布、粉絲年齡分布、粉絲終端分布、粉絲喜歡分類分布
1.2.4.平台管理端功能大綱
- 用戶管理:系統后台用來維護用戶信息,可以對用戶進行增刪改查操作,對於違規用戶可以進行凍結操
- 用戶審核:管理員審核用戶信息頁面,用戶審核分為身份審核和實名審核,身份審核是對用戶的身份信息進行審核,包括但不限於工作信息、資質信息、經歷信息等;實名認證是對用戶實名身份進行認證
- 內容管理:管理員查詢現有文章,並對文章進行新增、刪除、修改、置頂等操作
- 內容審核:管理員審核自媒體人發布的內容,包括但不限於文章文字、圖片、敏感信息等
- 頻道管理:管理頻道分類界面,可以新增頻道,查看頻道,新增或修改頻道關聯的標簽
- 網站統計:統計內容包括:日活用戶、訪問量、新增用戶、訪問量趨勢、熱門搜索、用戶地區分布等數據
- 內容統計:統計內容包括:文章采集量、發布量、閱讀量、閱讀時間、評論量、轉發量、圖片量等數據
- 權限管理:超級管理員對后台管理員賬號進行新增或刪除角色操作
1.2.5.其他需求
1.2.6.交互需求
1.3.技術架構說明
包括前端(Weex、Vue、Echarts、WS)、網關(GateWay)、DevOps(單元測試、代碼規范)
服務層中包括中間件(Kafka)、索引、微服務、大數據存儲等重難點技術
- Weex+Vue+WebSocket :使用Weex跨平台開發工具,整合集成VUE框架,完成黑馬頭條移動端功能開發,並集成WebSocket實現即時消息(文章推薦、私信)的推送
- Vue+Echarts : 自媒體系統使用Vue開發關鍵,集成Echarts圖表框架,完成相關粉絲畫像、數據分析等功能
- Vue+Echarts+WebSocket : 管理系統也是使用Vue開發,集成Echarts,完成網站統計、內容統計等功能,集成WebSocket,實現系統看板實時數據自動化更新
- Spring-Cloud-Gateway : 微服務之前架設的網關服務,實現服務注冊中的API請求路由,以及控制流速控制和熔斷處理都是常用的架構手段,而這些功能Gateway天然支持
- PMD&P3C : 靜態代碼掃描工具,在項目中掃描項目代碼,檢查異常點、優化點、代碼規范等,為開發團隊提供規范統一,提升項目代碼質量
- Junit : 在持續集成思想中,單元測試偏向自動化過程,項目通過Junit+Maven的集成實現這種過程
- 運用Spring Boot快速開發框架,構建項目工程;並結合Spring Cloud全家桶技術,實現后端個人中心、自媒體、管理中心等微服務。
- 運用WebMagic爬蟲技術,完善系統內容自動化采集
- 運用Kafka完成內部系統消息通知;與客戶端系統消息通知;以及實時數據計算
- 運用MyCat數據庫中間件計算,對系統數據進行分開分表,提升系統數據層性能
- 運用Redis緩存技術,實現熱數據的計算,NoSession等功能,提升系統性能指標
- 運用Zoookeeper技術,完成大數據節點之后的協調與管理,提升系統存儲層高可用
- 使用Mysql存儲用戶數據,以保證上層數據查詢的高性能
- 使用Mongo存儲用戶熱數據,以保證用戶熱數據高擴展和高性能指標
- 使用FastDFS作為靜態資源存儲器,在其上實現熱靜態資源緩存、淘汰等功能
- 運用Habse技術,存儲系統中的冷數據,保證系統數據的可靠性
- 運用ES搜索技術,對冷數據、文章數據建立索引,以保證冷數據、文章查詢性能
- 運用Sqoop、Kettle等工具,實現大數據的離線入倉;或者數據備份到Hadoop
- 運用Spark+Hive進行離線數據分析,實現系統中各類統計報表
- 運用Spark Streaming + Hive+Kafka實現實時數據分析與應用;比如文章推薦
- 運用Neo4j知識圖譜技術,分析數據關系,產出知識結果,並應用到上層業務中,以幫助用戶、自媒體、運營效果/能力提升。比如粉絲等級計算
- 運用AI技術,來完成系統自動化功能,以提升效率及節省成本。比如實名認證自動化
2.環境搭建
2.1.數據庫設計規范及導入數據庫
2.1.1.數據庫設計
2.1.1.1.ER圖設計
er圖設計划分出了9個庫,各個庫主要解決的是某一個特定的業務。
數據庫設計規范,詳見資料文件夾下《黑馬頭條-數據庫規范設計說明書.md》文件。
PowerDesinger工具使用,詳見資料文件夾下’powerdesinger的基本使用’文件夾里的《powerdesinger的基本使用》文件。
2.1.1.2.分庫設計
黑馬頭條項目采用的分庫分表設計,因為業務比較復雜,后期的訪問量巨大,為了分攤數據庫的壓力,整個項目用的不只是一個數據庫。其中核心庫有7個,每一個數據庫解決的是一個業務點,非常接近與實際項目設計。
創建數據庫及表,可以參考資料中的sql腳本。
2.1.1.3.核心數據流轉圖
說明:整個項目其核心數據為文章信息,上圖主要說明文章的數據流轉
1 爬蟲系統從外網采集數據后入爬蟲庫,即爬蟲庫中保存了一份采集的文章信息。
2 自媒體人可以通過發布文章后首先進入自媒體庫。
3 爬蟲文章和自媒體文章最后都要經過審核成功后入appinfo庫,這里面的文章信息,最終是要給app端用戶所查看。
4 在app端用戶查看的時候,需要記錄用戶的一些行為,如轉發、評論、點贊等需要入用戶行為庫。
2.1.1.4.冗余設計
黑馬頭條項目全部采用邏輯關聯,沒有采用主外鍵約束。也是方便數據源冗余,盡可能少的使用多表關聯查詢。冗余是為了效率,減少join。單表查詢比關聯查詢速度要快。某個訪問頻繁的字段可以冗余存放在兩張表里,不用關聯了。
如查詢一個訂單表需要查詢該條訂單的用戶名稱,就必須join另外用戶表,如果業務表很大,那么就會查詢的很慢,這個時候我們就可以使用冗余來解決這個問題,在新建訂單的同時不僅僅需要把用戶ID存儲,同時也需要存儲用戶的名稱,這樣我們在查詢訂單表的時候就不需要去join另外用戶表,也能查詢出該條訂單的用戶名稱。這樣的冗余可以直接的提高查詢效率,單表更快。
2.2.初始化工程導入及環境說明
2.2.1.主體結構說明
后端工程基於Spring-boot 2.1.5.RELEASE 版本構建,工程父項目為heima-leadnews,並通過繼承方式集成Spring-boot。
【父項目下分4個公共子項目】:
- heima-leadnews-common : 是整個工程的配置核心,包括所有集成三方框架的配置定義,比如redis、kafka等。除此之外還包括項目每個模塊及整個項目的常量定義;
- heima-leadnews-model :項目中用到的Dto、Pojo、Mapper、Enums定義工程;
- heima-leadnews-utils : 工程公用工具類項目,包含加密/解密、Date、JSON等工具類;
- heima-leadnew-apis : 整個項目微服務暴露的接口的定義項目,按每個模塊進行子包拆分;
【多個微服務】:
- heima-leadnews-login:用於實現APP+自媒體端用戶的登錄與注冊功能;
- heima-leadnews-user:用於實現APP端用戶中心的功能,比如我的收藏、我的粉絲等功能;
- heima-leadnews-article:用於實現APP端文章的獲取與搜索等功能;還包括頻道、標簽等功能;
- heima-leadnews-behavior:用於實現APP端各類行為數據的上傳服務;
- heima-leadnews-quartz:用於封裝項目中所有的調度計算任務;
- heima-leadnews-wemedia:用於實現自媒體管理端的功能;
- heima-leadnews-admin:用於實現后台管理系統的功能;
- heima-leadnews-gateway:網關
2.2.2.后端通用工程搭建
2.2.2.1.開發環境說明
項目依賴環境(需提前安裝好):
- JDK1.8
- Intellij Idea
- Tomcat 8.5
- Git
2.2.2.2.IDEA開發工具配置
1.設置本地倉庫,建議使用資料中提供好的倉庫
2.設置項目編碼格式
3.后端初始項目導入
在今天資料目錄的初始工程目錄找到heima-leadnews.zip文件,拷貝到一個沒有中文和空格的目錄,解壓縮,使用idea打開即可。
注意
資料中提供的初始項目,有問題,建議使用隨堂筆記中的初始項目。
擴展
導入項目之后,在父工程的pom.xml文件中會有一些依賴報紅(依賴沒有下載引起的),這個時候可以將報紅的依賴導入的
此外還有可能maven-resources-plugin也可能會報紅,可以改為以下的配置
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>3.1.0</version>
<configuration>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
3.開發規范
3.1.前后端分離開發
3.1.1.前后端分離開發介紹
項目基於前后端分離的架構進行開發,前后端分離架構總體上包括前端和服務端,通常是多人協作開發
- 對於后端java工程師:
把精力放在設計模式,spring+springmvc,linux,mysql事務隔離與鎖機制,mongodb,http/tcp,多線程,分布式架構,彈性計算架構,微服務架構,java性能優化,以及相關的項目管理等等。
- 對於前端工程師:
把精力放在html5,css3,vuejs,webpack,nodejs,Google V8引擎,javascript多線程,模塊化,面向切面編程,設計模式,瀏覽器兼容性,性能優化等等。
3.1.2.前后端分離開發流程
1,需求分析
梳理用戶的需求,分析業務流程
2,接口定義
根據需求分析定義接口,定義出接口文檔,具體寫法可以參考資料中的接口文檔目錄中的《接口示例文檔.md》
注意:controller的訪問路徑又稱之為接口,所以接口文檔是針對controller訪問路徑的描述。
3,服務端和前端並行開發
服務端:依據接口文檔進行服務端接口開發
前端:根據用戶需求開發操作界面,並根據接口文檔制作mock數據,進行測試
4,前后端集成接口聯調
最終前端調用服務端接口完成業務
3.2.后端開發通用規范
3.2.1.開發原則
-
自頂向下的設計原則:功能應該從表現層分析再到控制層、服務層、持久層逐層設計
-
自底向上的開發原則:上層需調用下層,因此開發應從底層向上層逐層開發項目中開發的層次次序參考DB->中間件->持久層->服務層->控制層
-
單一職責的開發原則:類或者方法提供的功能應該單一明確,特別越底層越應單一職責,以便維護項目中Mapper方法必須功能單一,參數明確,拒絕兩種以上的持久邏輯使用同一個Mapper方法
-
依賴倒置的開發原則:上層依賴下層,是依賴下層接口,並不是依賴下層的實現項目中每層都是通過接口調用Controller->Service->Mapper
3.2.2.開發步驟
- 明確類定義:明確哪些是重用類,哪些是需要新增的類
- 明確主鍵規則:確認操作表的ID生成規則,自增或id_work
- ControllerApi定義:定義接口
- Mapper實現:使用mybatis-plus封裝的方法還是自定義mapper映射
- Service實現:可用通過時序圖幫助我們梳理實現邏輯
- Controller實現:簡單的Service層調用
- 單元測試或接口測試或前端直接聯調測試
3.2.3.接口版本規范說明
隨着業務的復雜,同一個接口可能出現多個版本,為了方便后期切換和AB測試,需要定義接口的版本號
- 在某一個微服務下訪問controller的時候在包名下加一個版本號,如下
com.heima.article.controller.v1
- 在訪問具體的接口方法的url映射的時候也應該加上版本說明,如下:
@RequestMapping("/api/v1/article")
擴展
AB測試:為同一個目標,設計兩種方案,將兩種方案隨機投放市場中,讓組成成分相同(相似)用戶去隨機體驗兩種方案之一,根據觀測結果,判斷哪個方案效果更好。
3.3.接口通用請求和響應
dto(Data Transfer Object):數據傳輸對象,用於展示層與服務層之間的數據傳輸對象,比如Result
3.3.1.通用的響應對象
不分頁:com.heima.model.common.dtos.ResponseResult
/**
* 通用的結果返回類
* @param <T>
*/
public class ResponseResult<T> implements Serializable {
private String host;
private Integer code;
private String errorMessage;
private T data;
public ResponseResult() {
this.code = 200;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.errorMessage = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.errorMessage = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS,AppHttpCodeEnum.SUCCESS.getErrorMessage());
if(data!=null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getErrorMessage());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums,String errorMessage){
return setAppHttpCodeEnum(enums,errorMessage);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getErrorMessage());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums,String errorMessage){
return okResult(enums.getCode(),errorMessage);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.errorMessage = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.errorMessage = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}
分頁通用返回:com.heima.model.common.dtos.PageResponseResult
public class PageResponseResult extends ResponseResult {
private Integer currentPage;
private Integer size;
private Integer total;
public PageResponseResult(Integer currentPage, Integer size, Integer total) {
this.currentPage = currentPage;
this.size = size;
this.total = total;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
}
3.3.2.通用的請求dtos
接受頁面分頁請求參數:com.heima.model.common.dtos.PageRequestDto
@Data
@Slf4j
public class PageRequestDto {
protected Integer size;
protected Integer page;
public void checkParam() {
if (this.page == null || this.page < 0) {
setPage(1);
}
if (this.size == null || this.size < 0 || this.size > 100) {
setSize(10);
}
}
}
3.3.3.通用的異常枚舉
通用異常信息返回枚舉:com.heima.model.common.enums.AppHttpCodeEnum
public enum AppHttpCodeEnum {
// 成功段0
SUCCESS(0,"操作成功"),
// 登錄段1~50
NEED_LOGIN(1,"需要登錄后操作"),
LOGIN_PASSWORD_ERROR(2,"密碼錯誤"),
// TOKEN50~100
TOKEN_INVALID(50,"無效的TOKEN"),
TOKEN_EXPIRE(51,"TOKEN已過期"),
TOKEN_REQUIRE(52,"TOKEN是必須的"),
// SIGN驗簽 100~120
SIGN_INVALID(100,"無效的SIGN"),
SIG_TIMEOUT(101,"SIGN已過期"),
// 參數錯誤 500~1000
PARAM_REQUIRE(500,"缺少參數"),
PARAM_INVALID(501,"無效參數"),
PARAM_IMAGE_FORMAT_ERROR(502,"圖片格式有誤"),
SERVER_ERROR(503,"服務器內部錯誤"),
// 數據錯誤 1000~2000
DATA_EXIST(1000,"數據已經存在"),
AP_USER_DATA_NOT_EXIST(1001,"ApUser數據不存在"),
DATA_NOT_EXIST(1002,"數據不存在"),
// 數據錯誤 3000~3500
NO_OPERATOR_AUTH(3000,"無權限操作");
int code;
String errorMessage;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.errorMessage = errorMessage;
}
public int getCode() {
return code;
}
public String getErrorMessage() {
return errorMessage;
}
}
3.3.4.使用示例
在ResponseResult中的main方法中進行操作
public static void main(String[] args) {
//前置
/*
AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
System.out.println(success.getCode());
System.out.println(success.getErrorMessage());
*/
//查詢一個對象
/*
Map map = new HashMap();
map.put("name","zhangsan");
map.put("age",18);
ResponseResult result = ResponseResult.okResult(map);
System.out.println(JSON.toJSONString(result));
*/
//新增,修改,刪除 在項目中統一返回成功即可
/*
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
System.out.println(JSON.toJSONString(result));
*/
//根據不用的業務返回不同的提示信息 比如:當前操作需要登錄、參數錯誤
/*
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
System.out.println(JSON.toJSONString(result));
*/
//查詢分頁信息
PageResponseResult responseResult = new PageResponseResult(1, 5, 50);
List list = new ArrayList();
list.add("itcast");
list.add("itheima");
responseResult.setData(list);
System.out.println(JSON.toJSONString(responseResult));
}
3.4.多環境切換
在每一個微服務的工程中的根目錄下創建三個文件,方便各個環境的切換
(1)maven_dev.properties
定義開發環境的配置
(2)maven_prod.properties
定義生產環境的配置
(3)maven_test.properties
定義測試環境的配置,開發階段使用這個測試環境
默認加載的環境為test,在打包的過程中maven指令也可以指定參數打包: package -P test/prod/dev
具體配置,請查看父工程下的maven插件的profiles配置
<!-- maven的profile設置,主要來實現根據maven指令參數實現多環境切換 -->
<profiles>
<profile>
<id>dev</id>
<build>
<filters>
<filter>maven_dev.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>test</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<filters>
<filter>maven_test.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>prod</id>
<build>
<filters>
<filter>maven_prod.properties</filter>
</filters>
</build>
</profile>
</profiles>
4.平台運行端開發
4.1.頻道管理
4.1.1.需求分析
整體頁面
新增彈窗
數據庫表:頻道表:ad_channel
實體類
實體類存放位置
/**
* <p>
* 頻道信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("ad_channel")
public class AdChannel implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 頻道名稱
*/
@TableField("name")
private String name;
/**
* 頻道描述
*/
@TableField("description")
private String description;
/**
* 是否默認頻道
*/
@TableField("is_default")
private Boolean isDefault;
@TableField("status")
private Boolean status;
/**
* 默認排序
*/
@TableField("ord")
private Integer ord;
/**
* 創建時間
*/
@TableField("created_time")
private Date createdTime;
}
4.1.2.微服務搭建
1.創建springboot功能heima-leadnews-admin
2.pom.xml文件中引入依賴
<dependencies>
<!-- 引入依賴模塊 -->
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-leadnews-model</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-leadnews-common</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-leadnews-apis</artifactId>
</dependency>
<!-- Spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
注意:其中mybatis-plus相關的依賴在heima-leadnews-model中定義 其中實體類需要mybatis-plus的注解
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
3.創建引導類和包結構
引導類
@SpringBootApplication
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class,args);
}
}
包結構命名規范:
- com.heima.${模塊名稱}為基礎包名 如平台管理就是 com.heima.admin
- config 配置信息
- controller.v1 控制層
- feign 需要遠程調用的feign接口
- service 業務層
- mapper 持久層
4.application.yml文件配置
# 項目服務端口
server:
port: 9001
spring:
# 項目服務名稱
application:
name: leadnews-admin
# 數據庫設置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 設置Mapper接口所對應的XML文件位置,如果你在Mapper接口中有自定義方法,需要進行該配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 設置別名包掃描路徑,通過該屬性可以給包中的類注冊別名
type-aliases-package: com.heima.model.admin.pojos
5.在引導類中增加Mybatis-plus的mapper接口掃描操作,並設置分頁插件
@SpringBootApplication
@MapperScan("com.heima.admin.mapper")
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class,args);
}
/**
* mybatis-plus分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
6.新建日志文件:logback.xml(類似於log4j.properties)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定義日志文件的存儲地址,使用絕對路徑-->
<property name="LOG_HOME" value="E:/WorkSpace/2020WorkSpace/logs"/>
<!-- Console 輸出設置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 異步輸出 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丟失日志.默認的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默認的隊列的深度,該值會影響性能.默認值為256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一個 -->
<appender-ref ref="FILE"/>
</appender>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.boot" level="debug"/>
<root level="info">
<!--<appender-ref ref="ASYNC"/>-->
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
4.1.3.頻道列表-接口定義
接口詳情
1.在heima-leadnews-apis模塊中定義接口com.heima.api.admin.ChannelControllerApi
package com.heima.apis.admin;
import com.heima.model.admin.dtos.ChannelDto;
import com.heima.model.common.dtos.ResponseResult;
public interface AdChannelControllerApi {
/**
* 根據名稱分頁查詢頻道列表
* @param dto
* @return
*/
public ResponseResult findByNameAndPage(ChannelDto dto);
}
2.在heima-leadnews-model模塊中定義實體類ChannelDto,繼承PageRequestDto
package com.heima.model.admin.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
@Data
public class ChannelDto extends PageRequestDto {
/**
* 頻道名稱
*/
private String name;
}
4.1.4.頻道列表-功能實現
1.在heima-leadnews-admin中的com.heima.admin.mapper包下定義持久層接口
package com.heima.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.admin.pojos.AdChannel;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AdChannelMapper extends BaseMapper<AdChannel> {
}
2.在heima-leadnews-admin中的com.heima.admin.service包下定義業務層接口
package com.heima.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.admin.dtos.ChannelDto;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.common.dtos.ResponseResult;
public interface AdChannelService extends IService<AdChannel> {
/**
* 根據名稱分頁查詢頻道列表
* @param dto
* @return
*/
public ResponseResult findByNameAndPage(ChannelDto dto);
}
在com.heima.admin.service.impl包下定義業務層接口實現類
package com.heima.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.admin.mapper.AdChannelMapper;
import com.heima.admin.service.AdChannelService;
import com.heima.model.admin.dtos.ChannelDto;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Service
public class AdChannelServiceImpl extends ServiceImpl<AdChannelMapper, AdChannel> implements AdChannelService {
@Override
public ResponseResult findByNameAndPage(ChannelDto dto) {
//1.參數檢測
if(dto==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//分頁參數檢查
dto.checkParam();
//2.安裝名稱模糊分頁查詢
Page page = new Page(dto.getPage(),dto.getSize());
LambdaQueryWrapper<AdChannel> lambdaQueryWrapper = new LambdaQueryWrapper();
if(StringUtils.isNotBlank(dto.getName())){
// AdChannel::getName : 就是調用AdChannel中的getName()獲取返回結果,再在like中使用
lambdaQueryWrapper.like(AdChannel::getName,dto.getName());
}
IPage result = page(page, lambdaQueryWrapper);
//3.結果封裝
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)result.getTotal());
responseResult.setData(result.getRecords());
return responseResult;
}
}
3.在heima-leadnews-admin中的com.heima.admin.controller.v1包中定義ChannelController,注意:ChannelController實現接口AdChannelControllerApi
@RestController
@RequestMapping("/api/v1/channel")
public class AdChannelController implements AdChannelControllerApi {
@Autowired
private AdChannelService channelService;
@PostMapping("/list")
@Override
public ResponseResult findByNameAndPage(@RequestBody ChannelDto dto){
return channelService.findByNameAndPage(dto);
}
}
4.2.接口測試工具
啟動heima-leadnews-admin項目,進行測試。
注意事項
注意1
在install工程的時候,會出現heima-leadnews-admin工程install失敗,這個不要緊,因為heima-leadnews-admin是要啟動起來作為web項目存在的,所以不用install也可以。
注意2
啟動heima-leadnews-admin出現以下信息,不是啟動失敗
4.2.1.接口測試工具-postman
1.簡介
Postman是一款功能強大的網頁調試與發送網頁HTTP請求的Chrome插件。postman被500萬開發者和超100,000家公司用於每月訪問1.3億個API。java開發通常是作為后台開發語言,通常的項目中的接口開發需要一款測試工具來調試接口,這樣無需前端頁面也不耽誤后台代碼的開發進度,postman作為一個接口測試工具,是一個非常不錯的選擇。
2.安裝
解壓資料文件夾中的軟件,安裝即可。
3.請求和響應
請求方式選擇
url輸入,並發送請求
json請求參數的設置
整體設置,並發送請求,獲取響應數據。
4.2.2.接口測試工具-swagger
1.簡介
Swagger 是一個規范和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務(https://swagger.io/)。 它的主要作用是:
- 使得前后端分離開發更加方便,有利於團隊協作
- 接口的文檔在線自動生成,降低后端開發人員編寫接口文檔的負擔
- 功能測試
Spring已經將Swagger納入自身的標准,建立了Spring-swagger項目,現在叫Springfox。通過在項目中引入Springfox ,即可非常簡單快捷的使用Swagger。
2.SpringBoot集成Swagger
2.1.引入依賴,在heima-leadnews-model模塊中引入該依賴
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
只需要在heima-leadnews-model中進行配置即可,因為其他微服務工程都直接或間接依賴即可。
2.2.在heima-leadnews-admin工程的config包中添加一個配置類
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInfo())
.select()
// 要掃描的API(Controller)基礎包
.apis(RequestHandlerSelectors.basePackage("com.heima"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInfo() {
Contact contact = new Contact("黑馬程序員","","");//設置接口文檔的作者、作者主頁地址、郵箱
//設置接口文檔的描述信息,title:標題,description:描述信息,contact:作者信息,version:版本
return new ApiInfoBuilder()
.title("黑馬頭條-平台管理API文檔")
.description("平台管理服務api")
.contact(contact)
.version("1.0.0").build();
}
}
2.3.Swagger常用注解
在Java類中添加Swagger的注解即可生成Swagger接口文檔,常用Swagger注解如下:
@Api:修飾整個類,描述Controller的作用 @ApiOperation:描述一個類的一個方法,或者說一個接口 @ApiParam:單個參數的描述信息
@ApiModel:用對象來接收參數
@ApiModelProperty:用對象接收參數時,描述對象的一個字段
@ApiResponse:HTTP響應其中1個描述
@ApiResponses:HTTP響應整體描述
@ApiIgnore:使用該注解忽略這個API
@ApiError :發生錯誤返回的信息
@ApiImplicitParam:一個請求參數
@ApiImplicitParams:多個請求參數的描述信息
@ApiImplicitParam屬性:
3.具體操作
3.1.在heima-leadnews-apis的AdChannelControllerApi中添加swagger注解
//@Api:接口集的描述 value:說明字段,tags:標識,description:描述信息
@Api(value = "頻道管理", tags = "channel", description = "頻道管理API")
public interface AdChannelControllerApi {
/**
* 根據名稱分頁查詢頻道列表
* @param dto
* @return
*/
//@ApiOperation:接口描述
@ApiOperation("頻道分頁列表查詢")
public ResponseResult findByNameAndPage(ChannelDto dto);
}
3.2.在heima-leadnews-model的ChannelDto和PageRequestDto中添加swagger注解
@Data
public class ChannelDto extends PageRequestDto {
/**
* 頻道名稱
*/
//@ApiModelProperty : 對象屬性描述
@ApiModelProperty("頻道名稱")
private String name;
}
@Data
@Slf4j
public class PageRequestDto {
//value:描述信息,required : 是否為必須的描述
@ApiModelProperty(value="當前頁",required = true)
protected Integer size;
@ApiModelProperty(value="每頁顯示條數",required = true)
protected Integer page;
public void checkParam() {
if (this.page == null || this.page < 0) {
setPage(1);
}
if (this.size == null || this.size < 0 || this.size > 100) {
setSize(10);
}
}
}
3.3.重新啟動項目,訪問地址:http://localhost:9001/swagger-ui.html
查詢
先點擊Try it out 輸入參數,然后點擊Execute,結果如下:
4.2.3.接口測試工具-knife4j
1.簡介
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強解決方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一樣小巧,輕量,並且功能強悍!
gitee地址:https://gitee.com/xiaoym/knife4j
官方文檔:https://doc.xiaominfo.com/
效果演示:http://knife4j.xiaominfo.com/doc.html
2.核心功能
該UI增強包主要包括兩大核心功能:文檔說明 和 在線調試
- 文檔說明:根據Swagger的規范說明,詳細列出接口文檔的說明,包括接口地址、類型、請求示例、請求參數、響應示例、響應參數、響應碼等信息,使用swagger-bootstrap-ui能根據該文檔說明,對該接口的使用情況一目了然。
- 在線調試:提供在線接口聯調的強大功能,自動解析當前接口參數,同時包含表單驗證,調用參數可返回接口響應內容、headers、Curl請求命令實例、響應時間、響應狀態碼等信息,幫助開發者在線調試,而不必通過其他測試工具測試接口是否正確,簡介、強大。
- 個性化配置:通過個性化ui配置項,可自定義UI的相關顯示信息
- 離線文檔:根據標准規范,生成的在線markdown離線文檔,開發者可以進行拷貝生成markdown接口文檔,通過其他第三方markdown轉換工具轉換成html或pdf,這樣也可以放棄swagger2markdown組件
- 接口排序:自1.8.5后,ui支持了接口排序功能,例如一個注冊功能主要包含了多個步驟,可以根據swagger-bootstrap-ui提供的接口排序規則實現接口的排序,step化接口操作,方便其他開發者進行接口對接
3.快速集成
3.1.在heima-leadnews-common模塊中的pom.xml
文件中引入knife4j的依賴
<!-- knife4j依賴 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
3.2.在heima-leadnews-admin的SwaggerConfiguration中添加配置
@Configuration
@EnableSwagger2 //開啟Swagger支持
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfiguration {
........
}
3.3.啟動heima-leadnews-admin,在瀏覽器輸入地址:http://localhost:9001/doc.html
查詢頻道列表
4.3.頻道管理后續功能實現
4.3.1.頻道管理-新增
后台開發流程
1.在heima-leadnews-apis的AdChannelControllerApi中增加接口
/**
* 新增
* @param channel
* @return
*/
public ResponseResult save(AdChannel channel);
2.在heima-leadnews-admin的ChannelController中實現接口方法
/**
* 新增
* @param channel
* @return
*/
@Override
@PostMapping("/save")
public ResponseResult save(@RequestBody AdChannel channel) {
return channelService.insert(channel);
}
3.在heima-leadnews-admin的AdChannelService中增加接口
/**
* 新增
* @param channel
* @return
*/
public ResponseResult insert(AdChannel channel);
4.在heima-leadnews-admin的AdChannelServiceImpl中實現接口方法
/**
* 新增
* @param adChannel
* @return
*/
@Override
public ResponseResult insert(AdChannel adChannel) {
//1.檢查參數
if(null == adChannel){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.保存
adChannel.setCreatedTime(new Date());
save(adChannel);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
5.啟動項目測試
擴展
如下想要某些參數(id,createdTime)隱藏,可以在AdChannel的對應屬性上增加@ApiModelProperty(hidden = true)
4.3.2.頻道管理-修改
需求分析
有效無效狀態修改
編輯內容
1.在heima-leadnews-apis的AdChannelControllerApi中增加接口
/**
* 修改
* @param adChannel
* @return
*/
public ResponseResult update(AdChannel adChannel);
2.在heima-leadnews-admin的ChannelController中實現接口方法
/**
* 修改
*/
@Override
@PostMapping("/update")
public ResponseResult update(@RequestBody AdChannel adChannel) {
return channelService.update(adChannel);
}
3.在heima-leadnews-admin的AdChannelService中增加接口
/**
* 修改
* @param adChannel
* @return
*/
public ResponseResult update(AdChannel adChannel);
4.在heima-leadnews-admin的AdChannelServiceImpl中實現接口方法
/**
* 修改
* @param adChannel
* @return
*/
@Override
public ResponseResult update(AdChannel adChannel) {
//1.檢查參數
if(null == adChannel || adChannel.getId()==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.修改
updateById(adChannel);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
5.啟動項目測試
4.3.3.頻道管理-刪除
需求分析
注意:如果當前狀態為有效則不能刪除
1.在heima-leadnews-apis的AdChannelControllerApi中增加接口
/**
* 刪除
* @param id
* @return
*/
public ResponseResult deleteById(Integer id);
2.在heima-leadnews-admin的ChannelController中實現接口方法
/**
* 刪除
* @param id
* @return
*/
@Override
@GetMapping("/del/{id}")
public ResponseResult deleteById(@PathVariable("id") Integer id) {
return channelService.deleteById(id);
}
3.在heima-leadnews-admin的AdChannelService中增加接口
/**
* 刪除
* @param id
* @return
*/
public ResponseResult deleteById(Integer id);
4.在heima-leadnews-admin的AdChannelServiceImpl中實現接口方法
/**
* 刪除
* @param id
* @return
*/
@Override
public ResponseResult deleteById(Integer id) {
//1.檢查參數
if(id == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.判斷當前頻道是否存在 和 是否有效
AdChannel adChannel = getById(id);
if(adChannel==null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
if(adChannel.getStatus()){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"頻道有效不能刪除");
}
//int i = 10/0;
//3.刪除頻道
removeById(id);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
5.啟動項目測試
枚舉、MavenProfile、swagger的用法