Bootstrap

Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(二)项目实现-第一篇-后端项目框架搭建

本文参考自

Springboot3+微服务实战12306高性能售票系统 - 慕课网 (imooc.com)

本文是仿12306项目实战第(二)章——项目实现 的第一篇,详细讲解后端框架的搭建,手把手演示从0到1创建项目的过程

1.新建项目

注意:

  • 当新建不成功,可以试试把防火墙先关了
  • jdk选择17

在这里插入图片描述

  • 依赖选择

    devtool 热部署

    boot版本先随便选一个,后续改成3.0.0;cloud版本改成2022.0.0

在这里插入图片描述

在这里插入图片描述

  • 启动试试

在这里插入图片描述

2.初始化配置

  • 修改encode

在这里插入图片描述

在这里插入图片描述

  • 修改auto import

    (不用按Alt+Enter,会自动导入)

在这里插入图片描述

  • 热部署配置

在这里插入图片描述

在这里插入图片描述

3秒不动,会自动编译

在这里插入图片描述

在这里插入图片描述

此时,改动代码后,会自动编译重启

目前devtool会有重启两次的问题,可能是设计上的缺陷,它将代码的修改分成了 删除、修改class 和 新增class 两个操作,分别重启了一次

在这里插入图片描述

3.实现代码关联Git远程仓库

实际工作中,每完成一个小功能,就提交一次,写清楚注释,下班前,代码全部push到远程仓库,前提是代码不要报错

代码一定要放远程仓库,防止电脑损坏丢失。一般公司会用gitlib自己搭仓库

远程仓库需要配置ssh
远程地址选择ssh(选择https需要用户名密码)

演示IDEA操作

  • 启用git管理(相当于git init)

在这里插入图片描述

在这里插入图片描述

  • 配置项目git的提交用户名和邮箱

在这里插入图片描述

  • git add 和 git commit

    把Local Changes打开

在这里插入图片描述

git add

在这里插入图片描述

git commit

在这里插入图片描述

取消默认勾选的检查代码,提高速度

在这里插入图片描述

  • 创建远程仓库

    用gitee举例

    • 配置ssh

在这里插入图片描述

  • 新建仓库

在这里插入图片描述

  • 提交代码到远程仓库

    (已有本地仓库)

    git remote add origin 【远程git地址(ssh)git push -u origin "master"
    
    • git remote add <remote_name> <remote_url>:添加一个新的远程仓库。指定一个远程仓库的名称和 URL,将其添加到当前仓库中。
    • -u:指定后面的origin作为默认主机,后面就可以不加任何参数使用git push

在这里插入图片描述

4.新增member会员模块

  1. 在train模块下新建子模块——member

    • 将父pom的和剪切,移动到子pom

    • 创建启动类com.neilxu.train.member.config.MemberApplication

    • 增加测试控制器com.neilxu.train.member.controller.TestController

    • 增加配置类application.properties

      server.port=8001
      
    • 将父项目的src删除

在这里插入图片描述

  1. 启动

在这里插入图片描述

  1. 提交git

    每次改动都记得提交git保存记录

在这里插入图片描述

5.实现日志的相关配置

  1. 修改配置

    server.port=8001
    server.servlet.context-path=/member
    
  2. 修改增加启动日志

    package com.neilxu.train.member.config;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.core.env.Environment;
    
    @SpringBootApplication
    @ComponentScan("com.neilxu")
    public class MemberApplication {
    
        private static final Logger LOG = LoggerFactory.getLogger(MemberApplication.class);
    
        public static void main(String[] args) {
            SpringApplication app = new SpringApplication(MemberApplication.class);
            Environment env = app.run(args).getEnvironment();
            LOG.info("启动成功!!");
            LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"),env.getProperty("server.servlet.context-path"));
        }
    
    }
    
  3. 增加logback日志

    • 固定写法
    • 注意路径
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- 修改一下路径-->
        <property name="PATH" value="./log/member"></property>
    
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <!--            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>-->
                <Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>
            </encoder>
        </appender>
    
        <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${PATH}/trace.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>10MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <layout>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
            </layout>
        </appender>
    
        <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${PATH}/error.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>10MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <layout>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
            </layout>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <root level="ERROR">
            <appender-ref ref="ERROR_FILE" />
        </root>
    
        <root level="TRACE">
            <appender-ref ref="TRACE_FILE" />
        </root>
    
        <root level="INFO">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    
    
  4. 修改.gitignore文件,忽略日志文件夹

    log/
    

