项目小技巧

1、配置文件总结(更新中)

# 服务端口
server:
  port: 8001

# 服务名
spring:
  application:
    name: service-edu
  # 环境设置
  profiles:
    active: dev
    
  servlet:
    multipart:
      # 最大上传单个文件大小:默认1M
      max-file-size: 1024MB
      # 最大置总上传的数据大小 :默认10M
      max-request-size: 1024MB
      
  # MySQL数据库连接
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
    username: root
    password: root
    
  #返回json的全局时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    
  # nacos服务地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

# mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #配置mapper xml文件的路径
  mapper-locations: classpath:com/cqupt/mapper/xml/*.xml

#阿里云 OSS
#不同的服务器,地址不同
aliyun:
  oss:
    file:
      endpoint: oss-cn-chengdu.aliyuncs.com
      keyid: LTAI4GKsV94rbzAC9wApMQpg
      keysecret: 0AW5S5b1UqhylFCv7isypbBGwTnFgR
      bucketname: edu-cq
 vod:
    file:
      keyid: LTAI4GKsV94rbzAC9wApMQpg
      keysecret: 0AW5S5b1UqhylFCv7isypbBGwTnFgR

#开启熔断机制
feign:
  hystrix:
    enabled: true
# 设置hystrix超时时间,默认1000ms
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000

2、创建时间、更新时间自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用 Mybatis Plus 的自动填充功能,完成这些字段的赋值工作:

  1. 数据库表中添加自动填充字段

    在User表中添加datetime类型的新的字段 create_timeupdate_time

  2. 实体上添加注解

    @Data
    public class User {
          
          
        
        @TableField(fill = FieldFill.INSERT)
        private Date createTime;
        
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Date updateTime;
    }
    
  3. 实现元对象处理器接口

    注意:不要忘记添加 @Component 注解

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
          
          
        @Override
        public void insertFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
        @Override
        public void updateFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    

3、乐观锁

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。

  1. 数据库中添加version字段

  2. 实体类添加version字段,并添加 @Version 注解

    @Version
    @TableField(fill = FieldFill.INSERT)    //为了初始化
    private Integer version;
    
  3. 元对象处理器接口添加version的insert默认值

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
          
          
    
        @Override
        public void insertFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("updateTime", new Date(), metaObject);
    
            this.setFieldValByName("version", 1, metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    
  4. 在 MpConfig 中注册 Bean

    @MapperScan("com.cqupt.mybatis_plus.mapper")
    @Configuration
    public class MpConfig {
          
          
        /**
         * 乐观锁插件
         */
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
          
          
            return new OptimisticLockerInterceptor();
        }
    }
    
  5. 测试乐观锁可以修改成功

    @Test
        public void update() {
          
          
            //  先查询后修改
            User user = userMapper.selectById(10L);
            user.setName("小明");
            userMapper.updateById(user);
        }
    

4、Mp分页

Mybatis Plus自带分页插件,只要简单的配置即可实现分页功能

  1. 在 MpConfig 中注册 Bean

    @MapperScan("com.cqupt.mybatis_plus.mapper")
    @Configuration
    public class MpConfig {
          
          
        /**
         * 乐观锁插件
         */
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
          
          
            return new OptimisticLockerInterceptor();
        }
        /**
        * 分页插件
        */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
          
          
            return new PaginationInterceptor();
        }
    }
    
  2. 测试TestPage分页

    @Test
        public void TestPage() {
          
          
            Page<User> page = new Page<>(1, 3);
            userMapper.selectPage(page, null);
            page.getRecords().forEach(System.out::println);
            System.out.println(page.getCurrent());
            System.out.println(page.getPages());
            System.out.println(page.getSize());
            System.out.println(page.getTotal());
            System.out.println(page.hasNext());
            System.out.println(page.hasPrevious());
        }
    

