项目前置配置 IDEA设置热部署 2022版本IDEA配置参考博客 
step1:  setting->Compiler
step2: setting-Advance Settings
实现代码关联远程git仓库 实际场景中,完成一个小功能应当及时提交远程仓库
vcs->enable Version Control:相当于git init
IDEA右上角出现git相关的功能按钮
左下角git可以查看对应的信息,比如日志、未提交的文件等
在Terminal下关联自己的github`执行以下操作
1 2 3 4 5 6 7 8 9 10 11 12 # 关联自己的github(以前设置后无需重复设置,ssh key免登录) git config --global user.name xxx git config --global user.email xxx # 将其与远程仓库关联起来,其中origin是别名 git remote add origin 远程github地址(注意是ssh形式地址) # 提交至远程仓库 git push -u  origin 分支名 # 删除关联远程仓库 git remote remove origin # 查看关联远程仓库列表 git remote -v 
新建子模块 整个project由不同的子Module组成,project下的pom.xml只做模块管理
整个项目目录如下:
日志的相关配置 项目启动信息配置 改写启动类
1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootApplication 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:{}" ,env.getProperty("server.port" ));     } } 
banner.txt在线生成工具 
项目运行日志 在resource目录下新建日志配置文件loggback-spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?xml version="1.0"  encoding="UTF-8" ?> <configuration >          <property  name ="PATH"  value ="./log/memeber" > </property >      <appender  name ="STDOUT"  class ="ch.qos.logback.core.ConsoleAppender" >          <encoder >              <Pattern > %d{hh: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 > 
使用Http Client完成测试接口 IDEA自带Http Clinet插件,只要新建.http文件,即可发起http请求
增加AOP打印请求参数和返回结果 AOP和Interceptor都可以实现此功能,但是Interceptor只能处理Controller层的处理结果,
在memeber模块下新建AOP打印日志类aspect.LogAspect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package  com.bang.train.member.aspect;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.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("Common LogAspect" );     }     private  final  static  Logger  LOG  =  LoggerFactory.getLogger(LogAspect.class);          @Pointcut("execution(public * com.bang..*Controller.*(..))")      public  void  controllerPointcut ()  {     }     @Before("controllerPointcut()")      public  void  doBefore (JoinPoint joinPoint)  {                  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();                           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 = {};         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 = {};         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;     } } 
新建公共子模块common 微服务项目一般存在多个模块,每个模块对应一个服务,负责某一项功能,各个模块可能存在许多公共的代码和相同的依赖,此时为了减小代码冗余和代码管理和修改,我们在项目下新建一个子模块common
将公共代码放在此模块下
比如:工具类、拦截器、AOP、常量、枚举类、公共配置等 
 
 
将公共的依赖包放在此模块下的pom文件中
根目录下的pom文件负责依赖包的版本管理 
公共模块下的pom文件负责管理需要导入的包 
 
 
 
比如:上述AOP实现打印请求和返回结果的日志代码就可以移除到该模块下,但要注意,此时应该修改memeber模块下启动类的扫描范围,即@ComponentScan("com.bang.train.*")
增加公共模块后的项目目录
新建网关模块 网关模块主要用于:路由转发、请求校验
网关模块的配置文件application.yaml
1 2 3 4 5 6 7 8 9 10 server:   port:  8000  spring:   cloud:      gateway:        routes:          -  id:  memeber            uri:  http://127.0.0.1:8001             predicates:              -  Path=/member/**  
本地数据库的构建 对于各个项目而言,最好能够做到配置专库专用 ,对于一个项目,新建对应数据库的同时,创建一个专门的用户,将该用户的权限局限于对本项目对应数据库的增删改查,避免影响服务器中其他数据库里的数据。
集成Mybatis持久层框架 引入相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency >     <groupId > org.mybatis.spring.boot</groupId >      <artifactId > mybatis-spring-boot-starter</artifactId >      <version > 3.0.0</version >  </dependency > <dependency >     <groupId > mysql</groupId >      <artifactId > mysql-connector-java</artifactId >      <version > 5.1.47</version >  </dependency > 
配置数据库连接
在application.yaml配置文件中进行设置
集成Mybatis官方生成器 利用mybatis框架,需要:编写持久层接口->编写对应的mapper.xml文件(需要手动编写对应的SQL语句)
以上过程需要耗费较多经历,为简化开发可以使用以下两种替代方案
Mybatis-Plus第三方框架Mybatis+官方生成器 
这里我们采用第二种方案Mybatis+官方生成器
使用Mybatis官方生成器步骤
新建一个新的maven项目generator
在generator项目的pom文件中引入mybatis generator自动生成代码插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <build >     <plugins >                   <plugin >              <groupId > org.mybatis.generator</groupId >              <artifactId > mybatis-generator-maven-plugin</artifactId >              <version > 1.4.0</version >              <configuration >                                                    <configurationFile > src/main/resources/generator-config-business.xml</configurationFile >                                   <overwrite > true</overwrite >                  <verbose > true</verbose >              </configuration >              <dependencies >                  <dependency >                      <groupId > mysql</groupId >                      <artifactId > mysql-connector-java</artifactId >                      <version > 5.1.47</version >                  </dependency >              </dependencies >          </plugin >      </plugins >  </build > 
编写对应的配置文件src/main/resources/generator-config-member.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?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 ="`" />                   <plugin  type ="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"  />                   <plugin  type ="org.mybatis.generator.plugins.ToStringPlugin" />                   <commentGenerator >              <property  name ="suppressAllComments"  value ="true" />          </commentGenerator >                   <jdbcConnection  driverClass ="com.mysql.jdbc.Driver"                           connectionURL ="jdbc:mysql://localhost:3306/train_member?useUnicode=true& characterEncoding=utf8& useSSL=false"                          userId ="train_member"                          password ="wu123456" >         </jdbcConnection >                   <javaModelGenerator  targetProject ="../member/src/main/java"                               targetPackage ="com.bang.train.member.domain" />                  <sqlMapGenerator  targetProject ="../member/src/main/resources"                            targetPackage ="mapper" />                  <javaClientGenerator  targetProject ="../member/src/main/java"                                targetPackage ="com.bang.train.member.mapper"                               type ="XMLMAPPER" />         <table  tableName ="member"  domainObjectName ="Member" />               </context >  </generatorConfiguration > 
点击mybatis generator对应maven插件,会生成对应的代码文件
生成的代码文件
com.bang.train.member.domain.Member:数据库train_member的member表对应的java实体类(PO)com.bang.train.member.domain.MemberExample:组装SQL语句中where后面的条件对应的实体类;条件构建器,用于构建SQL语句中的各种条件com.bang.train.member.mapper.MemberMapper:持久层对应的接口src/main/resources/mapper/memberMapper.xml:对应的mapper.xml文件,里面含有各种SQL语句 
注意:
以上四个文件一定不要去动,每次重新店家genartor maven插件,这四个文件都会被覆盖重写;如果官方生成器对应插件无法满足项目需求,自定义的代码应编写在新的文件里,千万不要直接在这四个文件后面追加。
 