6.使用HTTP Client完成测试接口

新建http文件,使用快捷键gtr生成代码模板

在这里插入图片描述

7.增加AOP打印请求参数和返回结果

  1. 增加依赖

    父pom:

        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                
                <!-- 增加依赖 -->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                    <version>1.2.70</version>
                </dependency>
    
                <dependency>
                    <groupId>cn.hutool</groupId>
                    <artifactId>hutool-all</artifactId>
                    <version>5.6.3</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    

    子pom:

            <!--增加依赖-->
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
    
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
            </dependency>
    
  2. 增加切面类

    com.neilxu.train.member.aspect.LogAspect

    package com.neilxu.train.member.aspect;
    
    import cn.hutool.core.util.RandomUtil;
    import com.alibaba.fastjson.JSONObject;
    import com.alibaba.fastjson.support.spring.PropertyPreFilters;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import jakarta.servlet.http.HttpServletRequest;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.slf4j.MDC;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.multipart.MultipartFile;
    
    @Aspect
    @Component
    public class LogAspect {
        public LogAspect() {
            System.out.println("LogAspect");
        }
    
        private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
    
        /**
         * 定义一个切点
         */
        //任意返回类型,neilxu下任意包(项目名/模块名),任意Controller类下任意方法任意参数
        @Pointcut("execution(public * com.neilxu..*Controller.*(..))")
        public void controllerPointcut() {
        }
    
        @Before("controllerPointcut()")
        public void doBefore(JoinPoint joinPoint) {
    
            // 增加日志流水号
            MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));
    
            // 开始打印请求日志
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            Signature signature = joinPoint.getSignature();
            String name = signature.getName();
    
            // 打印请求信息
            LOG.info("------------- 开始 -------------");
            LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
            LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
            LOG.info("远程地址: {}", request.getRemoteAddr());
    
            // 打印请求参数
            Object[] args = joinPoint.getArgs();
            // LOG.info("请求参数: {}", JSONObject.toJSONString(args));
    
            // 排除特殊类型的参数,如文件类型
            Object[] arguments = new Object[args.length];
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof ServletRequest
                        || args[i] instanceof ServletResponse
                        || args[i] instanceof MultipartFile) {
                    continue;
                }
                arguments[i] = args[i];
            }
            // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等
            String[] excludeProperties = {"mobile"};
            PropertyPreFilters filters = new PropertyPreFilters();
            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
            excludefilter.addExcludes(excludeProperties);
            LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter));
        }
    
        @Around("controllerPointcut()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = proceedingJoinPoint.proceed();
            // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等
            String[] excludeProperties = {"mobile"};
            PropertyPreFilters filters = new PropertyPreFilters();
            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
            excludefilter.addExcludes(excludeProperties);
            LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
            LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
            return result;
        }
    
    }
    
    
  3. 重启服务

    加入新的依赖需要重启服务才会生效

  4. 测试

    调用http测试接口

在这里插入图片描述

8.增加通用模块common

  1. 新建common子模块

在这里插入图片描述

  1. 修改pom文件

    • 将member下的公用依赖(除了test和devtools)去除,移到common模块下

    • 将common作为依赖导入member模块,并在父模块管理版本

    • common pom文件

          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-aop</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>cn.hutool</groupId>
                  <artifactId>hutool-all</artifactId>
              </dependency>
      
          </dependencies>
      
    • member pom文件

          <dependencies>
      
              <dependency>
                  <groupId>com.neilxu</groupId>
                  <artifactId>common</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-devtools</artifactId>
                  <scope>runtime</scope>
                  <optional>true</optional>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
              </dependency>
          </dependencies>
      
    • train pom文件

      <dependency>
          <groupId>com.neilxu</groupId>
          <artifactId>common</artifactId>
          <version>0.0.1-SNAPSHOT</version>
      </dependency>
      
  2. 将aspect移到common模块

在这里插入图片描述

  1. 测试

在这里插入图片描述

9.增加网关模块

  1. 新增gateway模块

在这里插入图片描述

  1. 修改pom文件,增加gateway依赖

    gateway pom文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
    

在这里插入图片描述

