定时任务,随机数生成,日志处理,jackson序列化,自定义注解+反射,Guava本地缓存,httpclient封装,java原生动态代理,mybatis-plus,异常处理

开发版本缺陷记录

  1. 01版统一基于http+springmvc协议开发,02版再升级成netty(nio方式)

问题思考

文件传输架构选择了webflux,但是根本没有用到其特性,反而极大增加开发难度,无法调用某些已封装好的文件框架的api

默认Controller、Dao、Service都是单例的。线程不安全,可以使用ThreadLocal变量解决改问题

 @RequestMapping("/getAll")
    public Response getAll() {
        TimeUnit.SECONDS.sleep(10);
        return Response.success(demoService.getAll());
    }

请求A,B,C按照先后顺序发起请求,那么A请求需要10秒才能够响应,B请求需要20秒才能够响应,C请求需要30秒才能够响应。
你用了webflux其过程和结果也是一样的。

参考文章

jackson常用注解详解
Guava缓存详解及使用
Guava 快速入门(一)

ImmutableMap,ImmutableList使用

引入依赖

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

使用

appMapper.deleteByMap(ImmutableMap.of("biz_id", param.getAppIds()));
//ImmutableList.of(2l,3l);

java发送http请求

RestTemplate 用法详解
Springboot使用RestTemplate发送Post请求postForEntity (application/json)的坑-强推

随机数生成

随机生成MIN到MAX的一个数 random.nextInt(max - min) + min

        Random random = new Random();
        //包含最小min,不包含最大max
         int i = random.nextInt(max - min) + min;

js的公式是这样的

    //包括最小min,不包括最大max
    //Math.floor(Math.random() * (max - min)) + min

定时任务

假设打王者荣耀,玩家准备时间是1秒(首次延时时间),每局游戏时间是1到10秒随机(任务执行时间)
scheduleAtFixedRate的机制是每隔1秒(定时时间)就会去检查游戏结束了没有,结束了就马上开始下一局游戏,每结束,就等这局游戏结束后马上开始开始下一局游戏(任务执行)。
scheduleWithFixedDelay的机制是每局游戏结束后,休息1秒(定时时间),再开始下一局游戏。

scheduleAtFixedRate ,是以上一个任务开始的时间计时,1秒过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行.
scheduleWithFixedDelay优先保证任务执行的间隔,是以上一个任务结束时开始计时,1秒过去后,立即执行.

@Slf4j
public class ScheduleDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        //scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Random random = new Random();
                //随机生成1到10的一个数
                int i = random.nextInt(10) + 1;
                log.info("线程名:{},开始执行,执行时间在x秒:{},任务执行耗时:{}秒",Thread.currentThread().getName(),LocalTime.now().getSecond(),i);
                TimeUnit.SECONDS.sleep(i);
            }
        },1,1,TimeUnit.SECONDS);
//========scheduleAtFixedRate执行打印情况(循环周期1秒)
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:20,任务执行耗时:5秒
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:25,任务执行耗时:8秒
//        线程名:pool-1-thread-2,开始执行,执行时间在x秒:33,任务执行耗时:7秒
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:40,任务执行耗时:6秒
//========scheduleWithFixedDelay执行打印情况(循环周期1秒)
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:32,任务执行耗时:9秒
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:42,任务执行耗时:10秒
//        线程名:pool-1-thread-2,开始执行,执行时间在x秒:53,任务执行耗时:2秒
//        线程名:pool-1-thread-1,开始执行,执行时间在x秒:56,任务执行耗时:4秒
    }
}

日志处理

引入依赖

 <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
     <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>

resources下面新建文件logback.xml

  • 仅控制台输出就行
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
  <!-- 控制台输出 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- 日志输出级别 -->
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • 输出到文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="log" />
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/log/%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

