清理冗余的代码
This commit is contained in:
parent
ea7ad4b1ca
commit
0cf1738c0a
@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>onemall</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-cache</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.redisson</groupId>
|
|
||||||
<artifactId>redisson</artifactId>
|
|
||||||
<version>3.10.6</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>redis.clients</groupId>
|
|
||||||
<artifactId>jedis</artifactId>
|
|
||||||
<version>3.1.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,79 +0,0 @@
|
|||||||
package cn.iocoder.mall.cache.config;
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
import redis.clients.jedis.JedisSentinelPool;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class JedisClient {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private static JedisSentinelPool jedisSentinelPool;
|
|
||||||
|
|
||||||
public static String get(String key) {
|
|
||||||
Jedis jedis = null;
|
|
||||||
try {
|
|
||||||
jedis = jedisSentinelPool.getResource();
|
|
||||||
return jedis.get(key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
if (jedis != null) {
|
|
||||||
jedis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean set(String key, String value) {
|
|
||||||
Jedis jedis = null;
|
|
||||||
try {
|
|
||||||
jedis = jedisSentinelPool.getResource();
|
|
||||||
String ret = jedis.set(key, value);
|
|
||||||
return "ok".equalsIgnoreCase(ret);
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
if (jedis != null) {
|
|
||||||
jedis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean set(String key, String value, int seconds) {
|
|
||||||
Jedis jedis = null;
|
|
||||||
try {
|
|
||||||
jedis = jedisSentinelPool.getResource();
|
|
||||||
String ret = jedis.set(key, value);
|
|
||||||
jedis.expire(key, seconds);
|
|
||||||
return "ok".equalsIgnoreCase(ret);
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
if (jedis != null) {
|
|
||||||
jedis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean del(String key) {
|
|
||||||
Long removedSize = 0L;
|
|
||||||
Jedis jedis = null;
|
|
||||||
try {
|
|
||||||
jedis = jedisSentinelPool.getResource();
|
|
||||||
removedSize = jedis.del(key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
if (jedis != null) {
|
|
||||||
jedis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return removedSize > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package cn.iocoder.mall.cache.config;
|
|
||||||
|
|
||||||
|
|
||||||
import org.redisson.Redisson;
|
|
||||||
import org.redisson.config.Config;
|
|
||||||
import org.redisson.config.ReadMode;
|
|
||||||
import org.redisson.config.SentinelServersConfig;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RedissonClient {
|
|
||||||
|
|
||||||
@Value("${spring.redis.database}")
|
|
||||||
private int database;
|
|
||||||
|
|
||||||
@Value("${spring.redis.sentinel.master}")
|
|
||||||
private String master;
|
|
||||||
|
|
||||||
@Value("${spring.redis.sentinel.nodes}")
|
|
||||||
private String nodes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 哨兵模式 redisson 客户端
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public org.redisson.api.RedissonClient redissonClient() {
|
|
||||||
Config config = new Config();
|
|
||||||
List<String> nodes = Arrays.asList(this.nodes.split(","));
|
|
||||||
List<String> newNodes = new ArrayList(nodes.size());
|
|
||||||
nodes .forEach((index) -> newNodes.add(
|
|
||||||
index.startsWith("redis://") ? index : "redis://" + index));
|
|
||||||
|
|
||||||
SentinelServersConfig serverConfig = config.useSentinelServers()
|
|
||||||
.addSentinelAddress(newNodes.toArray(new String[3]))
|
|
||||||
.setMasterName(this.master)
|
|
||||||
.setReadMode(ReadMode.SLAVE) ;
|
|
||||||
|
|
||||||
serverConfig.setDatabase(this.database);
|
|
||||||
return Redisson.create(config);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,165 +0,0 @@
|
|||||||
package cn.iocoder.mall.cache.config;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
|
||||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.redisson.Redisson;
|
|
||||||
import org.redisson.api.RedissonClient;
|
|
||||||
import org.redisson.config.Config;
|
|
||||||
import org.redisson.config.ReadMode;
|
|
||||||
import org.redisson.config.SentinelServersConfig;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.cache.CacheManager;
|
|
||||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
import org.springframework.cache.interceptor.KeyGenerator;
|
|
||||||
import org.springframework.cache.interceptor.SimpleKeyGenerator;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
|
||||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
|
||||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
|
||||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|
||||||
import redis.clients.jedis.Jedis;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableCaching
|
|
||||||
public class SpringDataRedisConfig extends CachingConfigurerSupport {
|
|
||||||
|
|
||||||
@Value("${spring.redis.database}")
|
|
||||||
private int database;
|
|
||||||
|
|
||||||
@Value("${spring.redis.sentinel.master}")
|
|
||||||
private String master;
|
|
||||||
|
|
||||||
@Value("${spring.redis.sentinel.nodes}")
|
|
||||||
private String nodes;
|
|
||||||
|
|
||||||
private static RedisTemplate redisTemplate;
|
|
||||||
|
|
||||||
static {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String get(String key) {
|
|
||||||
redisTemplate.opsForValue().get(key);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean set(String key, String value) {
|
|
||||||
redisTemplate.opsForValue().set(key,value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean set(String key, String value, int seconds) {
|
|
||||||
redisTemplate.opsForValue().set(key,value,seconds);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* json序列化
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public RedisSerializer<Object> jackson2JsonRedisSerializer() {
|
|
||||||
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
|
||||||
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
|
||||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
|
||||||
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
||||||
serializer.setObjectMapper(mapper);
|
|
||||||
return serializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
|
||||||
//StringRedisTemplate的构造方法中默认设置了stringSerializer
|
|
||||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
|
||||||
//set key serializer
|
|
||||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
|
||||||
template.setKeySerializer(stringRedisSerializer);
|
|
||||||
template.setHashKeySerializer(stringRedisSerializer);
|
|
||||||
|
|
||||||
|
|
||||||
//set value serializer
|
|
||||||
template.setDefaultSerializer(jackson2JsonRedisSerializer());
|
|
||||||
|
|
||||||
template.setConnectionFactory(lettuceConnectionFactory);
|
|
||||||
template.afterPropertiesSet();
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
|
||||||
StringRedisTemplate template = new StringRedisTemplate();
|
|
||||||
template.setConnectionFactory(lettuceConnectionFactory);
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Bean
|
|
||||||
public KeyGenerator keyGenerator() {
|
|
||||||
return new SimpleKeyGenerator() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object generate(Object target, Method method, Object... params) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(target.getClass().getName());
|
|
||||||
sb.append(".").append(method.getName());
|
|
||||||
|
|
||||||
StringBuilder paramsSb = new StringBuilder();
|
|
||||||
for (Object param : params) {
|
|
||||||
// 如果不指定,默认生成包含到键值中
|
|
||||||
if (param != null) {
|
|
||||||
paramsSb.append(param.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramsSb.length() > 0) {
|
|
||||||
sb.append("_").append(paramsSb);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer time, int timeType) {
|
|
||||||
|
|
||||||
Duration duration = Duration.ofMillis(time);
|
|
||||||
if (timeType == 1){//时
|
|
||||||
duration = Duration.ofHours(time);
|
|
||||||
}else if (timeType == 2){//分
|
|
||||||
duration = Duration.ofMinutes(time);
|
|
||||||
}else if (timeType == 3){//秒
|
|
||||||
duration = Duration.ofSeconds(time);
|
|
||||||
}
|
|
||||||
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
|
|
||||||
redisCacheConfiguration = redisCacheConfiguration
|
|
||||||
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
|
|
||||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())
|
|
||||||
|
|
||||||
)
|
|
||||||
//超时时间
|
|
||||||
.entryTtl(duration);
|
|
||||||
|
|
||||||
return redisCacheConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
redis.pool.maxIdle=200
|
|
||||||
redis.pool.minIdle=10
|
|
||||||
#redis.pool.maxActive=600
|
|
||||||
redis.pool.maxTotal=1024
|
|
||||||
redis.pool.maxWaitMillis=3000
|
|
||||||
redis.pool.minEvictableIdleTimeMillis=300000
|
|
||||||
redis.pool.numTestsPerEvictionRun=1024
|
|
||||||
redis.pool.timeBetweenEvictionRunsMillis=30000
|
|
||||||
redis.pool.testOnBorrow=true
|
|
||||||
redis.pool.testWhileIdle=true
|
|
||||||
redis.pool.testOnReturn=true
|
|
||||||
|
|
||||||
#Redis 配置
|
|
||||||
spring.redis.sentinel.master=
|
|
||||||
spring.redis.sentinel.nodes=
|
|
||||||
spring.redis.database=
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>common</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-mybatis</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- 通用相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<artifactId>common-framework</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- DB 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mybatis</groupId>
|
|
||||||
<artifactId>mybatis</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mybatis.spring.boot</groupId>
|
|
||||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.baomidou</groupId>
|
|
||||||
<artifactId>mybatis-plus-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.baomidou</groupId>
|
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 工具相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>fastjson</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,23 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.config;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.mybatis.core.injector.CustomSqlInject;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Hccake 2020/8/3
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class MybatisPlusAutoConfiguration {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义方法扩展注入器
|
|
||||||
* @return ISqlInjector CustomSqlInject
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(ISqlInjector.class)
|
|
||||||
public ISqlInjector sqlInjector(){
|
|
||||||
return new CustomSqlInject();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.dataobject;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础实体对象
|
|
||||||
*/
|
|
||||||
public class BaseDO implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建时间
|
|
||||||
*/
|
|
||||||
private Date createTime;
|
|
||||||
/**
|
|
||||||
* 最后更新时间
|
|
||||||
*/
|
|
||||||
private Date updateTime;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BaseDO{" +
|
|
||||||
"createTime=" + createTime +
|
|
||||||
", updateTime=" + updateTime +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getCreateTime() {
|
|
||||||
return createTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDO setCreateTime(Date createTime) {
|
|
||||||
this.createTime = createTime;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getUpdateTime() {
|
|
||||||
return updateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDO setUpdateTime(Date updateTime) {
|
|
||||||
this.updateTime = updateTime;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.dataobject;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* extends BaseDO 扩展 delete 操作
|
|
||||||
*
|
|
||||||
* @author Sin
|
|
||||||
* @time 2019-03-22 22:03
|
|
||||||
*/
|
|
||||||
public class DeletableDO extends BaseDO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否删除
|
|
||||||
*/
|
|
||||||
@TableLogic
|
|
||||||
private Integer deleted;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "DeletableDO{" +
|
|
||||||
"deleted=" + deleted +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getDeleted() {
|
|
||||||
return deleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeletableDO setDeleted(Integer deleted) {
|
|
||||||
this.deleted = deleted;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.enums;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Hccake 2020/8/3
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public enum CustomSqlMethodEnum {
|
|
||||||
/**
|
|
||||||
* 批量插入
|
|
||||||
*/
|
|
||||||
INSERT_BATCH("insertByBatch",
|
|
||||||
"批量插入数据",
|
|
||||||
"<script>\n"
|
|
||||||
+ "INSERT INTO %s %s VALUES \n"
|
|
||||||
+ "<foreach collection=\"collection\" item=\"item\" separator=\",\"> %s\n </foreach>\n"
|
|
||||||
+ "</script>");
|
|
||||||
|
|
||||||
private final String method;
|
|
||||||
private final String desc;
|
|
||||||
private final String sql;
|
|
||||||
|
|
||||||
CustomSqlMethodEnum(String method, String desc, String sql) {
|
|
||||||
this.method = method;
|
|
||||||
this.desc = desc;
|
|
||||||
this.sql = sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMethod() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDesc() {
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSql() {
|
|
||||||
return sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.enums;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link DeletableDO#getDeleted()} delete 状态
|
|
||||||
*
|
|
||||||
* @author Sin
|
|
||||||
* @time 2019-03-22 21:15
|
|
||||||
*/
|
|
||||||
public enum DeletedStatusEnum {
|
|
||||||
|
|
||||||
DELETED_NO(0, "正常(未删除)"),
|
|
||||||
DELETED_YES(1, "删除");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态值
|
|
||||||
*/
|
|
||||||
private Integer value;
|
|
||||||
/**
|
|
||||||
* 状态名
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
DeletedStatusEnum(Integer value, String name) {
|
|
||||||
this.value = value;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.injector;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.mybatis.core.injector.method.InsertByBatch;
|
|
||||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
|
||||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义 Sql 注入器,继承 MybatisPlus 提供的默认注入器
|
|
||||||
* @author Hccake 2020/8/3
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class CustomSqlInject extends DefaultSqlInjector {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
|
|
||||||
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
|
|
||||||
methodList.add(new InsertByBatch());
|
|
||||||
return methodList;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.injector.method;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.mybatis.core.enums.CustomSqlMethodEnum;
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
|
|
||||||
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
|
|
||||||
import org.apache.ibatis.executor.keygen.KeyGenerator;
|
|
||||||
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
|
|
||||||
import org.apache.ibatis.mapping.MappedStatement;
|
|
||||||
import org.apache.ibatis.mapping.SqlSource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量插入
|
|
||||||
* @author Hccake 2020/8/3
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public class InsertByBatch extends AbstractMethod {
|
|
||||||
|
|
||||||
private final static String ITEM = "item";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
|
||||||
KeyGenerator keyGenerator = new NoKeyGenerator();
|
|
||||||
|
|
||||||
CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.INSERT_BATCH;
|
|
||||||
|
|
||||||
// ==== 拼接 sql 模板 ==============
|
|
||||||
StringBuilder columnScriptBuilder = new StringBuilder(LEFT_BRACKET);
|
|
||||||
StringBuilder valuesScriptBuilder = new StringBuilder(LEFT_BRACKET);
|
|
||||||
// 主键拼接
|
|
||||||
if (tableInfo.havePK()) {
|
|
||||||
columnScriptBuilder.append(tableInfo.getKeyColumn()).append(COMMA);
|
|
||||||
valuesScriptBuilder.append(SqlScriptUtils.safeParam(ITEM + DOT + tableInfo.getKeyProperty())).append(COMMA);
|
|
||||||
}
|
|
||||||
// 普通字段拼接
|
|
||||||
// PS:如有需要可在此实现插入字段过滤
|
|
||||||
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
|
|
||||||
for (TableFieldInfo fieldInfo : fieldList) {
|
|
||||||
columnScriptBuilder.append(fieldInfo.getColumn()).append(COMMA);
|
|
||||||
valuesScriptBuilder.append(SqlScriptUtils.safeParam(ITEM + DOT + fieldInfo.getProperty())).append(COMMA);
|
|
||||||
}
|
|
||||||
// 替换多余的逗号为括号
|
|
||||||
columnScriptBuilder.setCharAt(columnScriptBuilder.length() - 1, ')');
|
|
||||||
valuesScriptBuilder.setCharAt(valuesScriptBuilder.length() - 1, ')');
|
|
||||||
// sql 模板占位符替换
|
|
||||||
String columnScript = columnScriptBuilder.toString();
|
|
||||||
String valuesScript = valuesScriptBuilder.toString();
|
|
||||||
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
|
|
||||||
|
|
||||||
|
|
||||||
// === mybatis 主键逻辑处理:主键生成策略,以及主键回填=======
|
|
||||||
String keyColumn = null;
|
|
||||||
String keyProperty = null;
|
|
||||||
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
|
|
||||||
if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
|
|
||||||
if (tableInfo.getIdType() == IdType.AUTO) {
|
|
||||||
/** 自增主键 */
|
|
||||||
keyGenerator = new Jdbc3KeyGenerator();
|
|
||||||
keyProperty = tableInfo.getKeyProperty();
|
|
||||||
keyColumn = tableInfo.getKeyColumn();
|
|
||||||
} else {
|
|
||||||
if (null != tableInfo.getKeySequence()) {
|
|
||||||
keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(), tableInfo, builderAssistant);
|
|
||||||
keyProperty = tableInfo.getKeyProperty();
|
|
||||||
keyColumn = tableInfo.getKeyColumn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模板写入
|
|
||||||
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
|
|
||||||
return this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.mapper;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapper 层基类
|
|
||||||
* @author Hccake 2020/8/3
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public interface CommonMapper<T> extends BaseMapper<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量插入
|
|
||||||
* @param collection 批量插入数据
|
|
||||||
* @return ignore
|
|
||||||
*/
|
|
||||||
int insertByBatch(@Param("collection") Collection<T> collection);
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.query;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
|
|
||||||
*
|
|
||||||
* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
|
|
||||||
*
|
|
||||||
* @param <T> 数据类型
|
|
||||||
*/
|
|
||||||
public class QueryWrapperX<T> extends QueryWrapper<T> {
|
|
||||||
|
|
||||||
public QueryWrapperX<T> likeIfPresent(String column, String val) {
|
|
||||||
if (StringUtils.hasText(val)) {
|
|
||||||
return (QueryWrapperX<T>) super.like(column, val);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
|
|
||||||
if (!CollectionUtils.isEmpty(values)) {
|
|
||||||
return (QueryWrapperX<T>) super.in(column, values);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryWrapperX<T> inIfPresent(String column, Object... values) {
|
|
||||||
if (!ArrayUtils.isEmpty(values)) {
|
|
||||||
return (QueryWrapperX<T>) super.in(column, values);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryWrapperX<T> eqIfPresent(String column, Object val) {
|
|
||||||
if (val != null) {
|
|
||||||
return (QueryWrapperX<T>) super.eq(column, val);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryWrapperX<T> gtIfPresent(String column, Object val) {
|
|
||||||
if (val != null) {
|
|
||||||
return (QueryWrapperX<T>) super.gt(column, val);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
|
|
||||||
if (val1 != null && val2 != null) {
|
|
||||||
return (QueryWrapperX<T>) super.between(column, val1, val2);
|
|
||||||
}
|
|
||||||
if (val1 != null) {
|
|
||||||
return (QueryWrapperX<T>) ge(column, val1);
|
|
||||||
}
|
|
||||||
if (val2 != null) {
|
|
||||||
return (QueryWrapperX<T>) le(column, val2);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 重写父类方法,方便链式调用 ==========
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryWrapperX<T> eq(boolean condition, String column, Object val) {
|
|
||||||
super.eq(condition, column, val);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryWrapperX<T> eq(String column, Object val) {
|
|
||||||
super.eq(column, val);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryWrapperX<T> orderByDesc(String column) {
|
|
||||||
super.orderByDesc(true, column);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryWrapperX<T> last(String lastSql) {
|
|
||||||
super.last(lastSql);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.type;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import org.apache.ibatis.type.BaseTypeHandler;
|
|
||||||
import org.apache.ibatis.type.JdbcType;
|
|
||||||
|
|
||||||
import java.sql.CallableStatement;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO 芋艿
|
|
||||||
*
|
|
||||||
* 参考 https://www.cnblogs.com/waterystone/p/5547254.html
|
|
||||||
*
|
|
||||||
* 后续,补充下注释和测试类,以及文章。
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
public class JSONTypeHandler<T extends Object> extends BaseTypeHandler<T> {
|
|
||||||
|
|
||||||
private Class<T> clazz;
|
|
||||||
|
|
||||||
public JSONTypeHandler(Class<T> clazz) {
|
|
||||||
if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
|
|
||||||
this.clazz = clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
|
|
||||||
ps.setString(i, this.toJson(parameter));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
|
||||||
return this.toObject(rs.getString(columnName), clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
|
||||||
return this.toObject(rs.getString(columnIndex), clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
|
||||||
return this.toObject(cs.getString(columnIndex), clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toJson(T object) {
|
|
||||||
try {
|
|
||||||
return JSON.toJSONString(object);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private T toObject(String content, Class<?> clazz) {
|
|
||||||
if (content != null && !content.isEmpty()) {
|
|
||||||
try {
|
|
||||||
return (T) JSON.parseObject(content, clazz);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package cn.iocoder.mall.mybatis.core.util;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
|
||||||
import cn.iocoder.common.framework.vo.PageParam;
|
|
||||||
import cn.iocoder.common.framework.vo.SortingField;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页工具里
|
|
||||||
*
|
|
||||||
* 目前主要用于 {@link Page} 的构建
|
|
||||||
*/
|
|
||||||
public class PageUtil {
|
|
||||||
|
|
||||||
public static <T> Page<T> build(PageParam pageParam) {
|
|
||||||
return build(pageParam, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Page<T> build(PageParam pageParam, Collection<SortingField> sortingFields) {
|
|
||||||
// 页码 + 数量
|
|
||||||
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
|
|
||||||
// 排序字段
|
|
||||||
if (!CollectionUtils.isEmpty(sortingFields)) {
|
|
||||||
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
|
|
||||||
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|
||||||
cn.iocoder.mall.mybatis.config.MybatisPlusAutoConfiguration
|
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>common</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-redis</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.redisson</groupId>
|
|
||||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,71 +0,0 @@
|
|||||||
package cn.iocoder.mall.redis.core;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redis Key 定义类
|
|
||||||
*/
|
|
||||||
public class RedisKeyDefine {
|
|
||||||
|
|
||||||
public enum KeyTypeEnum {
|
|
||||||
|
|
||||||
STRING,
|
|
||||||
LIST,
|
|
||||||
HASH,
|
|
||||||
SET,
|
|
||||||
ZSET,
|
|
||||||
STREAM,
|
|
||||||
PUBSUB;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过期时间 - 永不过期
|
|
||||||
*/
|
|
||||||
public static final Duration TIMEOUT_FOREVER = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key 模板
|
|
||||||
*/
|
|
||||||
private final String keyTemplate;
|
|
||||||
/**
|
|
||||||
* Key 类型的枚举
|
|
||||||
*/
|
|
||||||
private final KeyTypeEnum keyType;
|
|
||||||
/**
|
|
||||||
* Value 类型
|
|
||||||
*
|
|
||||||
* 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型
|
|
||||||
*/
|
|
||||||
private final Class<?> valueType;
|
|
||||||
/**
|
|
||||||
* 过期时间
|
|
||||||
*
|
|
||||||
* 为空时,表示永不过期 {@link #TIMEOUT_FOREVER}
|
|
||||||
*/
|
|
||||||
private final Duration timeout;
|
|
||||||
|
|
||||||
public RedisKeyDefine(String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
|
|
||||||
this.keyTemplate = keyTemplate;
|
|
||||||
this.keyType = keyType;
|
|
||||||
this.valueType = valueType;
|
|
||||||
this.timeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyTemplate() {
|
|
||||||
return keyTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyTypeEnum getKeyType() {
|
|
||||||
return keyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> getValueType() {
|
|
||||||
return valueType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Duration getTimeout() {
|
|
||||||
return timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>common</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-rocketmq</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- MQ 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.rocketmq</groupId>
|
|
||||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>common</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-web</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- Mall 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<artifactId>system-service-api</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Spring 核心 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Web 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- RPC 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.dubbo</groupId>
|
|
||||||
<artifactId>dubbo</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 工具相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>fastjson</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,96 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.config;
|
|
||||||
|
|
||||||
import cn.iocoder.mall.web.core.handler.GlobalExceptionHandler;
|
|
||||||
import cn.iocoder.mall.web.core.handler.GlobalResponseBodyHandler;
|
|
||||||
import cn.iocoder.mall.web.core.interceptor.AccessLogInterceptor;
|
|
||||||
import cn.iocoder.mall.web.core.servlet.CorsFilter;
|
|
||||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
|
||||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
|
||||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
|
||||||
public class CommonWebAutoConfiguration implements WebMvcConfigurer {
|
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
|
||||||
|
|
||||||
// ========== 全局处理器 ==========
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
|
|
||||||
public GlobalResponseBodyHandler globalResponseBodyHandler() {
|
|
||||||
return new GlobalResponseBodyHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
|
|
||||||
public GlobalExceptionHandler globalExceptionHandler() {
|
|
||||||
return new GlobalExceptionHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 拦截器相关 ==========
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnClass(name = {"cn.iocoder.mall.systemservice.rpc.systemlog.SystemExceptionLogFeign", "org.apache.dubbo.config.annotation.Reference"})
|
|
||||||
@ConditionalOnMissingBean(AccessLogInterceptor.class)
|
|
||||||
public AccessLogInterceptor accessLogInterceptor() {
|
|
||||||
return new AccessLogInterceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
|
||||||
try {
|
|
||||||
registry.addInterceptor(this.accessLogInterceptor());
|
|
||||||
logger.info("[addInterceptors][加载 AccessLogInterceptor 拦截器完成]");
|
|
||||||
} catch (NoSuchBeanDefinitionException e) {
|
|
||||||
logger.warn("[addInterceptors][无法获取 AccessLogInterceptor 拦截器,因此不启动 AccessLog 的记录]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 过滤器相关 ==========
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public FilterRegistrationBean<CorsFilter> corsFilter() {
|
|
||||||
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
|
|
||||||
registrationBean.setFilter(new CorsFilter());
|
|
||||||
registrationBean.addUrlPatterns("/*");
|
|
||||||
return registrationBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== MessageConverter 相关 ==========
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
|
||||||
// 创建 FastJsonHttpMessageConverter 对象
|
|
||||||
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
|
|
||||||
// 自定义 FastJson 配置
|
|
||||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
|
||||||
fastJsonConfig.setCharset(Charset.defaultCharset()); // 设置字符集
|
|
||||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect, // 剔除循环引用
|
|
||||||
SerializerFeature.WriteNonStringKeyAsString); // 解决 Integer 作为 Key 时,转换为 String 类型,避免浏览器报错
|
|
||||||
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
|
|
||||||
// 设置支持的 MediaType
|
|
||||||
fastJsonHttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
|
|
||||||
// 添加到 converters 中
|
|
||||||
converters.add(0, fastJsonHttpMessageConverter); // 注意,添加到最开头,放在 MappingJackson2XmlHttpMessageConverter 前面
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.constant;
|
|
||||||
|
|
||||||
public interface CommonMallConstants {
|
|
||||||
|
|
||||||
// 全局请求路径枚举类,用于定义不同用户类型的根请求路径
|
|
||||||
/**
|
|
||||||
* 根路径 - 用户
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
String ROOT_PATH_USER = "/users";
|
|
||||||
/**
|
|
||||||
* 根路径 - 管理员
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
String ROOT_PATH_ADMIN = "/admins";
|
|
||||||
|
|
||||||
// HTTP Request Attr
|
|
||||||
/**
|
|
||||||
* HTTP Request Attr - 用户编号
|
|
||||||
*
|
|
||||||
* 考虑到 mall-spring-boot-starter-web 不依赖 mall-spring-boot-starter-security,但是又希望拿到认证过的用户编号,
|
|
||||||
* 因此通过 Request 的 Attribute 进行共享
|
|
||||||
*/
|
|
||||||
String REQUEST_ATTR_USER_ID_KEY = "mall_user_id";
|
|
||||||
/**
|
|
||||||
* HTTP Request Attr - 用户类型
|
|
||||||
*
|
|
||||||
* 作用同 {@link #REQUEST_ATTR_USER_ID_KEY}
|
|
||||||
*/
|
|
||||||
String REQUEST_ATTR_USER_TYPE_KEY = "mall_user_type";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP Request Attr - Controller 执行返回
|
|
||||||
*
|
|
||||||
* 通过该 Request 的 Attribute,获取到请求执行结果,从而在访问日志中,记录是否成功。
|
|
||||||
*/
|
|
||||||
String REQUEST_ATTR_COMMON_RESULT = "mall_common_result";
|
|
||||||
/**
|
|
||||||
* HTTP Request Attr - 访问开始时间
|
|
||||||
*/
|
|
||||||
String REQUEST_ATTR_ACCESS_START_TIME = "mall_access_start_time";
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,253 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.exception.GlobalException;
|
|
||||||
import cn.iocoder.common.framework.exception.ServiceException;
|
|
||||||
import cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants;
|
|
||||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
|
||||||
import cn.iocoder.common.framework.util.HttpUtil;
|
|
||||||
import cn.iocoder.common.framework.util.MallUtils;
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
|
||||||
import cn.iocoder.mall.systemservice.rpc.systemlog.SystemExceptionLogFeign;
|
|
||||||
import cn.iocoder.mall.systemservice.rpc.systemlog.dto.SystemExceptionLogCreateDTO;
|
|
||||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.validation.BindException;
|
|
||||||
import org.springframework.validation.FieldError;
|
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
||||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
||||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
|
||||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.validation.ConstraintViolation;
|
|
||||||
import javax.validation.ConstraintViolationException;
|
|
||||||
import javax.validation.ValidationException;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
|
||||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
|
|
||||||
*/
|
|
||||||
@RestControllerAdvice
|
|
||||||
public class GlobalExceptionHandler {
|
|
||||||
|
|
||||||
// TODO 芋艿,应该还有其它的异常,需要进行翻译
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 异常总数 Metrics
|
|
||||||
// */
|
|
||||||
// private static final Counter EXCEPTION_COUNTER = Metrics.counter("mall.exception.total");
|
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
|
||||||
|
|
||||||
@Value("${spring.application.name}")
|
|
||||||
private String applicationName;
|
|
||||||
|
|
||||||
// TODO 目前存在一个问题,如果未引入 system-rpc-api 依赖,GlobalExceptionHandler 会报类不存在。未来封装出 Repository 解决该问题
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SystemExceptionLogFeign systemExceptionLogFeign;
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 请求参数缺失
|
|
||||||
*
|
|
||||||
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = MissingServletRequestParameterException.class)
|
|
||||||
public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
|
|
||||||
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 请求参数类型错误
|
|
||||||
*
|
|
||||||
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
|
||||||
public CommonResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
|
|
||||||
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 参数校验不正确
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
||||||
public CommonResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
|
|
||||||
logger.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
|
|
||||||
FieldError fieldError = ex.getBindingResult().getFieldError();
|
|
||||||
assert fieldError != null; // 断言,避免告警
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(BindException.class)
|
|
||||||
public CommonResult bindExceptionHandler(BindException ex) {
|
|
||||||
logger.warn("[handleBindException]", ex);
|
|
||||||
FieldError fieldError = ex.getFieldError();
|
|
||||||
assert fieldError != null; // 断言,避免告警
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 Validator 校验不通过产生的异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = ConstraintViolationException.class)
|
|
||||||
public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
|
||||||
logger.warn("[constraintViolationExceptionHandler]", ex);
|
|
||||||
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 请求地址不存在
|
|
||||||
*
|
|
||||||
* 注意,它需要设置如下两个配置项:
|
|
||||||
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
|
|
||||||
* 2. spring.mvc.static-path-pattern 为 /statics/**
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(NoHandlerFoundException.class)
|
|
||||||
public CommonResult noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
|
|
||||||
logger.warn("[noHandlerFoundExceptionHandler]", ex);
|
|
||||||
return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 SpringMVC 请求方法不正确
|
|
||||||
*
|
|
||||||
* 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
|
||||||
public CommonResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
|
|
||||||
logger.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
|
|
||||||
return CommonResult.error(GlobalErrorCodeConstants.METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()))
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理业务异常 ServiceException
|
|
||||||
*
|
|
||||||
* 例如说,商品库存不足,用户手机号已存在。
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = ServiceException.class)
|
|
||||||
public CommonResult serviceExceptionHandler(ServiceException ex) {
|
|
||||||
logger.info("[serviceExceptionHandler]", ex);
|
|
||||||
return CommonResult.error(ex.getCode(), ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理全局异常 ServiceException
|
|
||||||
*
|
|
||||||
* 例如说,Dubbo 请求超时,调用的 Dubbo 服务系统异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = GlobalException.class)
|
|
||||||
public CommonResult globalExceptionHandler(HttpServletRequest req, GlobalException ex) {
|
|
||||||
// 系统异常时,才打印异常日志
|
|
||||||
if (INTERNAL_SERVER_ERROR.getCode().equals(ex.getCode())) {
|
|
||||||
// 插入异常日志
|
|
||||||
this.createExceptionLog(req, ex);
|
|
||||||
// 普通全局异常,打印 info 日志即可
|
|
||||||
} else {
|
|
||||||
logger.info("[globalExceptionHandler]", ex);
|
|
||||||
}
|
|
||||||
// 返回 ERROR CommonResult
|
|
||||||
return CommonResult.error(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = ValidationException.class)
|
|
||||||
public CommonResult validationException(ValidationException ex) {
|
|
||||||
logger.warn("[constraintViolationExceptionHandler]", ex);
|
|
||||||
// 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
|
|
||||||
return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确")
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理系统异常,兜底处理所有的一切
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(value = Exception.class)
|
|
||||||
public CommonResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
|
|
||||||
logger.error("[defaultExceptionHandler]", ex);
|
|
||||||
// 插入异常日志
|
|
||||||
this.createExceptionLog(req, ex);
|
|
||||||
// 返回 ERROR CommonResult
|
|
||||||
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage())
|
|
||||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createExceptionLog(HttpServletRequest req, Throwable e) {
|
|
||||||
// 插入异常日志
|
|
||||||
SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO();
|
|
||||||
try {
|
|
||||||
// 增加异常计数 metrics TODO 暂时去掉
|
|
||||||
// EXCEPTION_COUNTER.increment();
|
|
||||||
// 初始化 exceptionLog
|
|
||||||
initExceptionLog(exceptionLog, req, e);
|
|
||||||
// 执行插入 exceptionLog
|
|
||||||
createExceptionLog(exceptionLog);
|
|
||||||
} catch (Throwable th) {
|
|
||||||
logger.error("[createExceptionLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO 优化点:后续可以增加事件
|
|
||||||
@Async
|
|
||||||
public void createExceptionLog(SystemExceptionLogCreateDTO exceptionLog) {
|
|
||||||
try {
|
|
||||||
systemExceptionLogFeign.createSystemExceptionLog(exceptionLog);
|
|
||||||
} catch (Throwable th) {
|
|
||||||
logger.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) {
|
|
||||||
// 设置账号编号
|
|
||||||
exceptionLog.setUserId(CommonWebUtil.getUserId(request));
|
|
||||||
exceptionLog.setUserType(CommonWebUtil.getUserType(request));
|
|
||||||
// 设置异常字段
|
|
||||||
exceptionLog.setExceptionName(e.getClass().getName());
|
|
||||||
exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
|
|
||||||
exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
|
|
||||||
exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e));
|
|
||||||
StackTraceElement[] stackTraceElements = e.getStackTrace();
|
|
||||||
Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
|
|
||||||
StackTraceElement stackTraceElement = stackTraceElements[0];
|
|
||||||
exceptionLog.setExceptionClassName(stackTraceElement.getClassName());
|
|
||||||
exceptionLog.setExceptionFileName(stackTraceElement.getFileName());
|
|
||||||
exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName());
|
|
||||||
exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
|
|
||||||
// 设置其它字段
|
|
||||||
exceptionLog.setTraceId(MallUtils.getTraceId())
|
|
||||||
.setApplicationName(applicationName)
|
|
||||||
.setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
|
||||||
.setQueryString(HttpUtil.buildQueryString(request))
|
|
||||||
.setMethod(request.getMethod())
|
|
||||||
.setUserAgent(HttpUtil.getUserAgent(request))
|
|
||||||
.setIp(HttpUtil.getIp(request))
|
|
||||||
.setExceptionTime(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
|
||||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.ServerHttpResponse;
|
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局响应结果(ResponseBody)处理器
|
|
||||||
*
|
|
||||||
* 不同于在网上看到的很多文章,会选择自动将 Controller 返回结果包上 {@link CommonResult},
|
|
||||||
* 在 onemall 中,是 Controller 在返回时,主动自己包上 {@link CommonResult}。
|
|
||||||
* 原因是,GlobalResponseBodyHandler 本质上是 AOP,它不应该改变 Controller 返回的数据结构
|
|
||||||
*
|
|
||||||
* 目前,GlobalResponseBodyHandler 的主要作用是,记录 Controller 的返回结果,
|
|
||||||
* 方便 {@link cn.iocoder.mall.web.core.interceptor.AccessLogInterceptor} 记录访问日志
|
|
||||||
*/
|
|
||||||
@ControllerAdvice
|
|
||||||
public class GlobalResponseBodyHandler implements ResponseBodyAdvice {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("NullableProblems") // 避免 IDEA 警告
|
|
||||||
public boolean supports(MethodParameter returnType, Class converterType) {
|
|
||||||
if (returnType.getMethod() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 只拦截返回结果为 CommonResult 类型
|
|
||||||
return returnType.getMethod().getReturnType() == CommonResult.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("NullableProblems") // 避免 IDEA 警告
|
|
||||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
|
|
||||||
ServerHttpRequest request, ServerHttpResponse response) {
|
|
||||||
// 记录 Controller 结果
|
|
||||||
CommonWebUtil.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult) body);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.interceptor;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.util.HttpUtil;
|
|
||||||
import cn.iocoder.common.framework.util.MallUtils;
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
|
||||||
import cn.iocoder.mall.systemservice.rpc.systemlog.SystemAccessLogFeign;
|
|
||||||
import cn.iocoder.mall.systemservice.rpc.systemlog.dto.SystemAccessLogCreateDTO;
|
|
||||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问日志拦截器
|
|
||||||
*/
|
|
||||||
public class AccessLogInterceptor extends HandlerInterceptorAdapter {
|
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SystemAccessLogFeign systemAccessLogFeign;
|
|
||||||
@Value("${spring.application.name}")
|
|
||||||
private String applicationName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
|
||||||
// 记录当前时间
|
|
||||||
CommonWebUtil.setAccessStartTime(request, new Date());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
|
||||||
SystemAccessLogCreateDTO accessLog = new SystemAccessLogCreateDTO();
|
|
||||||
try {
|
|
||||||
// 初始化 accessLog
|
|
||||||
initAccessLog(accessLog, request);
|
|
||||||
// 执行插入 accessLog
|
|
||||||
addAccessLog(accessLog);
|
|
||||||
// TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
|
|
||||||
} catch (Throwable th) {
|
|
||||||
logger.error("[afterCompletion][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAccessLog(SystemAccessLogCreateDTO accessLog, HttpServletRequest request) {
|
|
||||||
// 设置账号编号
|
|
||||||
accessLog.setUserId(CommonWebUtil.getUserId(request));
|
|
||||||
accessLog.setUserType(CommonWebUtil.getUserType(request));
|
|
||||||
// 设置访问结果
|
|
||||||
CommonResult result = CommonWebUtil.getCommonResult(request);
|
|
||||||
if (result != null) {
|
|
||||||
accessLog.setErrorCode(result.getCode()).setErrorMessage(result.getMessage());
|
|
||||||
} else {
|
|
||||||
// 在访问非 onemall 系统提供的 API 时,会存在没有 CommonResult 的情况。例如说,Swagger 提供的接口
|
|
||||||
accessLog.setErrorCode(0).setErrorMessage("");
|
|
||||||
}
|
|
||||||
// 设置其它字段
|
|
||||||
accessLog.setTraceId(MallUtils.getTraceId())
|
|
||||||
.setApplicationName(applicationName)
|
|
||||||
.setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
|
||||||
.setQueryString(HttpUtil.buildQueryString(request))
|
|
||||||
.setMethod(request.getMethod())
|
|
||||||
.setUserAgent(HttpUtil.getUserAgent(request))
|
|
||||||
.setIp(HttpUtil.getIp(request))
|
|
||||||
.setStartTime(CommonWebUtil.getAccessStartTime(request))
|
|
||||||
.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime())); // 默认响应时间设为 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO 优化点:后续可以增加事件
|
|
||||||
@Async // 异步入库
|
|
||||||
public void addAccessLog(SystemAccessLogCreateDTO accessLog) {
|
|
||||||
try {
|
|
||||||
systemAccessLogFeign.createSystemAccessLog(accessLog);
|
|
||||||
} catch (Throwable th) {
|
|
||||||
logger.error("[addAccessLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.servlet;
|
|
||||||
|
|
||||||
import javax.servlet.*;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cors 过滤器
|
|
||||||
*
|
|
||||||
* 未来使用 {@link org.springframework.web.filter.CorsFilter} 替换
|
|
||||||
*/
|
|
||||||
public class CorsFilter implements Filter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(FilterConfig filterConfig) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
|
||||||
HttpServletResponse resp = (HttpServletResponse) response;
|
|
||||||
resp.setHeader("Access-Control-Allow-Origin", "*");
|
|
||||||
resp.setHeader("Access-Control-Allow-Methods", "*");
|
|
||||||
resp.setHeader("Access-Control-Allow-Headers", "*");
|
|
||||||
resp.setHeader("Access-Control-Max-Age", "1800");
|
|
||||||
// For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake
|
|
||||||
// 例如说,vue axios 请求时,会自带该逻辑的
|
|
||||||
HttpServletRequest req = (HttpServletRequest) request;
|
|
||||||
if (req.getMethod().equals("OPTIONS")) {
|
|
||||||
resp.setStatus(HttpServletResponse.SC_ACCEPTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 如果是其它请求方法,则继续过滤器。
|
|
||||||
chain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package cn.iocoder.mall.web.core.util;
|
|
||||||
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
|
||||||
import cn.iocoder.mall.web.core.constant.CommonMallConstants;
|
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class CommonWebUtil {
|
|
||||||
|
|
||||||
public static Integer getUserId(ServletRequest request) {
|
|
||||||
return (Integer) request.getAttribute(CommonMallConstants.REQUEST_ATTR_USER_ID_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setUserId(ServletRequest request, Integer userId) {
|
|
||||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_USER_ID_KEY, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Integer getUserType(ServletRequest request) {
|
|
||||||
return (Integer) request.getAttribute(CommonMallConstants.REQUEST_ATTR_USER_TYPE_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setUserType(ServletRequest request, Integer userType) {
|
|
||||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_USER_TYPE_KEY, userType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CommonResult getCommonResult(ServletRequest request) {
|
|
||||||
return (CommonResult) request.getAttribute(CommonMallConstants.REQUEST_ATTR_COMMON_RESULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setCommonResult(ServletRequest request, CommonResult result) {
|
|
||||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_COMMON_RESULT, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setAccessStartTime(ServletRequest request, Date startTime) {
|
|
||||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_ACCESS_START_TIME, startTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Date getAccessStartTime(ServletRequest request) {
|
|
||||||
return (Date) request.getAttribute(CommonMallConstants.REQUEST_ATTR_ACCESS_START_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|
||||||
cn.iocoder.mall.web.config.CommonWebAutoConfiguration
|
|
Loading…
Reference in New Issue
Block a user