1. 程式人生 > >【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API

【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API

## 【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API 通過本教程的學習,將帶你從零搭建一個商城系統。 當然,這個商城涵蓋了很多流行的`知識點`和`技術核心` ### 我可以學習到什麼? - SpringBoot - 鑑權與認證、token、有關許可權的相關的內容。 - 優雅的利用OSS 上傳檔案 - API 線上生成文件 - Redis - Redis 基本使用 - Redis 快取存放使用者token等 - Docker - 容器技術的使用 - SpringBoot 專案打包docker image - ElasticSearch - Elasticsearch 搜尋引擎框架 - RabbitMQ - 訊息佇列整合SpringBoot - Linux - 部署相關的Linux 命令的學習與使用 等等等。。。 不用猶豫了,和我一起來吧! ### 開始搭建 首先、當然以 `maven` 作為專案管理工具、以我們最熟悉的 `SpringBoot` 作為專案腳手架,幫助我們快速搭建起專案框架。 本小結需要了解的技術棧有: - `maven` 模組化的實現 - 引入`Mybatis-plus` 簡化CRUD - 設計基本的 `許可權三張表` ### 建立一個maven 專案 我這裡使用的是 `IDEA` ,希望朋友們也跟著我一起,當然其他優秀的整合開發工具也是很牛逼的,反正用著順手就行! ![](https://file.chaobei.xyz/20201007225455.png) 建立一個新的專案、自定義專案名稱,並且鍵入你自己的 `group id` 以及 `artifactId` 因為我們採用的是專案模組化的實現,父類就只是一個空殼,一般定義專案裡面需要的所有 `依賴資訊` 以及 `版本資訊` 等。引入我們所有下面的子專案都會用到的 `公共依賴包` 這些。 定義我們將要使用的 `Spring-boot` 版本,我們這是使用 `2.1.3` ```xml org.springframework.boot spring-boot-starter-parent 2.1.3.RELEASE ``` ### 劃分模組 這裡依舊參考 `mall` 專案對於模組的規劃,簡單介紹一下。 - mall-admin 後臺管理模組 - mall-common 公共包模組 - mall-mbg `mybatis-plus` 生成 mapper、model 等 - mall-security 鑑權、授權模組 暫時就先劃分這麼幾個吧!等後面用到了我們再劃分即可。 ### 父類定義版本和基本模組 ```xml pom
``` 父類作為一個空殼,其最主要的目的是模組化的劃分。它裡面其實是不包含程式碼的,所以將它的打包方式改為 `pom` > 就可以將父類下的 `src` 目錄刪掉了。 首先定義幾個每個模組**都會使用到**的依賴。比如 `aop切面` `test 測試模組` 等依賴資訊。 ```xml org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-aop
org.springframework.boot spring-boot-starter-test test
``` ### 使用 dependencyManagement 使用這個標籤是為了我們依賴的 `統一管理`。防止兩個模組引用不同版本的依賴,而導致打包衝突或者執行衝突等問題。 > 最大的好處也在於:父類定義好需要使用的依賴後、子類引用無需版本號。 ```xml 1.8 3.3.2 5.4.0 8.0.20
com.baomidou mybatis-plus-boot-starter ${mybatis.plus.version} cn.hutool hutool-all ${hutool.version} mysql mysql-connector-java ${mysql.connector.version} ``` 到這裡,我們基本的父類已經構建完成了,我們可以開始構建 `模組` 了。 ### 構建模組 直接在專案上右鍵 `new model` ,建立一個新的模組。 ![](https://file.chaobei.xyz/image-20201007232622658.png) ### 設計許可權三張表 建立後臺使用者表、用來儲存使用者資訊。 ```sql CREATE TABLE `ums_admin` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '後臺管理使用者', `username` varchar(64) NOT NULL COMMENT '使用者名稱', `password` varchar(64) NOT NULL COMMENT '密碼', `icon` varchar(1024) NOT NULL COMMENT '頭像', `lock` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0鎖定1正常使用', `email` varchar(128) NOT NULL COMMENT '電子郵箱', `nick_name` varchar(32) NOT NULL COMMENT '暱稱', `note` varchar(64) NOT NULL COMMENT '備註資訊', `create_time` datetime DEFAULT NULL COMMENT '建立時間', `login_time` datetime DEFAULT NULL COMMENT '最後登入時間', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除標記', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; ``` 建立後臺角色資訊表,儲存角色資訊。 ```sql CREATE TABLE `ums_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表', `name` varchar(64) NOT NULL COMMENT '角色名稱', `description` varchar(128) NOT NULL COMMENT '角色描述', `admin_count` smallint(6) NOT NULL DEFAULT '0' COMMENT '後臺使用者數量', `lock` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0鎖定 1正常使用', `sort` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序', `create_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '建立時間', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除狀態0 1正常', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; ``` 建立後臺選單許可權表,用於儲存選單關係。 ```sql CREATE TABLE `ums_menu` ( `id` int(11) NOT NULL COMMENT '選單表', `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父級ID', `title` varchar(11) NOT NULL COMMENT '選單標題', `level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '選單級別', `sort` smallint(6) NOT NULL DEFAULT '0' COMMENT '選單排序', `name` varchar(64) NOT NULL COMMENT '前端VUE 名稱', `icon` varchar(32) NOT NULL COMMENT '圖示', `hidden` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否隱藏 0隱藏 1展示', `create_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP, `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` 我們的許可權還是通過 `使用者-角色-許可權` 最經典的設計來進行了,這樣的許可權也適用大多數系統。 ### 實現一個 REST API 因為是前後端分離專案,我們所有的請求需要經過 `Controller` , 這也是現在絕大多數系統所使用的架構、這一套系統依舊沿用`Restful` 風格的介面。並且按照 如下圖的架構進行 `API` 的書寫。 #### Restful 風格介面 - POST `/user/` 新增一個使用者 - GET `/user/1` 查詢ID 為 `1` 的使用者資訊 - GET `/user/` 查詢所有的使用者資訊 - DELETE `/user/1` 刪除ID 為 `1` 的使用者資訊 - PUT `/user/1` 修改ID 為 `1` 的使用者資訊 - POST `/user/page` 當然就是按照傳入的條件進行分頁了 #### Restful 架構設定 ![](https://file.chaobei.xyz/bye-crud-drive.png_imagess) ### 開始寫程式碼吧 > 從上面的架構圖,我們已經可以寫出一個基本的CRUD Controller #### 包命名規則 一個合格的程式猿,寫的程式碼不僅給人一種舒服的感覺。而且包名命名等也是一個可以學習的點。 - `mapper` 當然就是mybatis mapper 放置的位置。 - `model` 一款 `ORM` 框架對重要的就是:資料庫物件與java物件的對映。 - `controller` 介面`api` 的位置。 - `pojo` 放置一些入參類、包裝類等。 - `config` 當然就是放置一些配置類。 #### Controller > 我們以 `ums_admin` 後臺使用者表作為示例,其實這些都是可以生成的~ 具體看 **開啟偷懶模式** - Controller 包含基本的 `CRUD` 介面。 - `Restful` 風格介面資訊,更加容易理解介面含義。 - `Swagger` 生成基本的API 文件資訊,以及測試介面。 - 校驗引數完整性! ```java @Api(tags = "ApiUmsAdminController",description = "後臺使用者") @RestController @RequestMapping("/umsAdmin") @Validated public class ApiUmsAdminController { @Autowired private UmsAdminService umsAdminService; /** *