用下面这个会好一些

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <property name="logback.logdir" value="/data/logs/weda/schema-server"/>
    <property name="logback.appname" value="schema-server"/>

    <appender name="sql"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${logback.appname}-sql.%d{yyyy-MM-dd}.log</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} [%t] [%X{traceId}]  %5p %c:%L] %m%n</pattern>
        </encoder>
    </appender>

    <!--输出到控制台 ConsoleAppender-->
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <!--展示格式 layout-->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%thread] [%logger{50}] >>> %msg%n</pattern>
            </pattern>
        </layout>
    </appender>

    <appender name="infoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高,
        所以我们使用下面的策略,可以避免输出 Error 的日志-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 Error-->
            <level>ERROR</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
            如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
            的日志改名为今天的日期。即,<File> 的日志都是当天的。
        -->
        <File>${logback.logdir}/info.log</File>
        <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
            <FileNamePattern>${logback.logdir}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--只保留最近90天的日志-->
            <maxHistory>90</maxHistory>
            <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
            <!--<totalSizeCap>1GB</totalSizeCap>-->
        </rollingPolicy>
        <!--日志输出编码格式化-->
        <encoder>
            <charset>UTF-8</charset>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%thread] [%logger{50}] >>> %msg%n</pattern>
        </encoder>
    </appender>


    <appender name="errorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>Error</level>
        </filter>
        <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
            如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
            的日志改名为今天的日期。即,<File> 的日志都是当天的。
        -->
        <File>${logback.logdir}/error.log</File>
        <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
            <FileNamePattern>${logback.logdir}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--只保留最近90天的日志-->
            <maxHistory>90</maxHistory>
            <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
            <!--<totalSizeCap>1GB</totalSizeCap>-->
        </rollingPolicy>
        <!--日志输出编码格式化-->
        <encoder>
            <charset>UTF-8</charset>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%thread] [%logger{50}] >>> %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.tencent.tgac.model.dao" level="DEBUG" additivity="false">
        <appender-ref ref="sql" />
    </logger>

    <!--指定最基础的日志输出级别-->
    <root level="INFO">
        <appender-ref ref="consoleLog"/>
        <!--appender将会添加到这个loger-->
        <appender-ref ref="sql"/>
        <appender-ref ref="infoLog"/>
        <appender-ref ref="errorLog"/>
    </root>

</configuration>
log:
  dir: ${log_dir}

使用例子

@Slf4j
public class Te {
    public static void main(String[] args) {
        log.info("参数1:{},参数2:{},参数3:{}秒","xxx",1,1);
    }
}

在这里插入图片描述

jackson序列化

引入依赖

 <!-- objectMapper对象-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.1</version>
    </dependency>
    <!-- @JsonIgnore, @JsonProperty对序列化的属性名重命名,@JsonProperty("first_name")-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.12.1</version>
    </dependency>

用法示例

@Slf4j
public class Te {
    @SneakyThrows
    public static void main(String[] args) {
        String jsonStr="{\n" +
                "\"name\":\"lmj\",\n" +
                "\"age\":\"18\"\n" +
                "}";
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String,Object>map= objectMapper.readValue(jsonStr, new TypeReference<Map<String,Object>>() {
        });
        log.info("序列化结果:{}",map);
        String str = objectMapper.writeValueAsString(map);
        log.info("反序列化结果为:{}",str);
    }
}

结果如下:
在这里插入图片描述

序列化和反序列化时字段统一按大驼峰规范

@Data
@JsonNaming(value = PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class SecretKeyUpdateReq {
    @NotEmpty
    //这样给出的提示是:secretName:不能为空【原因是valid校验给出提示时不涉及序列化操作,可以在捕捉参数异常处理时对其进行操作,但一般没有必要】
    private String secretName;
    @NotEmpty(message = "SecretId:不能为空")
    private String secretId;
    private String secretKey;
}

自定义注解+反射

创建注解(理解为类,方法,字段的附加信息,可通过反射获取到)

@Retention(value=RetentionPolicy.RUNTIME) //编译后仍有效
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) //作用于字段,方法,类
public @interface DimAnnotation {
    String value() default "";
    String name() default "";
}

创建测试类

public class AnnoDemo {
    @DimAnnotation(value = "result is success",name = "test method")
    private String test(){
        return "success";
    }
}

反射获取注解并调用示例示例

@Slf4j
public class Te {
    @SneakyThrows
    public static void main(String[] args) {
        Class<?> clazz = Class.forName("org.lmj.hf.common.AnnoDemo");
        //获取所有方法,包括private方法
        Method test = clazz.getDeclaredMethod("test");
        //只能获取public方法,private方法会报找不到
        //Method test = clazz.getMethod("test", String.class);
        //getMethod(“方法名”,“参数的类型”)
        //Method test = clazz.getMethod("test");
        DimAnnotation annotation = test.getAnnotation(DimAnnotation.class);
        String name = annotation.name();
        String value = annotation.value();
        AnnoDemo obj = (AnnoDemo)clazz.newInstance();
        //test方法是public可通过改方式调用,private只能通过反射调用
        //obj.test();
        test.setAccessible(true);
        String res = (String) test.invoke(obj, null);
        log.info("name:{},value:{},method result:{}",name,value,res);
    }
}

Guava本地缓存