gateway是基于netty,不需要引入springboot web依赖

  1. 增加启动类

    @SpringBootApplication
    @ComponentScan("com.neilxu")
    public class GatewayApplication {
    
        private static final Logger LOG = LoggerFactory.getLogger(GatewayApplication.class);
    
        public static void main(String[] args) {
            SpringApplication app = new SpringApplication(GatewayApplication.class);
            Environment env = app.run(args).getEnvironment();
            LOG.info("启动成功!!");
            LOG.info("网关地址: \thttp://127.0.0.1:{}", env.getProperty("server.port"));
        }
    
    }
    
  2. 增加配置文件和配置日志

    server.port=8000
    
    <property name="PATH" value="./log/gateway"></property>
    

在这里插入图片描述

  1. 启动服务

在这里插入图片描述

这里把多tab页的run面板,转移到了services面板

解决方式

.idea/workspace.xml 文件中 添加

<component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
</component>

重启服务

  1. 配置路由转发

    修改gateway 配置文件

    server.port=8000
    
    # 路由转发,将/member/...的请求转发了member模块
    spring.cloud.gateway.routes[0].id=member
    spring.cloud.gateway.routes[0].uri=http://127.0.0.1:8001
    #spring.cloud.gateway.routes[0].uri=lb://member
    spring.cloud.gateway.routes[0].predicates[0]=Path=/member/**
    

    yaml和properties互转在线工具

    在线yaml转properties-在线properties转yaml-ToYaml.com

    重启gateway服务

  2. 测试

    GET http://localhost:8000/member/hello
    Accept: application/json
    
    ###
    

在这里插入图片描述

生产发布时,只有gateway需要配置外网IP,其他模块都只开放对内网访问,外网访问不了,保证应用安全

  1. 给gateway增加日志

    配重VM参数

在这里插入图片描述

在这里插入图片描述

-Dreactor.netty.http.server.accessLogEnabled=true

重启gateway

测试

在这里插入图片描述

10.本地数据库的准备

  • 安装mysql8.0(本地/云数据库)

  • 重点:专库专用,忌用root

    • 新增数据库train_member

      先试用root连接本地mysql,再新建数据库

在这里插入图片描述

ci不区分大小写

> MySQL 8.0 表名大小写设置后不支持修改,请谨慎设置
  • 新增用户train_member,配置权限只能操作train_member数据库

在这里插入图片描述

在这里插入图片描述

  • 新建连接

在这里插入图片描述

在这里插入图片描述

11.阿里云RDS准备(云数据库)

这章节和上一章节二选一,本项目我自己使用的是本地数据库,所以这块不做详细介绍

12.使用IDEA配置数据库连接

按idea提示先下载好mysql8驱动

在这里插入图片描述

创建sql脚本并执行

在这里插入图片描述

drop table if exists `member`;
create table `member` (
  `id` bigint not null comment 'id',
  `mobile` varchar(11) comment '手机号',
  primary key (`id`),
  unique key `mobile_unique` (`mobile`)
) engine=innodb default charset=utf8mb4 comment='会员';

按快捷键 ctrl+shift+alt+v 能按原格式粘贴

为了简洁页面,后续执行sql语句用navicat工具

13.集成Mybatis

  1. 加入依赖

    修改common 的pom文件

    这里mybatis要升级到3.0.0,才支持和springboot3集成

    <!-- 集成mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    <!-- 集成mysql连接 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  2. 配置数据库连接

    # 数据库连接
    spring.datasource.url=jdbc:mysql://localhost/train_member?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
    spring.datasource.username=train_member
    spring.datasource.password=train_member
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
  3. 编写代码

    • 持久层

      com.neilxu.train.member.mapper.MemberMapper

      package com.neilxu.train.member.mapper;
      
      public interface MemberMapper {
          int count();
      }
      

      MemberMapper.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.neilxu.train.member.mapper.MemberMapper">
        <select id="count" resultType="int">
          select count(1) from member
        </select>
      </mapper>
      
    • 服务层

      com.neilxu.train.member.service.MemberService

      @Service
      public class MemberService {
      
          @Resource
          private MemberMapper memberMapper;
      
      
          public int count(){
              return memberMapper.count();
          }
      }
      
    • 控制层

      com.neilxu.train.member.controller.MemberController

      @RestController
      @RequestMapping("/member")
      public class MemberController {
      
          @Resource
          private MemberService memberService;
      
          @GetMapping("/count")
          public Integer count() {
              return memberService.count();
          }
      }
      
    • 修改启动类

      @MapperScan("com.neilxu.train.member.mapper")
      
    • 增加mybatis配置

      # mybatis xml路径
      mybatis.mapper-locations=classpath:/mapper/**/*.xml
      logging.level.com.neilxu.train.member.mapper=trace
      
    • 测试

      GET http://localhost:8000/member/member/count
      Accept: application/json
      
      ###
      

      member表手动加1行数据

