美文网首页
项目要点记录

项目要点记录

作者: 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> 

相关文章

  • 项目要点记录

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

  • 前端导出后台文件流

    项目需求:实现文件下载的功能,也即是:获取数据流,记录一下 配置要点 responseType: 'blob', ...

  • №22 11.2 识别风险

    11.2 识别风险 基本概念:识别单个项目风险以及整体项目风险的来源,并记录风险特征的过程。 过程要点: 1、启动...

  • 值得记录的 (七)- 微信小程序 globalData / r

    继续总结在小程序项目开发之中认为值得去记录的要点。本篇包括 globalData、wx.reLaunch 和 bi...

  • pymongo 亿级数据查询技术总结之一

    序言 这么多年来做过好几个使用mongodb的项目, 这里主要记录下大数据使用上的一些技巧和要点. 在公司项目我用...

  • 需求获取-需求记录技术

    需求记录技术包含:任务卡片、场景说明、用户故事和Volere白卡。 任务卡片的主要内容和要点如下 项目内容说明任务...

  • react 项目要点

    ——————————按需加载 项目第一步 : 框架 / 路由 先创建 pages 文件夹, 并且给每个页面加文件夹...

  • vue项目要点

    安装 node npm webpack环境node npmwebpack(本地和全局) 建立 vue -cli 脚...

  • VDS项目要点

  • №05 5.1 规划范围管理

    5.1 规划范围管理 基本概念:为记录如何定义、确认和控制项目范围及产品范围,而创建范围管理计划的过程。 过程要点...

网友评论

      本文标题:项目要点记录

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