本地缓存Guava和分布式缓存redis对比

  1. 本地缓存扩展内存困难,分布式下需要自己做数据多机同步处理(数据被多个服务用到时)
  2. redis可以写磁盘,持久化,本地缓存不可以
  3. 加本地缓存后,代码复杂度急剧上升,使用本地缓存极有可能导致严重的线程安全问题,并发考虑严重
  4. 其实在map(本地缓存原理)和redis取值(redis多种数据结构)这里省的时间,可能在我们写得乱七八糟的代码里,早都不算啥了,所有有时候咱们真的没必要较那几毫秒的真!
  5. 适用场景:本地缓存适用于数据量较小或变动较少的数据,比如数据字典的缓存;分布式缓存则适用于一致性要求较高及数量量大的场景(可弹性扩容)

eureka用了本地缓存

引入依赖

<dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>29.0-jre</version>
    </dependency>

使用案例

public class CacheDemo {
    /**初始化缓存*/
    //maximumSize设置最大存储1万条记录,超过则清除掉那些最近最少使用的缓存
    //expireAfterWrite写缓存之后3分钟以后该条记录过期
    static private final Cache<String, String> stringCache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).maximumSize(10000).build();
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                getNameCahe();
            }
        },1,2,TimeUnit.SECONDS);
    }
    /**读写缓存*/
    static public void getNameCahe() {
        String name = stringCache.getIfPresent("name");
        if (StringUtils.isEmpty(name)) {
            stringCache.put("name", "lmj");
            System.out.println("写入缓存数据name=" + "lmj");
        } else {
            System.out.println("直接从缓存中读出来的数据name=" + name);
        }
    }
}

结果如下:

写入缓存数据name=lmj
直接从缓存中读出来的数据name=lmj
写入缓存数据name=lmj
直接从缓存中读出来的数据name=lmj

httpclient封装

https://blog.csdn.net/justry_deng/article/details/81042379

java原生动态代理

自己手写一个Mybatis框架(简化)

先定义一个接口

public interface TestInterface {
    String sayHello(String content);
}

定义代理类(一般在框架启动的时候会去实现–主要就是一些通用,琐碎,可复用的非业务逻辑实现)

public class TestIfProxy {
    //<T>表示这个方法声明为泛型方法.
    public <T> T sayHelloImpl(Class<T> clazz){
        /**
         * 用哪个类加载器去加载代理对象
         * 动态代理类需要实现的接口
         * 动态代理方法在执行时,会调用InvocationHandler实现类里面的invoke方法去执行
         */
        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            /**
             * 就是代理对象,newProxyInstance方法的返回对象
             * 调用的方法
             * 方法中的参数
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("代理对象的名字:"+proxy.getClass().getName());
                System.out.println("方法名:" + method.getName());
                System.out.println("传入的参数:" + String.valueOf(args[0]));
                return "success";
            }
        });
        return (T)o;
    }
}

执行

public class Ha {
    public static void main(String[] args) {
        TestInterface testInterface=new TestIfProxy().sayHelloImpl(TestInterface.class);
        String result = testInterface.sayHello("hah");
        System.out.println("代理类执行返回结果为:"+result);
    }
}

结果如下:
在这里插入图片描述

在这里插入图片描述

mybatis-plus

填充器用法:
https://gitee.com/baomidou/mybatis-plus/issues/IKDXA

@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 插入时的填充策略
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        // 创建和修改时间自动填充当前时间
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    /**
     * 更新时的填充策略
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        // 修改时间自动填充当前时间
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
    libinstAppCategoryMapper.deleteByMap(ImmutableMap.of("biz_id",param.getBizId()));

UPDATE weda_libinst_app_category SET deleted=1 WHERE biz_id = ? AND deleted=0

原因如下:mybatis-plus:

  type-handlers-package: com.tencent.gov.goff.common.db.handler
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    # 0 <= id <=31, datacenter-id + worker-id 确保唯一
  # worker-id: 0
  # datacenter-id: 0
    db-config:
      # 全局删除字段名
      logic-delete-field: deleted
      # 逻辑已删除值
      logic-delete-value: 1
      # 逻辑未删除值
      logic-not-delete-value: 0
  mapper-locations: classpath*:/mapper/**/*.xml

没改配置那么自然是delete from


在这里插入图片描述
goff框架对于mybatis-plus做了配置重写,所有调用mybatis-plus的更新都会自动追加deleted=0,且不能根据程序更新deleted为1,所有的物理删除都被禁止使用(必要情况可自写sql)
在这里插入图片描述

如果这样写就会报错
在这里插入图片描述

    @Override
    public void delete(AppDeleteReq req) {
        //wedaAppMapper.delete(new QueryWrapper<WedaAppDo>().in("app_id",req.getAppIds()));
        wedaAppMapper.update(new WedaAppDo() {{
            setDeleted(1);
        }}, new UpdateWrapper<WedaAppDo>().lambda().set(WedaAppDo::getDeleted, 1)
                .in(WedaAppDo::getAppId, req.getAppIds()));

        /**
         * 报错
         */
        wedaAppMapper.update(new WedaAppDo() {{
            setDeleted(1);
        }},new UpdateWrapper<WedaAppDo>().in("app_id", req.getAppIds()));
    }

分页:

1)       limit分页公式
(1)limit分页公式:curPage是当前第几页;pageSize是一页多少条记录
limit (curPage-1)*pageSize,pageSize
(2)用的地方:sql语句中
select * from student limit(curPage-1)*pageSize,pageSize;

2)       总页数公式
(1)总页数公式:totalRecord是总记录数;pageSize是一页分多少条记录
int totalPageNum = (totalRecord +pageSize - 1) / pageSize;2)用的地方:前台UI分页插件显示分页码
(3)查询总条数:totalRecord是总记录数
SELECT COUNT(*) FROM tablename