在这里插入图片描述

在这里插入图片描述

14.集成Mybatis官方生成器

这里跟着课程使用了Mybaits官方的生成器(为了防止版本问题踩坑),实际项目中,我们也可以集成Mybaits Plus(目前已经支持springboot3,最新版是3.5.3)

  1. 创建generator 模块

在这里插入图片描述

  1. 配置插件

    generator的 pom文件

    <build>
        <plugins>
            <!-- mybatis generator 自动生成代码插件 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <!--<configurationFile>src/main/resources/generator-config-member.xml</configurationFile>-->
                    <configurationFile>src/main/resources/generator-config-business.xml</configurationFile>
                    <!--<configurationFile>src/main/resources/generator-config-batch.xml</configurationFile>-->
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.22</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    
  2. 编写生成规则xml

    generator-config-member.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
        <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
    
            <!-- 自动检查关键字,为关键字增加反引号 -->
            <property name="autoDelimitKeywords" value="true"/>
            <property name="beginningDelimiter" value="`"/>
            <property name="endingDelimiter" value="`"/>
    
            <!--覆盖生成XML文件-->
            <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
            <!-- 生成的实体类添加toString()方法 -->
            <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
    
            <!-- 不生成注释 -->
            <commentGenerator>
                <property name="suppressAllComments" value="true"/>
            </commentGenerator>
    
            <!-- 配置数据源,需要根据自己的项目修改 -->
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost/train_member?serverTimezone=Asia/Shanghai"
                            userId="train_member"
                            password="train_member">
            </jdbcConnection>
    
            <!-- domain类的位置 targetProject是相对pom.xml的路径-->
            <javaModelGenerator targetProject="../member/src/main/java"
                                targetPackage="com.neilxu.train.member.domain"/>
    
            <!-- mapper xml的位置 targetProject是相对pom.xml的路径 -->
            <sqlMapGenerator targetProject="../member/src/main/resources"
                             targetPackage="mapper"/>
    
            <!-- mapper类的位置 targetProject是相对pom.xml的路径 -->
            <javaClientGenerator targetProject="../member/src/main/java"
                                 targetPackage="com.neilxu.train.member.mapper"
                                 type="XMLMAPPER"/>
    
            <!--<table tableName="member" domainObjectName="Member"/>-->
            <!--<table tableName="passenger" domainObjectName="Passenger"/>-->
            <table tableName="member" domainObjectName="Member"/>
        </context>
    </generatorConfiguration>
    
    
  3. 执行代码生成器

在这里插入图片描述

生成四个文件:

在这里插入图片描述

在这里插入图片描述

  1. 修改service

        public int count(){
            return Math.toIntExact(memberMapper.countByExample(null));
        }
    
  2. 测试

在这里插入图片描述

15.完成会员注册接口的开发

  • 下载插件GeneratorAllSetter,用于快速创建一个对象的set方法

在这里插入图片描述

  • 编写service方法

        public long register(String mobile) {
    
            //先判断手机号是否已经被注册
            MemberExample memberExample = new MemberExample();
            memberExample.createCriteria().andMobileEqualTo(mobile);
            List<Member> list = memberMapper.selectByExample(memberExample);
            if (CollUtil.isNotEmpty(list)) {
    //            return list.get(0).getId();
                throw new RuntimeException("该手机号已被注册");
            }
    
            Member member = new Member();
            member.setId(System.currentTimeMillis());
            member.setMobile(mobile);
    
            memberMapper.insert(member);
            return member.getId();
    
        }
    
  • 编写controller

        @PostMapping("/register")
        public long register(String mobile) {
            return memberService.register(mobile);
        }
    
  • 测试

    快捷键ptrp 生成post模版

在这里插入图片描述

在这里插入图片描述

