• 【手摸手,带你搭建前后端分离商城系统】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 ,希望朋友们也跟着我一起,当然其他优秀的集成开发工具也是很牛逼的,反正用着顺手就行!

    创建一个新的项目、自定义项目名称,并且键入你自己的 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 {
    
    }
    

    开启偷懒模式

    能不能有一种东西,给我生成这种重复的东西,而我只关注逻辑呢?

    当然有了~

    上面示例的代码都是用工具生成的~ 总不能一个一个敲出来吧~

    学会偷懒其实也是一种好处。人类的发展不就是朝着偷懒的方向发展嘛

    程序猿小码/bye-crud-generate

    添加配置文件

    这已经是最后的几个步骤了。添加配置文件,主要是配置 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

    基本的增删改查,已经展现出来了。可以直接在这里测试我们接口的连通性,真的特别方便。

    image-20201013174756018

    测试这个一个添加的接口。

    image-20201013175602425

    操作成功的返回信息。状态码、以及提示语。

    image-20201013175624540

    小结

    学到这里。你已经整合了一个基本的接口、并且测试通了接口的连通性。而且文档也不用自己手写了,全部自动生成。

    总结一下:我们学习和使用到了:

    • maven 子项目的搭建
    • mybatis-plus 的整合
    • mybatis-plus 自动填充功能的使用
    • 逻辑删除字段的使用方式。
    • 以及整合swagger 自动生成测试接口和 接口说明文档。

    码云开源

    https://gitee.com/mrc1999/mall-pro

    持续更新中,欢迎关注

  • 相关阅读:
    xcode或者mac自带颜色器选择rgb格式
    -[UPAInitViewController startAPPay] in libUPAPayPlugin.a(UPAInitViewController.o)
    解决android studio设置版本号
    Android的Activity之间传对象的方法
    iOS图片目录批量复制到android图片目录
    shell批量转换iOS和Android图标
    activeandroid复制本地数据库问题总结
    第二次会议记录
    第一次会议记录
    MySql 创建函数 Error Code : 1418
  • 原文地址:https://www.cnblogs.com/ChromeT/p/13813369.html
Copyright © 2020-2023  润新知