会员注册接口开发 业务层 在com.bang.train.member.IMemberService接口下新建抽象方法register
1 2 3 4 5 6 long  register (String mobile) ;
在com.bang.train.member.MemberServiceImpl类下实现对应抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override public  long  register (String mobile)  {         MemberExample  memberExample  =  new  MemberExample ();     memberExample.createCriteria().andMobileEqualTo(mobile);     List<Member> memberList = memberMapper.selectByExample(memberExample);     if (!CollUtil.isEmpty(memberList)){         throw  new  RuntimeException ("手机号已被注册" );     }     Member  member  =  new  Member ();     member.setId(System.currentTimeMillis());     member.setMobile(mobile);     memberMapper.insert(member);     return  member.getId(); } 
控制层 1 2 3 4 @PostMapping("/register") public  long  register (String mobile) {    return  memberService.register(mobile); } 
编写http文件利用Http Client进行测试 1 2 3 4 POST http://localhost:8001/member/register Content-Type :  application/x-www-form-urlencodedmobile=15823209537 
封装请求参数和结果(此模块的代码个人认为没有电脑商城项目好)) 封装请求参数 对于每个功能模块,将对应的请求参数封装成一个实体类,注意实体类的属性名与请求参数名要一致,这样前端请求会自动映射到实体类对应属性值
新建member模块下用户注册对应的请求参数实体类com.bang.train.member.req.MemberRegReq
1 2 3 4 5 6 @Data @AllArgsConstructor @NoArgsConstructor public  class  MemberRegReq  {    String mobile; } 
封装响应结果 响应结果包含三大基本信息:响应状态、响应状态描述信息、响应数据
在common模块新建公共响应实体类com.bang.train.common.resp.CommonResp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Data @NoArgsConstructor @AllArgsConstructor public  class  CommonResp <T>{         public  boolean  success=true ;          public  String message;          public  T content;          public  CommonResp (T content) {         this .content=content;     } } 
对应控制层代码修改 1 2 3 4 5 6 @PostMapping("/register") public  CommonResp<Long> register (MemberRegReq req) {    CommonResp<Long> commResp = new  CommonResp <>();     commResp.setContent(memberService.register(req));     return  commResp; } 
统一异常处理(此模块的代码个人认为没有电脑商城项目好) 业务层根据业务逻辑和执行结果,会向上层抛出各种类型异常,控制层需要对异常进行处理,直接将异常抛给前端不友好,需要针对异常,转换成统一的响应结果数据格式,所以需要构建统一异常处理类,借助于Spring的@ExceptionHandler注解来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ControllerAdvice public  class  ControllerExceptionHandler  {    private  static  final  Logger  LOG  =  LoggerFactory.getLogger(ControllerExceptionHandler.class);     @ExceptionHandler(Exception.class)      @ResponseBody      public  CommonResp<Void> handleException (Exception e) {         CommonResp<Void> commResp = new  CommonResp <>();         commResp.setSuccess(false );         commResp.setMessage(e.getMessage());         return  commResp;     } } 
自定义异常 根据业务层的业务逻辑,自定义对应的异常类,可以考虑利用枚举类进行异常的管理
定义枚举类,统一管理自定义异常 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package  com.bang.train.common.exception;public  enum   BusinessExceptionEnum  {    MEMBER_EXIST_ERROR("手机号已注册" );     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 + '\''  +                 '}' ;     } } 
自定义业务异常类 
1 2 3 4 5 6 7 8 9 10 11 12 package  com.bang.train.common.exception;import  lombok.Data;@Data public  class  BusinessException  extends  RuntimeException {    public  BusinessExceptionEnum E;     public  BusinessException (BusinessExceptionEnum E)  {         this .E = E;     } } 
修改统一的异常处理模块 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  com.bang.train.common.controller;import  com.bang.train.common.exception.BusinessException;import  com.bang.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);          @ExceptionHandler(Exception.class)      @ResponseBody      public  CommonResp<Void> handleException1 (Exception e) {         CommonResp<Void> commResp = new  CommonResp <>();         commResp.setSuccess(false );         commResp.setMessage("未知类型异常,请联系管理员" );         return  commResp;     }          @ExceptionHandler(BusinessException.class)      @ResponseBody      public  CommonResp<Void> handleException2 (BusinessException e) {         CommonResp<Void> commResp = new  CommonResp <>();         commResp.setSuccess(false );         commResp.setMessage(e.getE().getDesc());         return  commResp;     } } 
集成校验框架Validation 在实际生产环境中,大多数情况下需要对用户的输入参数进行校验,比如校验输入是否有特殊字符、手机号位数是否正确等;当然,输入的校验也可在前端进行
校验框架Validation的使用步骤
引入对应的pom依赖 
1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-validation</artifactId >  </dependency > 
通过注解方式进行参数校验 
在member模块中注册功能对应的请求参数实体类进行修改
1 2 3 4 5 6 7 8 @Data @AllArgsConstructor @NoArgsConstructor public  class  MemberRegReq  {         @NotBlank(message = "【手机号】不能为空")      String mobile; } 
在注册功能对应controller类的请求处理方法上加上注解@Valid让校验功能起效
1 2 3 4 5 6 @PostMapping("/register") public  CommonResp<Long> register (@Valid  MemberRegReq req) {    CommonResp<Long> commResp = new  CommonResp <>();     commResp.setContent(memberService.register(req));     return  commResp; } 
新增校验异常处理代码 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ExceptionHandler(Exception.class) @ResponseBody public  CommonResp<Void> handleException1 (Exception e) {    CommonResp<Void> commResp = new  CommonResp <>();     commResp.setSuccess(false );     if (e instanceof  BindException){         BindException  be  =  (BindException) e;                  String  message  =  be.getBindingResult().getAllErrors().get(0 ).getDefaultMessage();         commResp.setMessage(message);         LOG.error("校验异常:{}" ,message);     }else {         commResp.setMessage("未知类型异常,请联系管理员" );         LOG.error("未知类型异常,请联系管理员" );     }     return  commResp; } 
雪花算法 member模块注册功能业务层代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public  long  register (MemberRegReq req)  {    String  mobile  =  req.getMobile();          MemberExample  memberExample  =  new  MemberExample ();     memberExample.createCriteria().andMobileEqualTo(mobile);     List<Member> memberList = memberMapper.selectByExample(memberExample);     if (!CollUtil.isEmpty(memberList)){         throw  new  BusinessException (BusinessExceptionEnum.MEMBER_EXIST_ERROR);     }     Member  member  =  new  Member ();     member.setId(System.currentTimeMillis());     member.setMobile(mobile);     memberMapper.insert(member);     return  member.getId(); } 
目前,新注册用户的ID是用当前时间戳来表示,在高并发场景下存在非唯一性问题,因为同一时刻存在大量请求
目前的可采取的其他方法及其对应的问题
更改service层注册功能代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public  long  register (MemberRegReq req)  {    String  mobile  =  req.getMobile();          MemberExample  memberExample  =  new  MemberExample ();     memberExample.createCriteria().andMobileEqualTo(mobile);     List<Member> memberList = memberMapper.selectByExample(memberExample);     if (!CollUtil.isEmpty(memberList)){         throw  new  BusinessException (BusinessExceptionEnum.MEMBER_EXIST_ERROR);     }     Member  member  =  new  Member ();          member.setId(SnowUtil.getSnowflakeId());     member.setMobile(mobile);     memberMapper.insert(member);     return  member.getId(); }