16.封装请求参数和返回结果

  • 封装请求参数

    这里引入下lombok依赖,提高效率

    父pom

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
    

    子pom

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    

    com.neilxu.train.req.MemberRegisterReq

    @Data
    public class MemberRegisterReq {
        private String mobile;
    }
    

    com.neilxu.train.member.service.MemberService

    public long register(MemberRegisterReq req) {
    
        String mobile = req.getMobile();
    
        //先判断手机号是否已经被注册
        MemberExample memberExample = new MemberExample();
        memberExample.createCriteria().andMobileEqualTo(mobile);
        List<Member> list = memberMapper.selectByExample(memberExample);
        if (CollUtil.isNotEmpty(list)) {
            //            return list.get(0).getId();
            throw new RuntimeException("该手机号已被注册");
        }
    
        Member member = new Member();
        member.setId(System.currentTimeMillis());
        member.setMobile(mobile);
    
        memberMapper.insert(member);
        return member.getId();
    
    }
    

    com.neilxu.train.member.controller.MemberController

    @PostMapping("/register")
    public long register(MemberRegisterReq req) {
        return memberService.register(req);
    }
    
  • 封装返回结果

    开发环境,先把日志忽略mobile的放开

    // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等
    String[] excludeProperties = {};
    

    com.neilxu.train.common.resp.CommonResp

    写法基本类似,直接复制来用

    package com.neilxu.train.common.resp;
    
    public class CommonResp<T> {
    
        /**
         * 业务上的成功或失败
         */
        private boolean success = true;
    
        /**
         * 返回信息
         */
        private String message;
    
        /**
         * 返回泛型数据,自定义类型
         */
        private T content;
    
        public CommonResp() {
        }
    
        public CommonResp(boolean success, String message, T content) {
            this.success = success;
            this.message = message;
            this.content = content;
        }
    
        public CommonResp(T content) {
            this.content = content;
        }
    
        public boolean getSuccess() {
            return success;
        }
    
        public void setSuccess(boolean success) {
            this.success = success;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getContent() {
            return content;
        }
    
        public void setContent(T content) {
            this.content = content;
        }
    
        @Override
        public String toString() {
            final StringBuffer sb = new StringBuffer("CommonResp{");
            sb.append("success=").append(success);
            sb.append(", message='").append(message).append('\'');
            sb.append(", content=").append(content);
            sb.append('}');
            return sb.toString();
        }
    }
    

    com.neilxu.train.member.controller.MemberController

    @GetMapping("/count")
    public CommonResp<Integer> count() {
        int count = memberService.count();
        CommonResp<Integer> commonResp = new CommonResp<>();
        commonResp.setContent(count);
        return commonResp;
    }
    
    @PostMapping("/register")
    public CommonResp<Long> register(MemberRegisterReq req) {
        long register =memberService.register(req);
        CommonResp<Long> commonResp = new CommonResp<>();
        commonResp.setContent(register);
        return commonResp;
    }
    
  • 测试

在这里插入图片描述

17.给项目增加统一异常处理

  • 增加统一异常处理器

    com.neilxu.train.common.controller.ControllerExceptionHandler

    package com.neilxu.train.common.controller;
    
    import com.neilxu.train.common.resp.CommonResp;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * 统一异常处理、数据预处理等
     */
    @ControllerAdvice
    public class ControllerExceptionHandler {
    
        private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
    
        /**
         * 所有异常统一处理
         * @param e
         * @return
         */
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        public CommonResp exceptionHandler(Exception e) throws Exception {
            CommonResp commonResp = new CommonResp();
            LOG.error("系统异常:", e);
            commonResp.setSuccess(false);
            // commonResp.setMessage("系统出现异常,请联系管理员");
            commonResp.setMessage(e.getMessage());
            return commonResp;
        }
    
    }
    
  • 测试

在这里插入图片描述

18.使用自定义异常处理异常业务

  • 添加自定义异常类

    自定义异常枚举类

    com.neilxu.train.common.exception.BusinessExceptionEnum

    package com.neilxu.train.common.exception;
    
    
    public enum BusinessExceptionEnum {
    
        MEMBER_MOBILE_EXIST("手机号已注册");
    
        private String desc;
    
        BusinessExceptionEnum(String desc) {
            this.desc = desc;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        @Override
        public String toString() {
            return "BusinessExceptionEnum{" +
                    "desc='" + desc + '\'' +
                    "} " + super.toString();
        }
    }
    

    自定义异常类

    com.neilxu.train.common.exception.BusinessException

    package com.neilxu.train.common.exception;
    
    public class BusinessException extends RuntimeException {
    
        private BusinessExceptionEnum e;
    
        public BusinessException(BusinessExceptionEnum e) {
            this.e = e;
        }
    
        public BusinessExceptionEnum getE() {
            return e;
        }
    
        public void setE(BusinessExceptionEnum e) {
            this.e = e;
        }
    
    }
    
  • 修改异常处理类

    com.neilxu.train.common.controller.ControllerExceptionHandler

    @ControllerAdvice
    public class ControllerExceptionHandler {
    
        private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
    
        /**
         * 所有异常统一处理
         * @param e
         * @return
         */
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        public CommonResp exceptionHandler(Exception e) {
            CommonResp commonResp = new CommonResp();
            LOG.error("系统异常:", e);
            commonResp.setSuccess(false);
            commonResp.setMessage("系统出现异常,请联系管理员");
            return commonResp;
        }
    
        /**
         * 业务异常统一处理
         * @param e
         * @return
         */
        @ExceptionHandler(value = BusinessException.class)
        @ResponseBody
        public CommonResp exceptionHandler(BusinessException e) {
            CommonResp commonResp = new CommonResp();
            LOG.error("业务异常:", e);
            commonResp.setSuccess(false);
            commonResp.setMessage(e.getE().getDesc());
            return commonResp;
        }
    
    }
    
  • 修改service

    com.neilxu.train.member.service.MemberService

    if (CollUtil.isNotEmpty(list)) {
        // return list.get(0).getId();
        throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST);
    }
    
  • 测试

在这里插入图片描述

  • 不写入堆栈信息,提高性能

    修改BusinessException.java

        /**
         * 不写入堆栈信息,提高性能
         */
        @Override
        public Throwable fillInStackTrace() {
            return this;
        }
    

    修改ControllerExceptionHandler.java

    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public CommonResp exceptionHandler(BusinessException e) {
        CommonResp commonResp = new CommonResp();
        LOG.error("业务异常:{}", e.getE().getDesc());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getE().getDesc());
        return commonResp;
    }
    

    测试