5、逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
  1. 数据库中添加 deleted字段

  2. 实体类添加deleted 字段 和 @TableLogic 注解

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
    @Version
    private Integer version;
    
    @TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;
    
  3. 元对象处理器接口添加deleted的insert默认值

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
          
          
    
        @Override
        public void insertFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("updateTime", new Date(), metaObject);
    
            this.setFieldValByName("version", 1, metaObject);
    
            this.setFieldValByName("deleted", 0, metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
          
          
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    
  4. 在 MpConfig 中注册 Bean

    @MapperScan("com.cqupt.mybatis_plus.mapper")
    @Configuration
    public class MpConfig {
          
          
        /**
         * 乐观锁插件
         */
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
          
          
            return new OptimisticLockerInterceptor();
        }
        /**
        * 分页插件
        */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
          
          
            return new PaginationInterceptor();
        }
    
        /**
         * 逻辑删除插件
         */
        @Bean
        public ISqlInjector sqlInjector() {
          
          
            return new LogicSqlInjector();
        }
    }
    
  5. 测试

  • 测试后发现,数据并没有被删除,deleted字段的值由0变成了1
  • 测试后分析打印的sql语句,是一条update
  • 注意:被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
@Test
public void testLogicDelete() {
    
    
    userMapper.deleteBatchIds(Arrays.asList(1,2,3));
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

在这里插入图片描述

  • 查询操作也会自动添加逻辑删除字段的判断
    在这里插入图片描述

6、性能分析

性能分析拦截器,用于输出每条 SQL 语句及其执行时间
SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题

参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。
参数:format: SQL是否格式化,默认false

/**
 * SQL 执行性能分析插件
 * 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
 */
@Bean
@Profile({
    
    "dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
    
    
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(500);//ms,超过此处设置的ms则sql不执行
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}

7、代码生成器

在test/java目录下创建包com.atguigu.eduservice,创建代码生成器:CodeGenerator.java

package com.cqupt.generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @author
 * @since 2018/12/13
 */
public class CodeGenerator {
    
    

    @Test
    public void run() {
    
    

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("D:\\IdeaProject\\IdeaProject\\guli_parent\\service\\service-edu" + "/src/main/java");

        gc.setAuthor("Zhy");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖

        gc.setServiceName("%sService");	//去掉Service接口的首字母I

        gc.setIdType(IdType.ID_WORKER_STR); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("eduservice"); //模块名
        pc.setParent("com.cqupt");

        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("edu_subject");

        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}

8、统一异常处理

我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处
理。

1、在service-base中创建统一异常处理类GlobalExceptionHandler.java:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
    
    
        e.printStackTrace();
        return R.error().message("服务器发生了错误");
    }
}

2、GlobalExceptionHandler.java中添加自定义异常

@ControllerAdvice
public class GlobalExceptionHandler {
    
    

    /*
    * 全局异常
    * */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
    
    
        e.printStackTrace();
        return R.error().message("服务器发生了错误!");
    }

    /*
     * 自定义异常
     * */
    @ExceptionHandler(GuliException.class)
    @ResponseBody
    public R error(GuliException e) {
    
    
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }
}

9、日志

日志记录器(Logger)的行为是分等级的。如下表所示:

分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别