在这里插入图片描述

只更新部分字段

LambdaUpdateWrapper<WedaCompReleaseDo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(WedaCompReleaseDo::getBizId, param.getLibReleaseId());
            updateWrapper.set(WedaCompReleaseDo::getReleaseVersion ,1);
            releaseMapper.update(null, updateWrapper);

对应生成的语句为:

UPDATE weda_comp_release SET release_version=? WHERE deleted=0 AND (biz_id = ?)

写动态sql

public interface WedaCompReleaseMapper extends BaseMapper<WedaCompReleaseDo> {

    List<WedaCompReleaseDo> selectByCodesAndVersions(@Param("deployCheckReqs") List<DeployCheckDTO> deployCheckReq,
            @Param("targetEnv") String targetEnv);
}
<select id="selectByCodesAndVersions"
            resultType="com.tencent.tgac.weda.entity.WedaCompReleaseDo">
        SELECT biz_id,library_code,library_version,release_env,release_version,
        remark FROM weda_comp_release WHERE
        deleted=0
        AND (library_code,library_version,release_env) in
        <foreach collection="deployCheckReqs" item="item" separator="," open="(" close=")">
            (#{item.libCode},#{item.libVersion},#{targetEnv})
        </foreach>
    </select>
SELECT * FROM weda_comp_release WHERE deleted=0 AND (library_code,library_version,release_env) in ( (?,?,?) , (?,?,?) ) 

日志处理

  1. 参数校验需要引入正确的依赖才行:
    在这里插入图片描述
    异常拦截分享2个
package com.tencent.tgac.weda.config;

import com.google.common.base.Joiner;
import com.tencent.gov.goff.common.core.exception.GoffException;
import com.tencent.gov.goff.common.pojo.bean.ErrorCode;
import com.tencent.tgac.weda.enums.ErrorCodeEnum;
import com.tencent.tgac.weda.pojo.vo.ServerResponse;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;

import feign.codec.DecodeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

/**
 * 异常处理
 *
 * @author lizhiqaing
 */
@Slf4j
@RestControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {


    /**
     * 参数值异常处理
     *
     * @param exception 参数值异常
     * @return 错误信息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ServerResponse validationBodyException(MethodArgumentNotValidException exception) {
        log.error("全局MethodArgumentNotValidException异常:", exception);
        BindingResult bindingResult = exception.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        List<String> collect = fieldErrors.stream().map(obj -> obj.getField() + ":" + obj.getDefaultMessage())
                .collect(Collectors.toList());
        return ServerResponse.fail(ErrorCode.INVALID_PARAMETER.getCode(), Joiner.on(";").join(collect));
    }

    /**
     * 参数值异常处理
     *
     * @param exception 参数值异常
     * @return 错误信息
     */
    @ExceptionHandler(MissingServletRequestPartException.class)
    public ServerResponse validationBodyException(MissingServletRequestPartException exception) {
        log.error("全局MissingServletRequestPartException异常:", exception);
        String partName = exception.getRequestPartName();
        return ServerResponse.fail(ErrorCode.INVALID_PARAMETER.getCode(), partName + ":" + "参数不合规");
    }

    /**
     * 参数类型异常处理
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    public ServerResponse parameterTypeException(HttpMessageConversionException exception) {
        log.error("全局HttpMessageConversionException异常:", exception);
        return ServerResponse.fail(ErrorCode.INVALID_PARAMETER.getCode(), "参数类型异常");
    }

    @ExceptionHandler(value = GoffException.class)
    public ServerResponse globalExceptionHandler(HttpServletRequest req, GoffException exception) {
        log.error("全局GoffException异常:", exception);
        String detailErrorMessage = exception.getDetailErrorMessage();
        if(StringUtils.isBlank(detailErrorMessage)){
            return ServerResponse.fail(exception.getCode(), exception.getMessage());
        }
        return ServerResponse.fail(exception.getCode(), detailErrorMessage);
    }

    @ExceptionHandler(value = Exception.class)
    public ServerResponse globalExceptionHandler(HttpServletRequest req, Exception exception) {
        log.error("全局Exception异常:", exception);
        return ServerResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), ErrorCodeEnum.INNER_ERROR.getDesc());
    }


    @ExceptionHandler(value = IllegalArgumentException.class)
    public ServerResponse illegalArgumentExceptionHandler(HttpServletRequest req, IllegalArgumentException exception) {
        log.error("全局IllegalArgumentException异常:", exception);
        return ServerResponse.fail(ErrorCode.INVALID_PARAMETER.getCode(), exception.getMessage());
    }

    @ExceptionHandler(value = DecodeException.class)
    public ServerResponse decodeExceptionHandler(HttpServletRequest req, DecodeException exception) {
        log.error("接口调用异常:", exception);
        return ServerResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), exception.getMessage());
    }

}

自己写的

package com.example.springdemo.exception;

import com.example.springdemo.controller.ServerResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * Spring security权限不足处理类
     * 只有登录后(即接口有传token)接口权限不足才会进入AccessDeniedHandler,
     * 如果是未登陆或者会话超时等,不会触发AccessDeniedHandler,而是会直接跳转到登陆页面。
     */
     //会执行该方法
    @ExceptionHandler(AccessDeniedException.class)
    public ServerResponse handleAccessDeniedException(HttpServletRequest req, AccessDeniedException e){
        log.error(e.getMessage(), e);
        return ServerResponse.fail(ServerResponse.UNAUTH, ("权限不足"));
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public ServerResponse handleException(HttpRequestMethodNotSupportedException e) {
        log.error(e.getMessage(), e);
        return ServerResponse.fail(ServerResponse.BAD_PARAMETER, ("不支持' " + e.getMethod() + "'请求"));
    }

    /**
     * 全局拦截就不需要有运行时异常拦截和中国类拦截,这样时没有意义的,你就应该写一个很拦截异常处理,不然不如向AccessDeniedException它就不会
     */
    /**
     * 拦截未知的运行时异常
     */
//    @ExceptionHandler(RuntimeException.class)
//    public ServerResponse notFount(RuntimeException e) {
//        log.error("运行时异常:", e);
//        return ServerResponse.fail(ServerResponse.INNER_ERROR, "运行时异常:" + e.getMessage());
//    }

    /**
     * 系统异常
     */
//    @ExceptionHandler(Exception.class)
//    public ServerResponse handleException(Exception e) {
//        log.error(e.getMessage(), e);
//        if (e instanceof RuntimeException){
//            log.error(e.getMessage(), e);
//            return ServerResponse.fail(ServerResponse.UNAUTH, ("权限不足"));
//        }
//        return ServerResponse.fail(ServerResponse.INNER_ERROR, "服务器错误,请联系管理员");
//    }

    /**
     * 校验异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ServerResponse exceptionHandler(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMesssage = "";
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMesssage += fieldError.getDefaultMessage() + ";";
        }
        return ServerResponse.fail(ServerResponse.BAD_PARAMETER, errorMesssage);
    }

    /**
     * 校验异常
     */
    @ExceptionHandler(value = BindException.class)
    public ServerResponse validationExceptionHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMesssage = "";
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMesssage += fieldError.getDefaultMessage() + ";";
        }
        return ServerResponse.fail(ServerResponse.BAD_PARAMETER, errorMesssage);
    }

    /**
     * 校验异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ServerResponse ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
        List<String> msgList = new ArrayList<>();
        while (iterator.hasNext()) {
            ConstraintViolation<?> cvl = iterator.next();
            msgList.add(cvl.getMessageTemplate());
        }
        return ServerResponse.fail(ServerResponse.BAD_PARAMETER, String.join(",", msgList));
    }

    /**
     * 业务异常
     */
   /* @ExceptionHandler(BusinessException.class)
    public AjaxResult businessException(BusinessException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.error(e.getMessage());
    }
*/
    /**
     * 演示模式异常
     */
    /*@ExceptionHandler(DemoModeException.class)
    public AjaxResult demoModeException(DemoModeException e) {
        return AjaxResult.error("演示模式,不允许操作");
    }*/

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值