【手摸手,带你搭建前后端分离商城系统】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