# 设置日志级别
logging.level.root=WARN
  1. 配置logback日志:resources 中创建 logback-spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="10 seconds">
        <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设
        置为WARN,则低于WARN的信息都不会输出 -->
        <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值
        为true -->
        <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认
        单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
        <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查
        看logback运行状态。默认值为false。 -->
        <contextName>logback</contextName>
        <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入
        到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
        <property name="log.path" value="D:/IdeaProject/IdeaProject/guli_parent/edu" />
        <!-- 彩色日志 -->
        <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
        <!-- magenta:洋红 -->
        <!-- boldMagenta:粗红-->
        <!-- cyan:青色 -->
        <!-- white:白色 -->
        <!-- magenta:洋红 -->
        <property name="CONSOLE_LOG_PATTERN"
                  value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level)
    |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
        <!--输出到控制台-->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或
            等于此级别的日志信息-->
            <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日
            志,也不会被输出 -->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
                <!-- 设置字符集 -->
                <charset>UTF-8</charset>
            </encoder>
        </appender>
        <!--输出到文件-->
        <!-- 时间滚动输出 level为 INFO 日志 -->
        <appender name="INFO_FILE"
                  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 正在记录的日志文件的路径及文件名 -->
            <file>${log.path}/log_info.log</file>
            <!--日志文件输出格式-->
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                    %logger{50} - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
            <rollingPolicy
                    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 每天日志归档路径以及格式 -->
                <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-
                    dd}.%i.log</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy
                        class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <!--日志文件保留天数-->
                <maxHistory>15</maxHistory>
            </rollingPolicy>
            <!-- 此日志文件只记录info级别的 -->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
        <!-- 时间滚动输出 level为 WARN 日志 -->
        <appender name="WARN_FILE"
                  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 正在记录的日志文件的路径及文件名 -->
            <file>${log.path}/log_warn.log</file>
            <!--日志文件输出格式-->
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                    %logger{50} - %msg%n</pattern>
                <charset>UTF-8</charset> <!-- 此处设置字符集 -->
            </encoder>
            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
            <rollingPolicy
                    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-
                    dd}.%i.log</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy
                        class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <!--日志文件保留天数-->
                <maxHistory>15</maxHistory>
            </rollingPolicy>
            <!-- 此日志文件只记录warn级别的 -->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>warn</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
        <!-- 时间滚动输出 level为 ERROR 日志 -->
        <appender name="ERROR_FILE"
                  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 正在记录的日志文件的路径及文件名 -->
            <file>${log.path}/log_error.log</file>
            <!--日志文件输出格式-->
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                    %logger{50} - %msg%n</pattern>
                <charset>UTF-8</charset> <!-- 此处设置字符集 -->
            </encoder>
            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
            <rollingPolicy
                    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-
                    dd}.%i.log</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy
                        class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <!--日志文件保留天数-->
                <maxHistory>15</maxHistory>
            </rollingPolicy>
            <!-- 此日志文件只记录ERROR级别的 -->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
        <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指
        定<appender>。
        <logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL
        和 OFF,
        如果未设置此属性,那么当前logger将会继承上级的级别。
        -->
        <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想
        要查看sql语句的话,有以下两种操作:
        第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过
        这样日志那边会出现很多其他消息
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打
        印,其他还是正常DEBUG级别:
        -->
        <!--开发环境:打印控制台-->
        <springProfile name="dev">
            <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
            <logger name="com.guli" level="INFO" />
            <!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR,
            ALL 和 OFF,默认是DEBUG
            可以包含零个或多个appender元素。
            -->
            <root level="INFO">
                <appender-ref ref="CONSOLE" />
                <appender-ref ref="INFO_FILE" />
                <appender-ref ref="WARN_FILE" />
                <appender-ref ref="ERROR_FILE" />
            </root>
        </springProfile>
        <!--生产环境:输出到文件-->
        <springProfile name="pro">
            <root level="INFO">
                <appender-ref ref="CONSOLE" />
                <appender-ref ref="DEBUG_FILE" />
                <appender-ref ref="INFO_FILE" />
                <appender-ref ref="ERROR_FILE" />
                <appender-ref ref="WARN_FILE" />
            </root>
        </springProfile>
    </configuration>
    
  2. 将错误日志输出到文件:GlobalExceptionHandler.java 中

    @ControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
          
          
    
        /*
        * 全局异常
        * */
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public R error(Exception e) {
          
          
            e.printStackTrace();
            log.error(e.getMessage());
            return R.error().message("服务器发生了错误!");
        }
    
        /*
         * 自定义异常
         * */
        @ExceptionHandler(GuliException.class)
        @ResponseBody
        public R error(GuliException e) {
          
          
            e.printStackTrace();
            log.error(e.getMessage());
            return R.error().code(e.getCode()).message(e.getMsg());
        }
    }
    
    

10、Excel 操作

EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理。

1、实现写操作

1、导入相关依赖

<dependencies>  
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <poi.version>3.17</poi.version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <poi.version>3.17</poi.version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>

2、创建实体类,设置表头和添加的数据字段

@Data
public class DemoData {
    
    

