清理冗余的代码

This commit is contained in:
YunaiV 2022-06-11 21:54:30 +08:00
parent ea7ad4b1ca
commit 0cf1738c0a
30 changed files with 0 additions and 1678 deletions

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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=

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.mybatis.config.MybatisPlusAutoConfiguration

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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 前面
}
}

View File

@ -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";
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.web.config.CommonWebAutoConfiguration