在这里插入图片描述

19.集成校验框架validation

  • 引入依赖

    common的 pom文件

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
  • 加注解

    com.neilxu.train.req.MemberRegisterReq

    @Data
    public class MemberRegisterReq {
        @NotBlank(message = "【手机号】不能为空")
        private String mobile;
    }
    

    com.neilxu.train.member.controller.MemberController

    @PostMapping("/register")
    public CommonResp<Long> register(@Valid MemberRegisterReq req) {
        long register =memberService.register(req);
        CommonResp<Long> commonResp = new CommonResp<>();
        commonResp.setContent(register);
        return commonResp;
    }
    

    重启member服务

  • 修改统一异常处理类

    com.neilxu.train.common.controller.ControllerExceptionHandler

    import org.springframework.validation.BindException;
    
    /**
     * 校验异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public CommonResp exceptionHandler(BindException e) {
        CommonResp commonResp = new CommonResp();
        LOG.error("校验异常:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return commonResp;
    }
    
  • 测试

在这里插入图片描述

在这里插入图片描述

20.详解雪花算法

生成ID:

自增ID不适合分布式数据库,分表分库场景。适合小型项目

UUID是无序的,会影响索引效率

因此,推荐雪花算法生成ID

在这里插入图片描述

雪花算法问题

  • 数据中心,机器ID怎么设置

    可在启动时,用IP或MAC到数据库中申请一个不重复的ID

  • 时钟回拨

    一个应用一般是多节点,可停止节点,等时间过了,再启动

实现

common 增加 雪花算法工具类

com.neilxu.train.common.util.SnowUtil

package com.neilxu.train.common.util;

import cn.hutool.core.util.IdUtil;

/**
 * 封装hutool雪花算法
 */
public class SnowUtil {

    private static long dataCenterId = 1;  //数据中心
    private static long workerId = 1;     //机器标识

    public static long getSnowflakeNextId() {
        return IdUtil.getSnowflake(workerId, dataCenterId).nextId();
    }

    public static String getSnowflakeNextIdStr() {
        return IdUtil.getSnowflake(workerId, dataCenterId).nextIdStr();
    }
}

修改service

public long register(MemberRegisterReq req) {
    String mobile = req.getMobile();
    MemberExample memberExample = new MemberExample();
    memberExample.createCriteria().andMobileEqualTo(mobile);
    List<Member> list = memberMapper.selectByExample(memberExample);

    if (CollUtil.isNotEmpty(list)) {
        // return list.get(0).getId();
        throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST);
    }

    Member member = new Member();
    member.setId(SnowUtil.getSnowflakeNextId());
    member.setMobile(mobile);
    memberMapper.insert(member);
    return member.getId();
}

测试

在这里插入图片描述

;