zoukankan      html  css  js  c++  java
  • 【手摸手,带你搭建前后端分离商城系统】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

    持续更新中,欢迎关注

  • 相关阅读:
    Junit单元测试
    win7的6个网络命令
    WOJ1024 (POJ1985+POJ2631) Exploration 树/BFS
    WOJ1022 Competition of Programming 贪心 WOJ1023 Division dp
    woj1019 Curriculum Schedule 输入输出 woj1020 Adjacent Difference 排序
    woj1018(HDU4384)KING KONG 循环群
    woj1016 cherry blossom woj1017 Billiard ball 几何
    woj1013 Barcelet 字符串 woj1014 Doraemon's Flashlight 几何
    woj1012 Thingk and Count DP好题
    woj1010 alternate sum 数学 woj1011 Finding Teamates 数学
  • 原文地址:https://www.cnblogs.com/ChromeT/p/13813369.html
Copyright © 2011-2022 走看看