    //设置Excel表头名称
    @ExcelProperty(value = "学生编号", index = 0)
    private Integer sno;
    @ExcelProperty(value = "学生姓名", index = 1)
    private String sname;
}

3、实现写操作

public static void main(String[] args) {
    
    
    String filename = "D:\\学习文档\\谷粒学院实战文档\\test.xlsx";
    EasyExcel.write(filename, DemoData.class).sheet("学生列表").doWrite(data());
}

2、实现读操作

1、创建实体类

@Data
public class DemoData {
    
    

    //设置Excel表头名称
    //设置列对应的属性
    @ExcelProperty(value = "学生编号", index = 0)
    private Integer sno;
    @ExcelProperty(value = "学生姓名", index = 1)
    private String sname;
}

2、创建读取操作的监听器

//创建读取excel监听器
public class ExcelListener extends AnalysisEventListener<DemoData> {
    
    

    //一行一行去读取excle内容
    @Override
    public void invoke(DemoData data, AnalysisContext analysisContext) {
    
    
        System.out.println("***" + data);
    }

    //读取excel表头信息
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    
    
        System.out.println("表头信息:" + headMap);
    }

    //读取完成后执行
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    
    

    }
}

3、实现读操作

public static void main(String[] args) {
    
    
    String filename = "D:\\学习文档\\谷粒学院实战文档\\test.xlsx";
    EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();
}

11、Redis缓存

1、导入依赖

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
</dependency>

2、添加redis配置类

/**
 * @author Zhy
 * @date 2021/2/26 16:01
 */
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    
    

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
                                                               factory) {
    
    
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    
    
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofSeconds(600))
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                        .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

12、单点登录实现方式

在这里插入图片描述

1、JWT令牌的使用

1、添加依赖

<!-- JWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

2、引入jwt工具类

/**
 * @author Zhy
 * @date 2021/2/26 21:02
 */
public class JwtUtils {
    
    

    public static final long EXPIRE = 1000 * 60 * 60 * 24;  // token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";  // 密钥

    // 生产token字符串的方法
    public static String getJwtToken(String id, String nickname){
    
    

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")  // 设置头部信息

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))  // 设置过期时间

                .claim("id", id)
                .claim("nickname", nickname)  // 设置token主体信息,存储用户信息,可以输入多个

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
    
    
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
    
    
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
    
    
        try {
    
    
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取用户id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
    
    
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

2、阿里云短信的使用

1、引入依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
</dependencies>

2、编写controller和service

@RestController
@RequestMapping("/edumsm/msm")
@CrossOrigin
@Api(description = "短信服务")
public class MsmController {
    
    

    @Autowired
    private MsmService msmService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @ApiOperation(value = "发送短信")
    @GetMapping("/send/{phone}")
    public R sendMsm(@PathVariable String phone) {
    
    

        // 从redis获取验证码,如果获取到直接返回
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
    
    
            return R.ok();
        }
        // 如果redis获取不到再通过阿里云发送
        code = RandomUtil.getFourBitRandom();
        Map<String, Object> param = new HashMap<>();
        param.put("code", code);
        Boolean isSend = msmService.send(param, phone);
        if (isSend) {
    
    
            // 存入redis并设置有效时间
            redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
            return R.ok();
        } else {
    
    
            return R.error().message("短信发送失败!");
        }
    }
}
@Service
public class MsmServiceImpl implements MsmService {
    
    
    @Override
    public Boolean send(Map<String, Object> param, String phone) {
    
    

        if (StringUtils.isEmpty(phone))
            return false;
        DefaultProfile profile = DefaultProfile.getProfile("default", "LTAI4FvvVEWiTJ3GNJJqJnk7", "9st82dv7EvFk9mTjYO1XXbM632fRbG");
        IAcsClient client = new DefaultAcsClient(profile);

        // 设置相关固定参数
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        // 设置发送相关参数
        request.putQueryParameter("PhoneNumbers",phone); //手机号
        request.putQueryParameter("SignName","我的谷粒在线教育网站"); //申请阿里云 签名名称
        request.putQueryParameter("TemplateCode","SMS_180051135"); //申请阿里云 模板code
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //验证码数据,转换json数据传递

        try {
    
    
            CommonResponse response = client.getCommonResponse(request);
            boolean success = response.getHttpResponse().isSuccess();
            return success;
        } catch (ClientException e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
}

13、HttpClient工具类

1、导入依赖

<dependencies>
    <!--httpclient-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
    <!--commons-io-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
    </dependency>
    <!--gson-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>
</dependencies>

2、创建httpclient工具类

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 *  依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
 * @author zhaoyb
 *
 */
public class HttpClientUtils {
    
    

	public static final int connTimeout=10000;
	public static final int readTimeout=10000;
	public static final String charset="UTF-8";
	private static HttpClient client = null;

	static {
    
    
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
		cm.setMaxTotal(128);
		cm.setDefaultMaxPerRoute(128);
		client = HttpClients.custom().setConnectionManager(cm).build();
	}

	public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    
    
		return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
	}

	public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    
    
		return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
	}

