美文网首页
项目要点记录

项目要点记录

作者: Ocean_e553 | 来源:发表于2022-02-21 15:22 被阅读0次

    一. 新建项目,创建UserController,增加test方法,能正常调用接口
    二. 集成mybatis,打通数据库连接

    1. 添加mybatis 和 mysql依赖

      <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.2.2</version>
      <dependency>
      
      <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
      </dependency>
      
    2. 在application.properties中配置连接信息

      spring.datasource.name=imooc_mall_datasource
      spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      spring.datasource.username=root
      spring.datasource.password=root12345
      
    3. 生成跟表格一一对应的pojo model(pojo每个字段都跟数据库完全对应) (准备工作)

    4. 编写 UserMapper 接口 和 UserMapper.xml, 并关联起来

      ·在Application上添加mapper扫描, 指定mapper接口的路径

      @SpringBootApplication
      @MapperScan(basePackages = {"com.lc.mall.model.dao"})
      public class MallApplication {
          public static void main(String[] args) {
              SpringApplication.run(MallApplication.class, args);
          }
      }
      

      ·在application.properties中指定 mapper.xml的路径

      mybatis.mapper-locations=classpath:mappers/*.xml
      

      ·每个mapper和对应的mapper.xml 使用同样的文件名, 且在mapper.xml中指定namespace,跟mapper接口绑定

      @Repository
      public interface UserMapper {
         User findById(Integer id);
      } 
      
      <?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.lc.mall.model.dao.UserMapper">
         <select id="findById" parameterType="int" resultType="com.lc.mall.model.pojo.User">
            select * from imooc_mall_user where id = #{id}
         </select>
      </mapper> 
      

    至此,实现mybatis的数据库打通。

    三.封装接口统一返回对象(ApiResponse), 自定义Exception, 添加全局异常处理类,定义错误枚举

     public class ApiRestResponse {
        private Integer status;
        private String message;
        private Object data;
    
        private static final Integer OK_CODE = 10000;
    
        private static final String OK_MSG = "SUCCESS";
    
        public ApiRestResponse(Integer status, String message) {
            this.status = status;
            this.message = message;
        }
    
        public static ApiRestResponse success(Object object) {
            ApiRestResponse resp = new ApiRestResponse(OK_CODE, OK_MSG);
            resp.setData(object);
            return resp;
        }
     }
    
    public class ImoocMallException extends RuntimeException {
      private Integer code;
      private String message;
    
      public ImoocMallException(Integer code, String message) {
          this.code = code;
          this.message = message;
      }
    
      public ImoocMallException(ImoocMallExceptionEnum exceptionEnum) {
          this.code = exceptionEnum.getCode();
          this.message = exceptionEnum.getMessage();
      }
    } 
    
    public enum ImoocMallExceptionEnum {
    
      NEED_USERNAME(10001, "用户名不能为空"),
      NEED_PASSWORD(10002, "密码不能为空"),
      PASSWORD_TOO_SHORT(10003, "密码长度不能小于8"),
      NAME_EXISTED(10004, "不允许重名"),
      INSERT_FAILED(10005, "插入失败, 请重试"),
      ;
    
    
      private Integer code;
      private String message;
    
      ImoocMallExceptionEnum(Integer code, String message) {
          this.code = code;
          this.message = message;
      }
    } 
    

    四.日志配置(集成log4j2)

    1. pom.xml中添加依赖
    <!-- 移除logback依赖, 后面新增log4j2依赖, 不然类名有冲突-->
    <exclusions>
      <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
    </exclusions>
    
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency> 
    
    1. log4j2配置文件 log4j2.xml
     <?xml version="1.0" encoding="UTF-8"?>
     <Configuration status="fatal">
        <Properties>
        <!--    指定日志保存路径-->
           <Property name="baseDir" value="/Users/luomeng/Desktop/Backend/java/images/"/>
        </Properties>
    
        <Appenders>
           <Console name="Console" target="SYSTEM_OUT">
              <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
              <ThresholdFilter level="info" onMatch="ACCEPT"
          onMismatch="DENY"/>
              <PatternLayout
          pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
           </Console>
    
      <!--debug级别日志文件输出-->
      <RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
        filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
        <!-- 过滤器 -->
        <Filters>
          <!-- 限制日志级别在debug及以上在info以下 -->
          <ThresholdFilter level="debug"/>
          <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
        </Filters>
        <!-- 日志格式 -->
        <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
        <!-- 策略 -->
        <Policies>
          <!-- 每隔一天转存 -->
          <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
          <!-- 文件大小 -->
          <SizeBasedTriggeringPolicy size="100 MB"/>
        </Policies>
      </RollingFile>
    
      <!-- info级别日志文件输出 -->
      <RollingFile name="info_appender" fileName="${baseDir}/info.log"
        filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
        <!-- 过滤器 -->
        <Filters>
          <!-- 限制日志级别在info及以上在error以下 -->
          <ThresholdFilter level="info"/>
          <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
        </Filters>
        <!-- 日志格式 -->
        <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
        <!-- 策略 -->
        <Policies>
          <!-- 每隔一天转存 -->
          <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
          <!-- 文件大小 -->
          <SizeBasedTriggeringPolicy size="100 MB"/>
        </Policies>
      </RollingFile>
    
      <!-- error级别日志文件输出 -->
      <RollingFile name="error_appender" fileName="${baseDir}/error.log"
        filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
        <!-- 过滤器 -->
        <Filters>
          <!-- 限制日志级别在error及以上 -->
          <ThresholdFilter level="error"/>
        </Filters>
        <!-- 日志格式 -->
        <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
        <Policies>
          <!-- 每隔一天转存 -->
          <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
          <!-- 文件大小 -->
          <SizeBasedTriggeringPolicy size="100 MB"/>
        </Policies>
      </RollingFile>
    </Appenders>
    <Loggers>
      <Root level="debug">
        <AppenderRef ref="Console"/>
    <!--      <AppenderRef ref="debug_appender"/>-->
        <AppenderRef ref="info_appender"/>
        <AppenderRef ref="error_appender"/>
      </Root>
    
    </Loggers>
    </Configuration>
    
    1. 在需要记录日志的地方获取logger并记录日志
    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    logger.error("Default Exception:", exception); 
    

    五. 集成swagger实现接口文档

    1. 在pom.xml中添加依赖
    <!--        swagger展示接口文档-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency> 
    
    1. 在Application上添加注解
    @SpringBootApplication
    @MapperScan(basePackages = {"com.lc.mall.model.dao"})
    @EnableSwagger2
    public class MallApplication {
        public static void main(String[] args) {
            SpringApplication.run(MallApplication.class, args);
        }
    } 
    
    1. 添加swagger配置类
    @Configuration
    public class SpringFoxConfig {
    
        //访问http://localhost:8083/swagger-ui.html可以看到API文档
        @Bean
        public Docket api() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.any())
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("慕慕生鲜")
                    .description("")
                    .termsOfServiceUrl("")
                    .build();
        }
    } 
    
    1. 运行项目后访问 http://localhost:8080/swagger-ui.html
    2. spring-boot 2.6.3 集成swagger时, 运行报错 Failed to start bean 'documentationPluginsBootstrapper';
      是因为版本冲突,
      解决方案是:springboot2.6.x之后将spring MVC默认路径匹配策略从ANT_PATH_MATCHER模式改为PATH_PATTERN_PARSER模式导致出错,解决方法是切换会原先的ANT_PATH_MATCHER模式
    spring.mvc.pathmatch.matching-strategy=ant_path_matcher 
    

    六. 不常修改的数据, 可以对接口做缓存

    1. 使用spring-boot内置缓存
      ·· 先在Application上打开缓存
    @SpringBootApplication
    @MapperScan(basePackages = {"com.lc.mall.model.dao"})
    @EnableSwagger2
    @EnableCaching
    public class MallApplication {
        public static void main(String[] args) {
            SpringApplication.run(MallApplication.class, args);
        }
    } 
    

    · 在需要缓存的方法上加上注解

    // CategoryServiceImpl 中
    @Cacheable(value = "listCategoryForCustomer")
    public List<CategoryVO> listCategoryForCustomer(Integer parendId) {
        List<CategoryVO> result = new ArrayList<>();
        findList(parendId, result);
        return result;
    } 
    
    1. 集成redis缓存
      · pom.xml中添加redis依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.6.3</version>
    </dependency> 
    

    · 在application.properties中配置redis连接信息

    spring.redis.host=localhost
    spring.redis.port=6379
    spring.redis.password= 
    

    · 增加缓存配置类

    @Configuration
    @EnableCaching
    public class CachingConfig {
    
        @Bean
        public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
    
            RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
            RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
            cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30));
    
            RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, cacheConfiguration);
            return redisCacheManager;
        }
    } 
    

    · 在Application中打开缓存 和 在需要缓存的地方添加注解, 配置跟使用内置缓存相同

    七. 使用aop记录所有请求的入参和返回值
    · 在pom.xml中添加aop依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency> 
    

    · 添加aop配置类

    @Aspect
    @Component
    public class WebLogAspect {
    
        private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
        @Pointcut("execution(public * com.lc.mall.controller.*.*(..))")
        public void webLog(){
    
        }
    
        @Before("webLog()")
        public void doBefore(JoinPoint joinpoint){
            //收到请求,记录请求的信息
            ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            log.info("URL:"+request.getRequestURL());
            log.info("HTTP_METHOD:"+request.getMethod());
            log.info("IP:"+request.getRemoteAddr());
            log.info("CLASS_METHOD:"+joinpoint.getSignature().getDeclaringTypeName()+"."+joinpoint.getSignature().getName());
            log.info("ARGS:"+ Arrays.toString(joinpoint.getArgs()));
        }
    
        @AfterReturning(returning = "res", pointcut = "webLog()")
        public void doAfter(Object res) throws JsonProcessingException {
            //处理完请求返回内容
            log.info("RESPONSE:"+ new ObjectMapper().writeValueAsString(res));
        }
    } 
    

    八. 查询功能增加分页功能
    · 增加pagehelper依赖 (注:版本需要跟mybatis版本兼容, 不然启动时会报错com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration)

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.1</version>
    </dependency> 
    

    · 在需要实现分页的地方调用

    public PageInfo listForAdmin(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize, "type, order_num");
        List<Category> categoryList = categoryMapper.selectList();
        PageInfo pageInfo = new PageInfo(categoryList);
        return pageInfo;
    } 
    

    九. 请求传入接口参数校验
    有的springboot版本自动集成了 spring-boot-starter-validation,但是有的版本需要自己添加依赖(springboot2.6.3就需要手动添加)

    1. 添加依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency> 
    
    1. 使用注解进行校验
    @ApiOperation("更新")
    @PostMapping("/update")
    public ApiRestResponse update(@RequestParam("signature") @NotNull(message = "签名信息不能为空") String signature, HttpSession session) {
    } 
    
    public class AddCategoryReq {
    
        @Size(min = 3, max = 8, message = "名称长度在3-8个字符")
        @NotNull(message = "名称不能为空")
        private String name;
        
        private Integer orderNum;
    
        private Integer parentId;
    
        @NotNull(message = "orderNum不能为null")
        @Max(value = 3)
        private Integer type;
    } 
    
    1. 当参数校验不通过时, 会抛出异常 MethodArgumentNotValidException, 需要在全局异常处理中进行处理
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        @ExceptionHandler(value = Exception.class)
        public ApiRestResponse handleException(Exception exception) {
            logger.error("Default Exception:", exception);
            return new ApiRestResponse(20000, "系统异常");
        }
    
        @ExceptionHandler(value = ImoocMallException.class)
        public ApiRestResponse handleMallException(ImoocMallException exception) {
            logger.error("ImoocMallException:", exception);
            return new ApiRestResponse(exception.getCode(), exception.getMessage());
        }
    
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ApiRestResponse handleValidException(MethodArgumentNotValidException e) {
            logger.error("MethodArgumentNotValidException:", e);
            return handleBindingResult(e.getBindingResult());
        }
    
        // 从异常提示中提取信息返回给前端
        private ApiRestResponse handleBindingResult(BindingResult result){
            //把异常处理为对外暴露的提示
            List<String> list = new ArrayList<>();
            if (result.hasErrors()) {
                List<ObjectError> allErrors = result.getAllErrors();
                for (int i = 0; i < allErrors.size(); i++) {
                    ObjectError objectError = allErrors.get(i);
                    String message = objectError.getDefaultMessage();
                    list.add(message);
                }
            }
            if (list.size() == 0){
                return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR);
            }
            return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR.getCode(), list.toString());
        }
    } 
    

    十. 过滤器使用
    1.编写过滤器 继承 Filter

    /**
     * 用户过滤器
     * @author Rex
     * @create 2021-02-09 13:52
     */
    public class UserFilter implements Filter {
        
        public static User currentUser;
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpSession session = request.getSession();
            currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
            if (currentUser == null) {
                PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
                out.write("{\n" +
                        "    \"status\": 10007,\n" +
                        "    \"msg\": \"NEED_LOGIN\",\n" +
                        "    \"data\": null\n" +
                        "}");
                out.flush();
                out.close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    } 
    
    1. 添加过滤器配置
    /**
     * User过滤器的配置
     * @author Rex
     * @create 2021-02-09 13:59
     */
    @Configuration
    public class UserFilterConfig {
    
        @Bean
        public UserFilter userFilter(){
            return new UserFilter();
        }
    
        @Bean(name = "userFilterConf")
        public FilterRegistrationBean userFilterConfig(){
            FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
            filterFilterRegistrationBean.setFilter(userFilter());
            filterFilterRegistrationBean.addUrlPatterns("/cart/*");
            filterFilterRegistrationBean.addUrlPatterns("/order/*");
            filterFilterRegistrationBean.setName("userFilterConfig");
            return filterFilterRegistrationBean;
        }
    } 
    

    一般每个过滤器只做一件事,需要多个过滤器时, 使用相同的步骤进行配置即可

    十一. 图片上传接口

    1. 定义接口
    file.upload.dir=/Users/luomeng/Desktop/Backend/java/images/
    file.upload.ip=127.0.0.1 
    
    @Component
    public class Constant {
        public static String FILE_UPLOAD_DIR;
    
        @Value("${file.upload.dir}")
        public void setFileUploadDir(String fileUploadDir) {
            FILE_UPLOAD_DIR = fileUploadDir;
        }
    } 
    
    /**
    * 上传文件, 保存文件到指定路径并返回图片url
    * */
    @PostMapping("/upload/file")
    public ApiRestResponse upload(HttpServletRequest httpRequest, @RequestParam("file") MultipartFile file) {
    
        String fileName = file.getOriginalFilename();
        String fileType = fileName.substring(fileName.lastIndexOf("."));
        String newFileName = UUID.randomUUID().toString() + fileType;
    
        File fileDir = new File(Constant.FILE_UPLOAD_DIR);
        File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
        if (!fileDir.exists()) {
            if (!fileDir.mkdir()) {
                throw new ImoocMallException(ImoocMallExceptionEnum.MKDIR_FAILED);
            }
        }
        try {
            // 文件写入
            file.transferTo(destFile);
        } catch (IOException e) {
            throw new ImoocMallException(ImoocMallExceptionEnum.FILE_UPLOAD_FAILED);
        }
    
        // 拼接url并返回
        try {
            String url = getHost(new URI(httpRequest.getRequestURL()+"")) + "/images/" + newFileName;
            return ApiRestResponse.success(url);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return ApiRestResponse.error(ImoocMallExceptionEnum.FILE_UPLOAD_FAILED);
        }
    }
    
    private URI getHost(URI uri){
        URI effectiveURI;
        try {
            effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), null,null, null);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            effectiveURI = null;
        }
        return effectiveURI;
    } 
    
    1. 增加配置,将返回的图片地址指定到本机图片存储的目录(配置后才能通过返回的图片链接访问图片)
    @Configuration
    public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/admin/**")
                    .addResourceLocations("classpath:/static/admin/");
            registry.addResourceHandler("/images/**")
                    .addResourceLocations("file:"+ Constant.FILE_UPLOAD_DIR);
            registry.addResourceHandler("swagger-ui.html").addResourceLocations(
                    "classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations(
                    "classpath:/META-INF/resources/webjars/");
    
    
        }
    } 
    

    十二. 当遇到查询结果与预期不符, 想查看执行的sql时,如下配置可打印出mybatis最终运行的sql语句

    在application.properties中配置mybatis

    mybatis.mapper-locations=classpath:mappers/*.xml
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl 
    

    十三. 项目中配置多环境(如:开发环境dev, 生产环境prod等)
    在application.properties同级目录中分别创建各个环境的配置文件,文件名格式:application
    -xx.properties, (如:application-dev.xml, application-prod.xml)

    在application.properties文件中指定使用环境

    spring.profiles.active=prod 
    

    一些坑:

    1. 当数据库的字段和pojo模型中的属性不一致时,需要在mapper.xml中指定映射关系
    <resultMap id="userMap" type="com.lc.mall.model.pojo.User">
       <result column="personalized_signature" property="personalizedSignature"></result>
       <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
       <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
    </resultMap>
    
    <select id="findById" parameterType="int" resultType="com.lc.mall.model.pojo.User" resultMap="userMap">
       select * from imooc_mall_user where id = #{id}
    </select> 
    
    1. 当像数据库插入数据时,需要同步获取自动生成的id主键时,使用 useGeneratedKeys="true" 和 keyProperty="id"同时使用
     <insert id="insert" parameterType="com.lc.mall.model.pojo.User" useGeneratedKeys="true" keyProperty="id">
          insert into imooc_mall_user (xxx, xxx,xxx) values (xxx,xxx,xxx)
     </insert> 
    

    相关文章

      网友评论

          本文标题:项目要点记录

          本文链接:https://www.haomeiwen.com/subject/axlilrtx.html