查詢所有後臺使用者 *

author: mrc * * @return xyz.chaobei.common.api.CommonResult * @since 2020-10-12 11:18:42 **/ @ApiOperation("查詢所有後臺使用者") @GetMapping("/") public CommonResult getAll() { List allList = umsAdminService.findAll(); return CommonResult.success(allList); } /** *

預設分頁請求後臺使用者 *

author: mrc * * @param pageAO 分頁查詢引數 * @since 2020-10-12 11:18:42 * @return xyz.chaobei.common.api.CommonResult **/ @ApiOperation("預設分頁請求後臺使用者") @PostMapping("/page") public CommonResult paging(@RequestBody @ApiParam("分頁查詢引數") UmsAdminPageAO pageAO) { Page allList = umsAdminService.findPage(pageAO); return CommonResult.success(allList); } /** *

儲存一個後臺使用者 *

author: mrc * * @param params 儲存欄位 * @since 2020-10-12 11:18:42 * @return xyz.chaobei.common.api.CommonResult **/ @ApiOperation("儲存一個後臺使用者") @PostMapping("/") public CommonResult save(@RequestBody @Valid @ApiParam("儲存欄位") UmsAdminSaveAO params) { boolean isSave = umsAdminService.save(params); return CommonResult.result(isSave); } /** *

修改一個後臺使用者 *

author: mrc * * @param id 被修改的ID * @param params 被修改的欄位 * @since 2020-10-12 11:18:42 * @return xyz.chaobei.common.api.CommonResult **/ @ApiOperation("修改一個後臺使用者") @PutMapping("/{id}") public CommonResult update(@PathVariable("id") @ApiParam("被修改的ID") Integer id, @Valid @RequestBody @ApiParam("被修改的欄位") UmsAdminSaveAO params) { boolean isUpdate = umsAdminService.updateById(params,id); return CommonResult.result(isUpdate); } /** *

刪除一個後臺使用者 *

author: mrc * * @param id 被刪除的ID * @since 2020-10-12 11:18:42 * @return xyz.chaobei.common.api.CommonResult **/ @ApiOperation("刪除一個後臺使用者") @DeleteMapping("/{id}") public CommonResult delete(@Valid @NotNull @PathVariable("id") @ApiParam("被刪除的ID") Integer id) { boolean isDelete = umsAdminService.deleteById(id); return CommonResult.result(isDelete); } } ``` #### SaveAO > SaveAO 一般就是前端 `填寫表單入參的資訊` ,當然我們能直接使用 `DO` 進行攜帶引數。那樣不安全。`AO` 將引數從 `Controller` > > 攜帶後,通過 `javax.validation.Valid` 對欄位進行校驗後、方可進行下一步。 - `SaveAO` 將引數從 `Controller` 傳遞到 `Service` 處理邏輯 - `Controller` 入參的時候,檢驗 `SaveAO` 所包含的引數。 - @NotBlank - @NotNull - 略... - `@ApiModelProperty` 說明引數註釋資訊 ```java @Getter @Setter public class UmsAdminSaveAO { /** * 使用者名稱 */ @NotBlank @ApiModelProperty("使用者名稱") private String username; /** * 密碼 */ @NotBlank @ApiModelProperty("密碼") private String password; /** * 頭像 */ @ApiModelProperty("頭像") private String icon; /** * 0鎖定1正常使用 */ @NotNull @ApiModelProperty("0鎖定1正常使用") private Integer lock; /** * 電子郵箱 */ @NotBlank @ApiModelProperty("電子郵箱") private String email; /** * 暱稱 */ @ApiModelProperty("暱稱") private String nickName; /** * 備註資訊 */ @ApiModelProperty("備註資訊") private String note; } ``` 當然。這裡的所有引數都是可以自定義的。你想要哪些,就生成哪些~ #### Service - `Service` 負責將 `Controller` 傳遞的 `AO` 複製到 `DO(Database Object)` 。 - 呼叫 `Mapper` 的方法進行持久化。 - `Service` 返回一個 成功或者失敗的標誌。 - 邏輯異常,丟擲一個異常資訊【例如這個ID 找不到使用者。。。】,全域性捕獲後,返回給前端進行提示。 ```java @Service public class UmsAdminServiceimpl implements UmsAdminService { @Autowired private UmsAdminMapper umsAdminMapper; @Override public List findAll() { return umsAdminMapper.selectList(null); } @Override public Page findPage(UmsAdminPageAO pageAO) { Page page = new Page(pageAO.getCurrent(),pageAO.getSize()); QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("`username`", pageAO.getUsername()); wrapper.eq("`lock`", pageAO.getLock()); wrapper.eq("`note`", pageAO.getNote()); umsAdminMapper.selectPage(page, wrapper); return page; } @Override public boolean save(UmsAdminSaveAO params) { UmsAdminModel model = new UmsAdminModel(); BeanUtils.copyProperties(params,model); /** * 你的邏輯寫在這裡 */ int num = umsAdminMapper.insert(model); return SqlHelper.retBool(num); } @Override public boolean updateById(UmsAdminSaveAO params, Integer id) { UmsAdminModel model = new UmsAdminModel(); BeanUtils.copyProperties(params,model); /** * 你的邏輯寫在這裡 */ model.setId(id); int num = umsAdminMapper.updateById(model); return SqlHelper.retBool(num); } @Override public boolean deleteById(Integer id) { /** * 你的邏輯寫在這裡 */ int num = umsAdminMapper.deleteById(id); return SqlHelper.retBool(num); } } ``` #### Mapper - 繼承 `Mybatis-Plus BaseMapper` 獲得基礎CRUD 能力。 ```java public interface UmsAdminMapper extends BaseMapper { // 繼承mybatis-plus 獲得基礎crud } ``` #### Mybatis-Plus Config > 主要是配置 mybatis 掃描的mapper 所在的位置。以及開啟事務的支援。 ```java @Configuration @EnableTransactionManagement @MapperScan({"xyz.chaobei.mall.dao","xyz.chaobei.mall.mapper"}) public class MyBatisPlusConfig { } ``` ### 開啟偷懶模式 能不能有一種東西,給我生成這種重複的東西,而我**只關注邏輯**呢? 當然有了~ > 上面示例的程式碼都是用工具生成的~ 總不能一個一個敲出來吧~ > > 學會偷懶其實也是一種好處。**人類的發展不就是朝著偷懶的方向發展嘛** [![程式猿小碼/bye-crud-generate](https://gitee.com/mrc1999/bye-crud-generate/widgets/widget_card.svg?colors=4183c4,ffffff,ffffff,e3e9ed,666666,9b9b9b)](https://gitee.com/mrc1999/bye-crud-generate) ### 新增配置檔案 這已經是最後的幾個步驟了。新增配置檔案,主要是配置 `mybatis-plus` mapper 所在的位置。 以及配置我們的邏輯刪除、自動填充這兩個很好用的功能。 #### 配置邏輯刪除 > https://baomidou.com/guide/logic-delete.html > > 邏輯刪除有什麼好處呢?我覺得主要還是資料的完整性。上線以後、就算這條資料要被刪除,也只能是通過狀態隱藏起來, > > 並非真實刪除。 > > 還有一個注意的點就是:既然配置了這個`status` ,那麼所有的表都應該有這個欄位。 ```yaml #mybatis-plus 基礎配置 mybatis-plus: mapper-locations: - classpath:/dao/**/*.xml - classpath*:/mapper/**/*.xml global-config: db-config: logic-delete-field: status logic-not-delete-value: 1 logic-delete-value: 0 ``` #### 配置自動填充功能 > https://baomidou.com/guide/auto-fill-metainfo.html > > 一般情況下:我們每一條資料都會包含一個 `時間欄位(建立、修改)` 這樣的欄位每次要在:插入、修改的時候進行新增。 > > 其實很難受的。所以還是偷個懶~ 讓程式碼幫我們完成。我這裡只有一個 `createTime` 需要填充,你可以參考官網再詳細一些。 > > **所以:你的每個表都應該包含這個填充欄位** ```java @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); } @Override public void updateFill(MetaObject metaObject) {} } ``` 別忘了標識欄位。 ```java /** * 建立時間 */ @TableField(value = "`create_time`",fill = FieldFill.INSERT) private Date createTime; ``` ### 測試介面程式碼 > 細心的朋友已經發現了。我們系統已經集成了 `swagger` > > `swagger` 對於介面文件的生成和測試,簡直完美。程式碼寫好了,文件自然而然的被生成。並且可以在頁面上測試 `介面通訊` > > 簡直完美啊! #### 整合Swagger 考慮到 swagger 通用的配置類可能被多個模組所使用,所以我們首先建立一個 `abstract class` 讓子類重寫它的抽象方法。這樣就實現了一個通用的 `swagger config` ```java public abstract class BaseSwaggerConfig { @Bean public Docket createDocket() { // 獲取自定義配置 SwaggerProperties properties = this.customSwagger(); Docket docket = new Docket(DocumentationType.SWAGGER_2) // api 生成基本資訊 .apiInfo(this.buildApiInfo(properties)) // 開啟一個端點 .select() // 生成API 的包路徑 .apis(RequestHandlerSelectors.basePackage(properties.getApiBasePackage())) // 路徑選擇 .paths(PathSelectors.any()) .build(); return docket; } /** * 構建API 資訊方法,通過自定義的SwaggerProperties 轉化為 ApiInfo * 通過ApiInfoBuilder 構建一個api資訊。 * * @param properties 自定義資訊 * @return */ private ApiInfo buildApiInfo(SwaggerProperties properties) { return new ApiInfoBuilder() // 標題 .title(properties.getTitle()) // 描述 .description(properties.getDescription()) // 聯絡人資訊 .contact(new Contact(properties.getContactName(), properties.getContactUrl(), properties.getContactEmail())) // 版本資訊 .version(properties.getVersion()) .build(); } /** * 自定義實現配置資訊 * * @return */ public abstract SwaggerProperties customSwagger(); } ``` #### Admin Swagger Config 讓我們的子類繼承通用的父類,並且重寫`customSwagger` 自定義一個配置類。填寫一些 api 的基本資訊。即可。 > @EnableSwagger2 開啟Swagger 支援 > > SwaggerProperties 是自己定義的一個配置資訊類,使用者包裝如下的資訊。詳細見程式碼 ```java @Configuration @EnableSwagger2 public class AdminSwaggerConfig extends BaseSwaggerConfig { @Override public SwaggerProperties customSwagger() { return SwaggerProperties.builder() .title("mall-pro") .description("mall-pro 介面描述資訊") .apiBasePackage("xyz.chaobei.mall.controller") .contactName("mrc") .enableSecurity(false) .version("1.0") .build(); } } ``` #### 訪問Swagger 啟動我們的main() 方法。讓這個專案跑起來! 訪問:http://localhost:8080/swagger-ui.html > 基本的增刪改查,已經展現出來了。可以直接在這裡測試我們介面的連通性,真的特別方便。 ![image-20201013174756018](https://file.chaobei.xyz/image-20201013174756018.png_imagess) 測試這個一個新增的介面。 ![image-20201013175602425](https://file.chaobei.xyz/image-20201013175602425.png_imagess) 操作成功的返回資訊。狀態碼、以及提示語。 ![image-20201013175624540](https://file.chaobei.xyz/image-20201013175624540.png_imagess) ### 小結 學到這裡。你已經整合了一個基本的介面、並且測試通了介面的連通性。而且文件也不用自己手寫了,全部自動生成。 總結一下:我們學習和使用到了: - maven 子專案的搭建 - mybatis-plus 的整合 - mybatis-plus 自動填充功能的使用 - 邏輯刪除欄位的使用方式。 - 以及整合`swagger` 自動生成測試介面和 介面說明文件。 ### 碼雲開源 [https://gitee.com/mrc1999/mall-pro](https://gitee.com/mrc1999/mall-pro) ### 持續更新中,歡迎關注 ![](https://file.chaobei.xyz/blogs/banner_1591192617