文章目录
开发版本缺陷记录
- 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对比
- 本地缓存扩展内存困难,分布式下需要自己做数据多机同步处理(数据被多个服务用到时)
- redis可以写磁盘,持久化,本地缓存不可以
- 加本地缓存后,代码复杂度急剧上升,使用本地缓存极有可能导致严重的线程安全问题,并发考虑严重
- 其实在map(本地缓存原理)和redis取值(redis多种数据结构)这里省的时间,可能在我们写得乱七八糟的代码里,早都不算啥了,所有有时候咱们真的没必要较那几毫秒的真!
- 适用场景:本地缓存适用于数据量较小或变动较少的数据,比如数据字典的缓存;分布式缓存则适用于一致性要求较高及数量量大的场景(可弹性扩容)
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原生动态代理
先定义一个接口
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 ( (?,?,?) , (?,?,?) )
日志处理
- 参数校验需要引入正确的依赖才行:
异常拦截分享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("演示模式,不允许操作");
}*/
}