diff --git a/hiver-admin/pom.xml b/hiver-admin/pom.xml
index fb8d5b75..5aea8b10 100644
--- a/hiver-admin/pom.xml
+++ b/hiver-admin/pom.xml
@@ -66,6 +66,24 @@
org.springframework.boot
spring-boot-starter-actuator
+
+ io.netty
+ netty-resolver-dns-native-macos
+ 4.1.77.Final
+ osx-aarch_64
+
+
+
+ org.bgee.log4jdbc-log4j2
+ log4jdbc-log4j2-jdbc4.1
+ 1.16
+
+
+ top.javatool
+ canal-spring-boot-starter
+ 1.2.1-RELEASE
+
+
diff --git a/hiver-admin/src/main/resources/application.yml b/hiver-admin/src/main/resources/application.yml
index 89141df5..ab92fae3 100644
--- a/hiver-admin/src/main/resources/application.yml
+++ b/hiver-admin/src/main/resources/application.yml
@@ -23,12 +23,15 @@ spring:
timeout-per-shutdown-phase: 10S
# 数据源
datasource:
- url: jdbc:mysql://154.8.162.157:3306/hiver_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
+ url: jdbc:log4jdbc:mysql://154.8.162.157:3306/hiver_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
+ #url: jdbc:mysql://154.8.162.157:3306/hiver_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: reddoor
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果) 以下解密结果为123456
password: ENC(Zla4U4+yRLPhicvuX2TmiEgxEpzP4dk8BHzFDEtiEhwLQIIaftZrrEUJZce6efoe)
type: com.alibaba.druid.pool.DruidDataSource
- driver-class-name: com.mysql.jdbc.Driver
+ driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
+ #driver-class-name: com.mysql.jdbc.Driver
+
# Druid StatViewServlet配置
druid:
stat-view-servlet:
@@ -58,10 +61,11 @@ spring:
multi-statement-allow: true
jpa:
# 显示sql
- show-sql: true
+ #show-sql: true
# 自动生成表结构 关闭设为none
hibernate:
ddl-auto: update
+ database-platform: org.hibernate.dialect.MySQL5InnoDBDialect #设置数据库方言 记住必须要使用 MySQL5InnoDBDialect 指定数据库类型对应InnoDB ;如果使用MySQLDialect 则对应的是MyISAM
# Redis 若设有密码自行添加配置password
redis:
host: 154.8.162.157
@@ -70,6 +74,16 @@ spring:
port: 6379
# 超时时间 Duration类型 3秒
timeout: 3S
+ jedis:
+ pool:
+ max-active: 8
+ max-wait: -1
+ max-idle: 8
+ min-idle: 0
+ #Canal服务用于数据同步
+ canal:
+ server: 154.8.162.157:11111
+ destination: example
# Elasticsearch
data:
elasticsearch:
@@ -97,7 +111,7 @@ spring:
# 加锁调度
acquireTriggersWithinLock: true
# “容忍”触发器经过下一次触发时间的毫秒数
- misfireThreshold: 10000
+ misfireThreshold: 500000
servlet:
multipart:
max-file-size: -1
@@ -304,8 +318,10 @@ management:
OUT_OF_SERVICE: 200
FATAL: 200
UNKNOWN: 200
+ enabled: false
endpoints:
web:
+ enabled-by-default: false #关闭所有监控端点
base-path: /hiver/actuator/
exposure:
include: '*'
@@ -336,15 +352,16 @@ mybatis-plus:
# 日志
logging:
- # 输出级别
- level:
- root: info
- file:
- # 指定路径
- path: hiver-logs
- logback:
- rollingpolicy:
- # 最大保存天数
- max-history: 7
- # 每个文件最大大小
- max-file-size: 5MB
\ No newline at end of file
+ config: classpath:logback.xml
+# 输出级别
+# level:
+# root: info
+# file:
+# # 指定路径
+# path: hiver-logs
+# logback:
+# rollingpolicy:
+# # 最大保存天数
+# max-history: 7
+# # 每个文件最大大小
+# max-file-size: 5MB
\ No newline at end of file
diff --git a/hiver-admin/src/main/resources/logback.xml b/hiver-admin/src/main/resources/logback.xml
new file mode 100644
index 00000000..c0480f43
--- /dev/null
+++ b/hiver-admin/src/main/resources/logback.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+ logback
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ debug
+
+
+ ${CONSOLE_LOG_PATTERN}
+
+ UTF-8
+
+
+
+
+
+
+
+ ${OPEN_FILE_PATH}/log_debug.log
+
+ ${OPEN_FILE_PATH}/debug/${FILE_PREFIX}-debug_%d{yyyy-MM-dd}-%i.log
+ 30
+
+ ${MAX_FILE_SIZE}
+
+
+ true
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
+ utf-8
+
+
+
+
+ DEBUG
+
+ ACCEPT
+
+ DENY
+
+
+
+
+
+
+ ${OPEN_FILE_PATH}/log_info.log
+
+ ${OPEN_FILE_PATH}/info/${FILE_PREFIX}_%d{yyyy-MM-dd}-%i.log
+ 30
+
+ ${MAX_FILE_SIZE}
+
+
+ true
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
+ utf-8
+
+
+
+ INFO
+
+ ACCEPT
+
+ DENY
+
+
+
+
+
+
+ ${OPEN_FILE_PATH}/log_warn.log
+
+ ${OPEN_FILE_PATH}/warn/${FILE_PREFIX}_%d{yyyy-MM-dd}-%i.log
+ 30
+
+ ${MAX_FILE_SIZE}
+
+
+ true
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
+ utf-8
+
+
+
+ WARN
+
+ ACCEPT
+
+ DENY
+
+
+
+
+
+
+ ${OPEN_FILE_PATH}/log_error.log
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
+ UTF-8
+
+
+
+ ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 15
+
+
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hiver-core/pom.xml b/hiver-core/pom.xml
index f3039e4e..04253911 100644
--- a/hiver-core/pom.xml
+++ b/hiver-core/pom.xml
@@ -138,5 +138,9 @@
org.seleniumhq.selenium
selenium-java
+
+ com.github.ben-manes.caffeine
+ caffeine
+
\ No newline at end of file
diff --git a/hiver-core/src/main/java/cc/hiver/core/config/cache/CustomCacheResolver.java b/hiver-core/src/main/java/cc/hiver/core/config/cache/CustomCacheResolver.java
new file mode 100644
index 00000000..d090170f
--- /dev/null
+++ b/hiver-core/src/main/java/cc/hiver/core/config/cache/CustomCacheResolver.java
@@ -0,0 +1,62 @@
+package cc.hiver.core.config.cache;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.CacheOperationInvocationContext;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class CustomCacheResolver implements CacheResolver, InitializingBean {
+
+ @Nullable
+ private List cacheManagerList;
+
+ public CustomCacheResolver(){
+ }
+ public CustomCacheResolver(List cacheManagerList){
+ this.cacheManagerList = cacheManagerList;
+ }
+
+ public void setCacheManagerList(@Nullable List cacheManagerList) {
+ this.cacheManagerList = cacheManagerList;
+ }
+ public List getCacheManagerList() {
+ return cacheManagerList;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ Assert.notNull(this.cacheManagerList, "CacheManager is required");
+ }
+
+ @Override
+ public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
+ Collection cacheNames = getCacheNames(context);
+ if (cacheNames == null) {
+ return Collections.emptyList();
+ }
+ Collection result = new ArrayList<>();
+ for(CacheManager cacheManager : getCacheManagerList()){
+ for (String cacheName : cacheNames) {
+ Cache cache = cacheManager.getCache(cacheName);
+ if (cache == null) {
+ throw new IllegalArgumentException("Cannot find cache named '" +
+ cacheName + "' for " + context.getOperation());
+ }
+ result.add(cache);
+ }
+ }
+ return result;
+ }
+
+ private Collection getCacheNames(CacheOperationInvocationContext> context){
+ return context.getOperation().getCacheNames();
+ }
+}
diff --git a/hiver-core/src/main/java/cc/hiver/core/config/cache/RedisCacheConfig.java b/hiver-core/src/main/java/cc/hiver/core/config/cache/RedisCacheConfig.java
index d7a207e0..74199ca0 100644
--- a/hiver-core/src/main/java/cc/hiver/core/config/cache/RedisCacheConfig.java
+++ b/hiver-core/src/main/java/cc/hiver/core/config/cache/RedisCacheConfig.java
@@ -4,22 +4,27 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
+import org.springframework.cache.interceptor.CacheResolver;
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.RedisConnectionFactory;
-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 org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.*;
+import java.io.Serializable;
import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
/**
* Redis缓存
@@ -32,14 +37,48 @@ public class RedisCacheConfig extends CachingConfigurerSupport {
@Value("${hiver.cache.timeToLive:-1}")
private Duration timeToLive;
+ @Autowired
+ RedisConnectionFactory factory;
+
+
+ @Override
+ public CacheResolver cacheResolver() {
+ // 通过Guava实现的自定义堆内存缓存管理器
+ CacheManager caffeineCacheManager = new CaffeineCacheManager();
+ CacheManager redisCacheManager = cacheManager();
+ List list = new ArrayList<>();
+ // 优先读取堆内存缓存
+ list.add(caffeineCacheManager);
+ // 堆内存缓存读取不到该key时再读取redis缓存
+ list.add(redisCacheManager);
+ return new CustomCacheResolver(list);
+ }
+
+
+ @Bean
+ public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory){
+ //初始化一个redis模板
+ RedisTemplate template = new RedisTemplate<>();
+ // key采用String的序列化方式
+ template.setKeySerializer(new StringRedisSerializer());
+ // value序列化方式采用jackson
+ template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+ // hash的key也采用String的序列化方式
+ template.setHashKeySerializer(new StringRedisSerializer());
+ // hash的value序列化方式采用jackson
+ template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+ template.setConnectionFactory(redisConnectionFactory);
+ return template;
+ }
+
/**
* 自定义序列化方式
*
- * @param factory
+ * @param
* @return
*/
@Bean
- public CacheManager cacheManager(RedisConnectionFactory factory) {
+ public CacheManager cacheManager() {
RedisSerializer redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
@@ -92,4 +131,5 @@ public class RedisCacheConfig extends CachingConfigurerSupport {
};
return cacheErrorHandler;
}
+
}
diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/OrderController.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/OrderController.java
index d02bbad9..6dd0711e 100644
--- a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/OrderController.java
+++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/OrderController.java
@@ -41,6 +41,7 @@ import java.util.stream.Collectors;
*
* @author Yazhi Li
*/
+
@Slf4j
@RestController
@Api(tags = "订单接口")
@@ -117,6 +118,13 @@ public class OrderController {
* 1.当前抢单工的抢单状态需进行查验,非正常无法进行抢单
* 2.正常状态下,判定当前抢单工押金余额是否充足,不足时需要将当前扛包工状态自动修正为不可接单状态,同时当前订单无法抢购。
* 3.正常抢单情况下,需要在redis中设置抢单后的剩余押金金额,要存在抢单锁机制,实时更新余额
+ *
+ * 计划暂定使用延时双删策略,尽量保证最终一致性,但不是强一致性:
+ * 先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除。
+ * (延迟N秒)的时间要大于一次写操作的时间,一般为3-5秒。
+ * 原因:如果延迟时间小于写入redis的时间,会导致请求1清除了缓存,但是请求2缓存还未写入的尴尬。。。
+ * ps:一般写入的时间会远小于5秒
+ *
*/
@RequestMapping(value = "/rush/order/{orderId}", method = RequestMethod.POST)
@ApiOperation(value = "抢单接口", notes = "需要通过下单编号获取订单信息后进行绑定")
diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/handler/OrderXdHandler.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/handler/OrderXdHandler.java
new file mode 100644
index 00000000..a149c3e6
--- /dev/null
+++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/handler/OrderXdHandler.java
@@ -0,0 +1,41 @@
+package cc.hiver.base.handler;
+
+import cc.hiver.core.entity.OrderXd;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import top.javatool.canal.client.annotation.CanalTable;
+import top.javatool.canal.client.handler.EntryHandler;
+
+/**
+ * @author benjamin_5
+ * @Description
+ * @date 2022/9/25
+ */
+@CanalTable("bs_ecif")
+@Component
+@AllArgsConstructor
+@Slf4j
+public class OrderXdHandler implements EntryHandler {
+
+ private final RedisTemplate