【手摸手,帶你搭建前后端分離商城系統】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
,希望朋友們也跟着我一起,當然其他優秀的集成開發工具也是很牛逼的,反正用着順手就行!
創建一個新的項目、自定義項目名稱,並且鍵入你自己的 group id
以及 artifactId
因為我們采用的是項目模塊化的實現,父類就只是一個空殼,一般定義項目里面需要的所有 依賴信息
以及 版本信息
等。引入我們所有下面的子項目都會用到的 公共依賴包
這些。
定義我們將要使用的 Spring-boot
版本,我們這是使用 2.1.3
<!-- springboot 2.3.0 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
划分模塊
這里依舊參考 mall
項目對於模塊的規划,簡單介紹一下。
- mall-admin 后台管理模塊
- mall-common 公共包模塊
- mall-mbg
mybatis-plus
生成 mapper、model 等 - mall-security 鑒權、授權模塊
暫時就先划分這么幾個吧!等后面用到了我們再划分即可。
父類定義版本和基本模塊
<packaging>pom</packaging>
父類作為一個空殼,其最主要的目的是模塊化的划分。它里面其實是不包含代碼的,所以將它的打包方式改為 pom
就可以將父類下的
src
目錄刪掉了。
首先定義幾個每個模塊都會使用到的依賴。比如 aop切面
test 測試模塊
等依賴信息。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 省略其他。。。 -->
</dependencies>
使用 dependencyManagement
使用這個標簽是為了我們依賴的 統一管理
。防止兩個模塊引用不同版本的依賴,而導致打包沖突或者運行沖突等問題。
最大的好處也在於:父類定義好需要使用的依賴后、子類引用無需版本號。
<!-- 變量定義,定義版本號 -->
<properties>
<java.version>1.8</java.version>
<mybatis.plus.version>3.3.2</mybatis.plus.version>
<hutool.version>5.4.0</hutool.version>
<mysql.connector.version>8.0.20</mysql.connector.version>
</properties>
<!-- 父類定義的所有的包管理、子類統一使用、而不用寫明版本號 -->
<dependencyManagement>
<dependencies>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!--Hutool Java工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--mysql 驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
到這里,我們基本的父類已經構建完成了,我們可以開始構建 模塊
了。
構建模塊
直接在項目上右鍵 new model
,創建一個新的模塊。
設計權限三張表
創建后台用戶表、用來存儲用戶信息。
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;
創建后台角色信息表,存儲角色信息。
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;
創建后台菜單權限表,用於存儲菜單關系。
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 架構設置
開始寫代碼吧
從上面的架構圖,我們已經可以寫出一個基本的CRUD Controller
包命名規則
一個合格的程序猿,寫的代碼不僅給人一種舒服的感覺。而且包名命名等也是一個可以學習的點。
mapper
當然就是mybatis mapper 放置的位置。model
一款ORM
框架對重要的就是:數據庫對象與java對象的映射。controller
接口api
的位置。pojo
放置一些入參類、包裝類等。config
當然就是放置一些配置類。
Controller
我們以
ums_admin
后台用戶表作為示例,其實這些都是可以生成的~ 具體看 開啟偷懶模式
- Controller 包含基本的
CRUD
接口。 Restful
風格接口信息,更加容易理解接口含義。Swagger
生成基本的API 文檔信息,以及測試接口。- 校驗參數完整性!
@Api(tags = "ApiUmsAdminController",description = "后台用戶")
@RestController
@RequestMapping("/umsAdmin")
@Validated
public class ApiUmsAdminController {
@Autowired
private UmsAdminService umsAdminService;
/**
* <p>查詢所有后台用戶
* <p>author: mrc
*
* @return xyz.chaobei.common.api.CommonResult
* @since 2020-10-12 11:18:42
**/
@ApiOperation("查詢所有后台用戶")
@GetMapping("/")
public CommonResult getAll() {
List<UmsAdminModel> allList = umsAdminService.findAll();
return CommonResult.success(allList);
}
/**
* <p>默認分頁請求后台用戶
* <p>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<UmsAdminModel> allList = umsAdminService.findPage(pageAO);
return CommonResult.success(allList);
}
/**
* <p>保存一個后台用戶
* <p>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);
}
/**
* <p>修改一個后台用戶
* <p>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);
}
/**
* <p>刪除一個后台用戶
* <p>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
說明參數注釋信息
@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 找不到用戶。。。】,全局捕獲后,返回給前端進行提示。
@Service
public class UmsAdminServiceimpl implements UmsAdminService {
@Autowired
private UmsAdminMapper umsAdminMapper;
@Override
public List<UmsAdminModel> findAll() {
return umsAdminMapper.selectList(null);
}
@Override
public Page<UmsAdminModel> 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 能力。
public interface UmsAdminMapper extends BaseMapper<UmsAdminModel> {
// 繼承mybatis-plus 獲得基礎crud
}
Mybatis-Plus Config
主要是配置 mybatis 掃描的mapper 所在的位置。以及開啟事務的支持。
@Configuration
@EnableTransactionManagement
@MapperScan({"xyz.chaobei.mall.dao","xyz.chaobei.mall.mapper"})
public class MyBatisPlusConfig {
}
開啟偷懶模式
能不能有一種東西,給我生成這種重復的東西,而我只關注邏輯呢?
當然有了~
上面示例的代碼都是用工具生成的~ 總不能一個一個敲出來吧~
學會偷懶其實也是一種好處。人類的發展不就是朝着偷懶的方向發展嘛
添加配置文件
這已經是最后的幾個步驟了。添加配置文件,主要是配置 mybatis-plus
mapper 所在的位置。
以及配置我們的邏輯刪除、自動填充這兩個很好用的功能。
配置邏輯刪除
https://baomidou.com/guide/logic-delete.html
邏輯刪除有什么好處呢?我覺得主要還是數據的完整性。上線以后、就算這條數據要被刪除,也只能是通過狀態隱藏起來,
並非真實刪除。
還有一個注意的點就是:既然配置了這個
status
,那么所有的表都應該有這個字段。
#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
需要填充,你可以參考官網再詳細一些。所以:你的每個表都應該包含這個填充字段
@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) {}
}
別忘了標識字段。
/**
* 創建時間
*/
@TableField(value = "`create_time`",fill = FieldFill.INSERT)
private Date createTime;
測試接口代碼
細心的朋友已經發現了。我們系統已經集成了
swagger
swagger
對於接口文檔的生成和測試,簡直完美。代碼寫好了,文檔自然而然的被生成。並且可以在頁面上測試接口通信
簡直完美啊!
整合Swagger
考慮到 swagger 通用的配置類可能被多個模塊所使用,所以我們首先建立一個 abstract class
讓子類重寫它的抽象方法。這樣就實現了一個通用的 swagger config
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 是自己定義的一個配置信息類,用戶包裝如下的信息。詳細見代碼
@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
基本的增刪改查,已經展現出來了。可以直接在這里測試我們接口的連通性,真的特別方便。
測試這個一個添加的接口。
操作成功的返回信息。狀態碼、以及提示語。
小結
學到這里。你已經整合了一個基本的接口、並且測試通了接口的連通性。而且文檔也不用自己手寫了,全部自動生成。
總結一下:我們學習和使用到了:
- maven 子項目的搭建
- mybatis-plus 的整合
- mybatis-plus 自動填充功能的使用
- 邏輯刪除字段的使用方式。
- 以及整合
swagger
自動生成測試接口和 接口說明文檔。
碼雲開源
https://gitee.com/mrc1999/mall-pro