	public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
    
    
		return postForm(url, params, null, connTimeout, readTimeout);
	}

	public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
    
    
		return postForm(url, params, null, connTimeout, readTimeout);
	}

	public static String get(String url) throws Exception {
    
    
		return get(url, charset, null, null);
	}

	public static String get(String url, String charset) throws Exception {
    
    
		return get(url, charset, connTimeout, readTimeout);
	}

	/**
	 * 发送一个 Post 请求, 使用指定的字符集编码.
	 *
	 * @param url
	 * @param body RequestBody
	 * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
	 * @param charset 编码
	 * @param connTimeout 建立链接超时时间,毫秒.
	 * @param readTimeout 响应超时时间,毫秒.
	 * @return ResponseBody, 使用指定的字符集编码.
	 * @throws ConnectTimeoutException 建立链接超时异常
	 * @throws SocketTimeoutException  响应超时
	 * @throws Exception
	 */
	public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
			throws ConnectTimeoutException, SocketTimeoutException, Exception {
    
    
		HttpClient client = null;
		HttpPost post = new HttpPost(url);
		String result = "";
		try {
    
    
			if (StringUtils.isNotBlank(body)) {
    
    
				HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
				post.setEntity(entity);
			}
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
    
    
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
    
    
				customReqConf.setSocketTimeout(readTimeout);
			}
			post.setConfig(customReqConf.build());

			HttpResponse res;
			if (url.startsWith("https")) {
    
    
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(post);
			} else {
    
    
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(post);
			}
			result = IOUtils.toString(res.getEntity().getContent(), charset);
		} finally {
    
    
			post.releaseConnection();
			if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
    
    
				((CloseableHttpClient) client).close();
			}
		}
		return result;
	}


	/**
	 * 提交form表单
	 *
	 * @param url
	 * @param params
	 * @param connTimeout
	 * @param readTimeout
	 * @return
	 * @throws ConnectTimeoutException
	 * @throws SocketTimeoutException
	 * @throws Exception
	 */
	public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
    
    

		HttpClient client = null;
		HttpPost post = new HttpPost(url);
		try {
    
    
			if (params != null && !params.isEmpty()) {
    
    
				List<NameValuePair> formParams = new ArrayList<NameValuePair>();
				Set<Entry<String, String>> entrySet = params.entrySet();
				for (Entry<String, String> entry : entrySet) {
    
    
					formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
				}
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
				post.setEntity(entity);
			}

			if (headers != null && !headers.isEmpty()) {
    
    
				for (Entry<String, String> entry : headers.entrySet()) {
    
    
					post.addHeader(entry.getKey(), entry.getValue());
				}
			}
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
    
    
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
    
    
				customReqConf.setSocketTimeout(readTimeout);
			}
			post.setConfig(customReqConf.build());
			HttpResponse res = null;
			if (url.startsWith("https")) {
    
    
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(post);
			} else {
    
    
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(post);
			}
			return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
		} finally {
    
    
			post.releaseConnection();
			if (url.startsWith("https") && client != null
					&& client instanceof CloseableHttpClient) {
    
    
				((CloseableHttpClient) client).close();
			}
		}
	}




	/**
	 * 发送一个 GET 请求
	 *
	 * @param url
	 * @param charset
	 * @param connTimeout  建立链接超时时间,毫秒.
	 * @param readTimeout  响应超时时间,毫秒.
	 * @return
	 * @throws ConnectTimeoutException   建立链接超时
	 * @throws SocketTimeoutException   响应超时
	 * @throws Exception
	 */
	public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
			throws ConnectTimeoutException,SocketTimeoutException, Exception {
    
    

		HttpClient client = null;
		HttpGet get = new HttpGet(url);
		String result = "";
		try {
    
    
			// 设置参数
			Builder customReqConf = RequestConfig.custom();
			if (connTimeout != null) {
    
    
				customReqConf.setConnectTimeout(connTimeout);
			}
			if (readTimeout != null) {
    
    
				customReqConf.setSocketTimeout(readTimeout);
			}
			get.setConfig(customReqConf.build());

			HttpResponse res = null;

			if (url.startsWith("https")) {
    
    
				// 执行 Https 请求.
				client = createSSLInsecureClient();
				res = client.execute(get);
			} else {
    
    
				// 执行 Http 请求.
				client = HttpClientUtils.client;
				res = client.execute(get);
			}

			result = IOUtils.toString(res.getEntity().getContent(), charset);
		} finally {
    
    
			get.releaseConnection();
			if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
    
    
				((CloseableHttpClient) client).close();
			}
		}
		return result;
	}


	/**
	 * 从 response 里获取 charset
	 *
	 * @param ressponse
	 * @return
	 */
	@SuppressWarnings("unused")
	private static String getCharsetFromResponse(HttpResponse ressponse) {
    
    
		// Content-Type:text/html; charset=GBK
		if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
    
    
			String contentType = ressponse.getEntity().getContentType().getValue();
			if (contentType.contains("charset=")) {
    
    
				return contentType.substring(contentType.indexOf("charset=") + 8);
			}
		}
		return null;
	}



	/**
	 * 创建 SSL连接
	 * @return
	 * @throws GeneralSecurityException
	 */
	private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
    
    
		try {
    
    
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
    
    
				public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    
    
					return true;
				}
			}).build();

			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
    
    

				@Override
				public boolean verify(String arg0, SSLSession arg1) {
    
    
					return true;
				}

				@Override
				public void verify(String host, SSLSocket ssl)
						throws IOException {
    
    
				}

				@Override
				public void verify(String host, X509Certificate cert)
						throws SSLException {
    
    
				}

				@Override
				public void verify(String host, String[] cns,
								   String[] subjectAlts) throws SSLException {
    
    
				}

			});

			return HttpClients.custom().setSSLSocketFactory(sslsf).build();

		} catch (GeneralSecurityException e) {
    
    
			throw e;
		}
	}

	public static void main(String[] args) {
    
    
		try {
    
    
			String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
			//String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
            /*Map<String,String> map = new HashMap<String,String>();
            map.put("name", "111");
            map.put("page", "222");
            String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
			System.out.println(str);
		} catch (ConnectTimeoutException e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SocketTimeoutException e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

14、定时任务

1、添加注解 @EnableScheduling

@SpringBootApplication
@ComponentScan(basePackages = {
    
    "com.cqupt"})
@EnableDiscoveryClient
@EnableFeignClients
@EnableScheduling
@MapperScan("com.cqupt.staservice.mapper")
public class StaApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(StaApplication.class, args);
    }
}

2、创建定时任务类,使用cron表达式

@Component
public class ScheduledTask {
    
    

    @Autowired
    private StatisticsDailyService statisticService;

    @ApiOperation(value = "每天凌晨1点执行方法")
    @Scheduled(cron = "0 0 1 * * ?")
    public void taskStatistics() {
    
    
        String day = DateUtil.formatDate(DateUtil.addDays(new Date(), -1));
        statisticService.registerCount(day);
    }

}

猜你喜欢

转载自blog.csdn.net/qq_42372017/article/details/114333678