Toggle navigation
Toggle navigation
This project
Loading...
Sign in
jiujian
/
client-customer-order-api
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Jiaqi Xia
2025-09-29 16:10:55 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
967fdec2f8c44b2e1869c53e0c2b15f112b6909d
967fdec2
1 parent
4c308216
feat(cache): add cache dependencies and configuration for Redis and Caffeine
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
1328 additions
and
0 deletions
build.gradle
src/main/java/com/infoloop/tianting/cache/CacheAutoConfiguration.java
src/main/java/com/infoloop/tianting/cache/CacheHealthIndicator.java
src/main/java/com/infoloop/tianting/cache/CacheManagementController.java
src/main/java/com/infoloop/tianting/cache/CacheProperties.java
src/main/java/com/infoloop/tianting/cache/CacheService.java
src/main/java/com/infoloop/tianting/cache/CacheStats.java
src/main/java/com/infoloop/tianting/cache/CaffeineCacheService.java
src/main/java/com/infoloop/tianting/cache/MultiLevelCacheManager.java
src/main/java/com/infoloop/tianting/cache/MultiLevelCacheService.java
src/main/java/com/infoloop/tianting/cache/RedisCacheService.java
src/main/java/com/infoloop/tianting/cache/SpringCacheAdapter.java
src/main/java/com/infoloop/tianting/service/client/MeiZhongYiHeServiceClient.java
src/main/java/com/infoloop/tianting/service/client/OperatorServiceRpcClient.java
src/main/java/com/infoloop/tianting/service/client/SkuServiceRpcClient.java
src/main/resources/application.properties
build.gradle
View file @
967fdec
...
...
@@ -51,6 +51,14 @@ dependencies {
implementation
'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.2'
// Jackson XML 模块
implementation
'org.springframework.boot:spring-boot-starter-websocket'
// websocket
// 缓存相关依赖
implementation
'org.springframework.boot:spring-boot-starter-data-redis'
// Redis
implementation
'org.apache.commons:commons-pool2'
// Redis连接池(增强版)
implementation
'com.github.ben-manes.caffeine:caffeine:3.1.8'
// Caffeine本地缓存
implementation
'org.springframework:spring-aspects'
// Spring AOP
implementation
'io.micrometer:micrometer-core:1.14.2'
// 监控指标
implementation
'io.micrometer:micrometer-registry-statsd:1.14.2'
// StatsD监控
implementation
'com.aliyun:aliyun-java-sdk-core:4.6.4'
//阿里云SDK核心库
implementation
'com.aliyun:aliyun-java-sdk-dysmsapi:2.2.1'
//阿里云短信服务SDK
implementation
'com.aliyun:aliyun-java-sdk-dm:3.3.2'
//阿里云邮件服务SDK
...
...
src/main/java/com/infoloop/tianting/cache/CacheAutoConfiguration.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
com.github.benmanes.caffeine.cache.Caffeine
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnClass
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
import
org.springframework.boot.context.properties.EnableConfigurationProperties
;
import
org.springframework.cache.CacheManager
;
import
org.springframework.cache.annotation.EnableCaching
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Primary
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
java.util.concurrent.TimeUnit
;
/**
* 缓存自动配置类
* 在Spring Boot启动时自动配置缓存相关组件
*/
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass
({
Caffeine
.
class
,
StringRedisTemplate
.
class
})
@EnableConfigurationProperties
(
CacheProperties
.
class
)
@ConditionalOnProperty
(
name
=
"cache.enabled"
,
havingValue
=
"true"
,
matchIfMissing
=
true
)
public
class
CacheAutoConfiguration
{
@Autowired
private
CacheProperties
cacheProperties
;
/**
* 配置Caffeine缓存管理器
*/
@Bean
@ConditionalOnMissingBean
public
com
.
github
.
benmanes
.
caffeine
.
cache
.
Cache
<
String
,
Object
>
caffeineCache
()
{
Caffeine
<
Object
,
Object
>
caffeine
=
Caffeine
.
newBuilder
()
.
initialCapacity
(
cacheProperties
.
getCaffeine
().
getInitialCapacity
())
.
maximumSize
(
cacheProperties
.
getCaffeine
().
getMaximumSize
())
.
expireAfterWrite
(
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
(),
TimeUnit
.
SECONDS
);
// 如果配置了访问后过期时间,则启用
if
(
cacheProperties
.
getCaffeine
().
getExpireAfterAccess
()
>
0
)
{
caffeine
.
expireAfterAccess
(
cacheProperties
.
getCaffeine
().
getExpireAfterAccess
(),
TimeUnit
.
SECONDS
);
}
// 优化并发性能:使用调用线程执行过期任务,减少线程切换开销
caffeine
.
executor
(
Runnable:
:
run
);
if
(
cacheProperties
.
getCaffeine
().
isRecordStats
())
{
caffeine
.
recordStats
();
}
log
.
info
(
"Caffeine缓存配置完成: initialCapacity={}, maximumSize={}, expireAfterWrite={}s, expireAfterAccess={}s"
,
cacheProperties
.
getCaffeine
().
getInitialCapacity
(),
cacheProperties
.
getCaffeine
().
getMaximumSize
(),
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
(),
cacheProperties
.
getCaffeine
().
getExpireAfterAccess
());
return
caffeine
.
build
();
}
/**
* 配置Caffeine缓存服务
*/
@Bean
(
"caffeineCacheService"
)
@ConditionalOnMissingBean
(
name
=
"caffeineCacheService"
)
public
CaffeineCacheService
caffeineCacheService
(
com
.
github
.
benmanes
.
caffeine
.
cache
.
Cache
<
String
,
Object
>
caffeineCache
)
{
log
.
info
(
"Caffeine缓存服务配置完成"
);
return
new
CaffeineCacheService
(
caffeineCache
);
}
/**
* 配置ObjectMapper用于JSON序列化
* 针对Lombok @Data类优化,支持标准的序列化/反序列化
*/
@Bean
(
"cacheObjectMapper"
)
@ConditionalOnMissingBean
(
name
=
"cacheObjectMapper"
)
public
com
.
fasterxml
.
jackson
.
databind
.
ObjectMapper
cacheObjectMapper
()
{
com
.
fasterxml
.
jackson
.
databind
.
ObjectMapper
mapper
=
new
com
.
fasterxml
.
jackson
.
databind
.
ObjectMapper
();
mapper
.
findAndRegisterModules
();
// 注册所有模块,包括Java 8时间模块
// 基本配置
mapper
.
configure
(
com
.
fasterxml
.
jackson
.
databind
.
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
,
false
);
mapper
.
configure
(
com
.
fasterxml
.
jackson
.
databind
.
SerializationFeature
.
FAIL_ON_EMPTY_BEANS
,
false
);
mapper
.
configure
(
com
.
fasterxml
.
jackson
.
databind
.
SerializationFeature
.
WRITE_DATES_AS_TIMESTAMPS
,
false
);
mapper
.
configure
(
com
.
fasterxml
.
jackson
.
databind
.
SerializationFeature
.
FAIL_ON_SELF_REFERENCES
,
false
);
// 为Lombok @Data类启用类型信息,这样可以正确反序列化
mapper
.
activateDefaultTyping
(
com
.
fasterxml
.
jackson
.
databind
.
jsontype
.
BasicPolymorphicTypeValidator
.
builder
()
.
allowIfSubType
(
Object
.
class
)
.
build
(),
com
.
fasterxml
.
jackson
.
databind
.
ObjectMapper
.
DefaultTyping
.
NON_FINAL
,
com
.
fasterxml
.
jackson
.
annotation
.
JsonTypeInfo
.
As
.
PROPERTY
);
log
.
info
(
"缓存专用ObjectMapper配置完成(优化支持Lombok @Data类)"
);
return
mapper
;
}
/**
* 配置Redis缓存服务
*/
@Bean
(
"redisCacheService"
)
@ConditionalOnMissingBean
(
name
=
"redisCacheService"
)
public
RedisCacheService
redisCacheService
(
StringRedisTemplate
redisTemplate
,
@org
.
springframework
.
beans
.
factory
.
annotation
.
Qualifier
(
"cacheObjectMapper"
)
com
.
fasterxml
.
jackson
.
databind
.
ObjectMapper
objectMapper
)
{
log
.
info
(
"Redis缓存服务配置完成"
);
return
new
RedisCacheService
(
redisTemplate
,
objectMapper
);
}
/**
* 配置缓存服务(主要使用多级缓存)
*/
@Bean
(
"multiLevelCacheService"
)
@ConditionalOnMissingBean
(
name
=
"multiLevelCacheService"
)
public
MultiLevelCacheService
multiLevelCacheService
(
CaffeineCacheService
caffeineCacheService
,
RedisCacheService
redisCacheService
,
CacheProperties
cacheProperties
)
{
log
.
info
(
"多级缓存服务配置完成"
);
return
new
MultiLevelCacheService
(
caffeineCacheService
,
redisCacheService
,
cacheProperties
);
}
/**
* 配置Spring Cache管理器 - 桥接到多级缓存服务
*/
@Bean
@Primary
@ConditionalOnMissingBean
(
CacheManager
.
class
)
public
CacheManager
cacheManager
(
@org
.
springframework
.
beans
.
factory
.
annotation
.
Qualifier
(
"multiLevelCacheService"
)
CacheService
cacheService
,
CacheProperties
cacheProperties
)
{
log
.
info
(
"Spring Cache管理器配置完成,桥接到多级缓存服务"
);
return
new
MultiLevelCacheManager
(
cacheService
,
cacheProperties
);
}
/**
* 配置缓存健康检查
*/
@Bean
@ConditionalOnMissingBean
public
CacheHealthIndicator
cacheHealthIndicator
()
{
log
.
info
(
"缓存健康检查配置完成"
);
return
new
CacheHealthIndicator
();
}
/**
* 配置缓存管理控制器
*/
@Bean
@ConditionalOnMissingBean
public
CacheManagementController
cacheManagementController
()
{
log
.
info
(
"缓存管理控制器配置完成"
);
return
new
CacheManagementController
();
}
}
src/main/java/com/infoloop/tianting/cache/CacheHealthIndicator.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.boot.actuate.health.Health
;
import
org.springframework.boot.actuate.health.HealthIndicator
;
/**
* 缓存健康检查指示器
*/
@Slf4j
public
class
CacheHealthIndicator
implements
HealthIndicator
{
@Autowired
@Qualifier
(
"multiLevelCacheService"
)
private
CacheService
cacheService
;
@Override
public
Health
health
()
{
try
{
// 执行简单的缓存操作来检查健康状态
String
testKey
=
"health_check_"
+
System
.
currentTimeMillis
();
cacheService
.
put
(
testKey
,
"test"
,
10
);
Object
value
=
cacheService
.
get
(
testKey
);
cacheService
.
evict
(
testKey
);
if
(
"test"
.
equals
(
value
))
{
return
Health
.
up
()
.
withDetail
(
"status"
,
"缓存服务正常"
)
.
build
();
}
else
{
return
Health
.
down
()
.
withDetail
(
"status"
,
"缓存读写异常"
)
.
build
();
}
}
catch
(
Exception
e
)
{
return
Health
.
down
()
.
withDetail
(
"status"
,
"缓存服务异常"
)
.
withDetail
(
"error"
,
e
.
getMessage
())
.
build
();
}
}
}
src/main/java/com/infoloop/tianting/cache/CacheManagementController.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.web.bind.annotation.*
;
/**
* 缓存管理控制器
* 提供缓存操作的REST端点
*/
@Slf4j
@RestController
@RequestMapping
(
"/actuator/cache"
)
public
class
CacheManagementController
{
@Autowired
@Qualifier
(
"multiLevelCacheService"
)
private
CacheService
cacheService
;
/**
* 获取缓存统计信息
*/
@GetMapping
(
"/stats"
)
public
CacheStats
getCacheStats
()
{
return
cacheService
.
getStats
();
}
/**
* 清除指定缓存
*/
@DeleteMapping
(
"/evict"
)
public
ResponseEntity
<?>
evictCache
(
@RequestParam
String
key
)
{
try
{
cacheService
.
evict
(
key
);
log
.
info
(
"缓存清除成功: {}"
,
key
);
return
ResponseEntity
.
ok
(
"缓存已清除: "
+
key
);
}
catch
(
Exception
e
)
{
log
.
error
(
"缓存清除失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
internalServerError
().
body
(
"缓存清除失败: "
+
e
.
getMessage
());
}
}
/**
* 根据前缀清除缓存
*/
@DeleteMapping
(
"/evict/prefix"
)
public
ResponseEntity
<?>
evictByPrefix
(
@RequestParam
String
prefix
)
{
try
{
cacheService
.
evictByPrefix
(
prefix
);
log
.
info
(
"前缀缓存清除成功: {}"
,
prefix
);
return
ResponseEntity
.
ok
(
"前缀缓存已清除: "
+
prefix
);
}
catch
(
Exception
e
)
{
log
.
error
(
"前缀缓存清除失败: prefix={}, error={}"
,
prefix
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
internalServerError
().
body
(
"前缀缓存清除失败: "
+
e
.
getMessage
());
}
}
/**
* 清空所有缓存
*/
@DeleteMapping
(
"/clear"
)
public
ResponseEntity
<?>
clearCache
()
{
try
{
cacheService
.
clear
();
log
.
info
(
"所有缓存清空成功"
);
return
ResponseEntity
.
ok
(
"所有缓存已清除"
);
}
catch
(
Exception
e
)
{
log
.
error
(
"缓存清空失败: error={}"
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
internalServerError
().
body
(
"缓存清空失败: "
+
e
.
getMessage
());
}
}
/**
* 检查缓存是否存在
*/
@GetMapping
(
"/exists"
)
public
ResponseEntity
<?>
checkExists
(
@RequestParam
String
key
)
{
try
{
boolean
exists
=
cacheService
.
exists
(
key
);
return
ResponseEntity
.
ok
(
String
.
format
(
"缓存key '%s' %s"
,
key
,
exists
?
"存在"
:
"不存在"
));
}
catch
(
Exception
e
)
{
log
.
error
(
"缓存存在性检查失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
internalServerError
().
body
(
"缓存检查失败: "
+
e
.
getMessage
());
}
}
/**
* 获取缓存过期时间
*/
@GetMapping
(
"/expire"
)
public
ResponseEntity
<?>
getExpire
(
@RequestParam
String
key
)
{
try
{
long
expire
=
cacheService
.
getExpire
(
key
);
String
message
;
if
(
expire
==
-
2
)
{
message
=
"缓存key不存在"
;
}
else
if
(
expire
==
-
1
)
{
message
=
"缓存key永久有效"
;
}
else
{
message
=
String
.
format
(
"缓存key剩余时间: %d秒"
,
expire
);
}
return
ResponseEntity
.
ok
(
message
);
}
catch
(
Exception
e
)
{
log
.
error
(
"缓存过期时间获取失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
internalServerError
().
body
(
"缓存过期时间获取失败: "
+
e
.
getMessage
());
}
}
}
src/main/java/com/infoloop/tianting/cache/CacheProperties.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.Data
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
/**
* 缓存配置属性
*/
@Data
@ConfigurationProperties
(
prefix
=
"cache"
)
public
class
CacheProperties
{
/**
* 是否启用缓存
*/
private
boolean
enabled
=
true
;
/**
* 默认缓存过期时间(秒)
*/
private
int
defaultTtl
=
300
;
/**
* Caffeine本地缓存配置
*/
private
CaffeineProperties
caffeine
=
new
CaffeineProperties
();
/**
* Redis缓存配置
*/
private
RedisProperties
redis
=
new
RedisProperties
();
/**
* 监控配置
*/
private
MetricsProperties
metrics
=
new
MetricsProperties
();
@Data
public
static
class
CaffeineProperties
{
/**
* 初始容量
*/
private
int
initialCapacity
=
100
;
/**
* 最大容量
*/
private
int
maximumSize
=
10000
;
/**
* 写入后过期时间(秒)
*/
private
int
expireAfterWrite
=
60
;
/**
* 访问后过期时间(秒),0表示不启用
*/
private
int
expireAfterAccess
=
0
;
/**
* 是否记录统计信息
*/
private
boolean
recordStats
=
true
;
}
@Data
public
static
class
RedisProperties
{
/**
* 是否启用Redis缓存
*/
private
boolean
enabled
=
true
;
/**
* Redis主机
*/
private
String
host
=
"localhost"
;
/**
* Redis端口
*/
private
int
port
=
6379
;
/**
* Redis密码
*/
private
String
password
;
/**
* Redis数据库索引
*/
private
int
database
=
1
;
/**
* 连接超时时间(毫秒)
*/
private
int
timeout
=
2000
;
}
@Data
public
static
class
MetricsProperties
{
/**
* 是否启用监控
*/
private
boolean
enabled
=
true
;
/**
* 监控步长
*/
private
String
step
=
"1m"
;
}
}
src/main/java/com/infoloop/tianting/cache/CacheService.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
/**
* 缓存服务接口
* 定义了缓存的基本操作方法
*/
public
interface
CacheService
{
/**
* 获取缓存
* @param key 缓存key
* @return 缓存的值,如果不存在返回null
*/
Object
get
(
String
key
);
/**
* 设置缓存
* @param key 缓存key
* @param value 缓存值
* @param ttlSeconds 过期时间(秒)
*/
void
put
(
String
key
,
Object
value
,
int
ttlSeconds
);
/**
* 删除缓存
* @param key 缓存key
*/
void
evict
(
String
key
);
/**
* 删除缓存(别名方法)
* @param key 缓存key
*/
default
void
delete
(
String
key
)
{
evict
(
key
);
}
/**
* 根据前缀删除缓存
* @param prefix 缓存key前缀
*/
void
evictByPrefix
(
String
prefix
);
/**
* 根据模式删除缓存
* @param pattern 缓存key模式(支持通配符*)
*/
default
void
deleteByPattern
(
String
pattern
)
{
evictByPrefix
(
pattern
.
replace
(
"*"
,
""
));
}
/**
* 清空所有缓存
*/
void
clear
();
/**
* 获取缓存统计信息
* @return 缓存统计信息
*/
CacheStats
getStats
();
/**
* 检查缓存是否存在
* @param key 缓存key
* @return true表示存在,false表示不存在
*/
boolean
exists
(
String
key
);
/**
* 获取缓存剩余过期时间
* @param key 缓存key
* @return 剩余时间(秒),-2表示key不存在,-1表示永久
*/
long
getExpire
(
String
key
);
}
src/main/java/com/infoloop/tianting/cache/CacheStats.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.Builder
;
import
lombok.Data
;
/**
* 缓存统计信息
*/
@Data
@Builder
public
class
CacheStats
{
/**
* 本地缓存命中次数
*/
private
long
localHits
;
/**
* 本地缓存未命中次数
*/
private
long
localMisses
;
/**
* Redis缓存命中次数
*/
private
long
redisHits
;
/**
* Redis缓存未命中次数
*/
private
long
redisMisses
;
/**
* 总请求次数
*/
private
long
totalRequests
;
/**
* 缓存命中率
*/
private
double
hitRate
;
/**
* 本地缓存大小
*/
private
long
localSize
;
/**
* Redis缓存大小(如果可获取)
*/
private
long
redisSize
;
}
src/main/java/com/infoloop/tianting/cache/CaffeineCacheService.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
com.github.benmanes.caffeine.cache.Cache
;
import
lombok.extern.slf4j.Slf4j
;
/**
* Caffeine本地缓存服务实现
*/
@Slf4j
public
class
CaffeineCacheService
implements
CacheService
{
private
final
Cache
<
String
,
Object
>
cache
;
public
CaffeineCacheService
(
Cache
<
String
,
Object
>
cache
)
{
this
.
cache
=
cache
;
}
@Override
public
Object
get
(
String
key
)
{
return
cache
.
getIfPresent
(
key
);
}
@Override
public
void
put
(
String
key
,
Object
value
,
int
ttlSeconds
)
{
// Caffeine不支持单个key的TTL,使用全局配置的过期时间
cache
.
put
(
key
,
value
);
}
@Override
public
void
evict
(
String
key
)
{
cache
.
invalidate
(
key
);
}
@Override
public
void
evictByPrefix
(
String
prefix
)
{
cache
.
asMap
().
keySet
().
removeIf
(
key
->
key
.
startsWith
(
prefix
));
}
@Override
public
void
clear
()
{
cache
.
invalidateAll
();
}
@Override
public
CacheStats
getStats
()
{
com
.
github
.
benmanes
.
caffeine
.
cache
.
stats
.
CacheStats
caffeineStats
=
cache
.
stats
();
return
CacheStats
.
builder
()
.
localHits
(
caffeineStats
.
hitCount
())
.
localMisses
(
caffeineStats
.
missCount
())
.
totalRequests
(
caffeineStats
.
requestCount
())
.
hitRate
(
caffeineStats
.
hitRate
())
.
localSize
(
cache
.
estimatedSize
())
.
build
();
}
@Override
public
boolean
exists
(
String
key
)
{
return
cache
.
getIfPresent
(
key
)
!=
null
;
}
@Override
public
long
getExpire
(
String
key
)
{
// Caffeine不支持获取单个key的过期时间
return
exists
(
key
)
?
-
1
:
-
2
;
}
}
src/main/java/com/infoloop/tianting/cache/MultiLevelCacheManager.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.cache.Cache
;
import
org.springframework.cache.CacheManager
;
import
java.util.Collection
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.ConcurrentMap
;
/**
* 多级缓存管理器
* 实现Spring Cache CacheManager接口,桥接到现有的MultiLevelCacheService
*/
@Slf4j
public
class
MultiLevelCacheManager
implements
CacheManager
{
private
final
CacheService
cacheService
;
private
final
CacheProperties
cacheProperties
;
private
final
ConcurrentMap
<
String
,
Cache
>
cacheMap
=
new
ConcurrentHashMap
<>();
public
MultiLevelCacheManager
(
CacheService
cacheService
,
CacheProperties
cacheProperties
)
{
this
.
cacheService
=
cacheService
;
this
.
cacheProperties
=
cacheProperties
;
log
.
info
(
"多级缓存管理器初始化完成,默认TTL: {}s"
,
cacheProperties
.
getDefaultTtl
());
}
@Override
public
Cache
getCache
(
String
name
)
{
if
(
name
==
null
)
{
return
null
;
}
return
cacheMap
.
computeIfAbsent
(
name
,
cacheName
->
{
log
.
debug
(
"创建新的缓存区域: {}"
,
cacheName
);
return
new
SpringCacheAdapter
(
cacheName
,
cacheService
,
cacheProperties
.
getDefaultTtl
());
});
}
@Override
public
Collection
<
String
>
getCacheNames
()
{
return
cacheMap
.
keySet
();
}
/**
* 清除所有缓存
*/
public
void
clearAll
()
{
cacheMap
.
values
().
forEach
(
Cache:
:
clear
);
log
.
info
(
"已清除所有缓存区域"
);
}
/**
* 获取缓存统计信息
*/
public
String
getCacheStats
()
{
StringBuilder
stats
=
new
StringBuilder
();
stats
.
append
(
"缓存区域数量: "
).
append
(
cacheMap
.
size
()).
append
(
"\n"
);
stats
.
append
(
"缓存区域列表: "
).
append
(
String
.
join
(
", "
,
getCacheNames
()));
return
stats
.
toString
();
}
}
src/main/java/com/infoloop/tianting/cache/MultiLevelCacheService.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.extern.slf4j.Slf4j
;
/**
* 多级缓存服务实现
* 结合Caffeine本地缓存和Redis分布式缓存
* 实现L1 + L2缓存架构
*
* 缓存逻辑说明:
* 1. 查询逻辑:先查Caffeine(60s) -> 未命中再查Redis -> 未命中再查数据库
* 2. 写入逻辑:先写Redis(业务TTL) -> 再写Caffeine(固定60s)
* 3. Caffeine过期后正确从Redis读取数据,避免直接查数据库
*/
@Slf4j
public
class
MultiLevelCacheService
implements
CacheService
{
private
final
CaffeineCacheService
caffeineCacheService
;
private
final
RedisCacheService
redisCacheService
;
private
final
CacheProperties
cacheProperties
;
// 缓存空值的特殊标记
private
static
final
String
NULL_VALUE_MARKER
=
"$$NULL$$"
;
public
MultiLevelCacheService
(
CaffeineCacheService
caffeineCacheService
,
RedisCacheService
redisCacheService
,
CacheProperties
cacheProperties
)
{
this
.
caffeineCacheService
=
caffeineCacheService
;
this
.
redisCacheService
=
redisCacheService
;
this
.
cacheProperties
=
cacheProperties
;
log
.
info
(
"多级缓存服务初始化完成,Caffeine TTL: {}s"
,
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
());
}
@Override
public
Object
get
(
String
key
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
return
null
;
}
log
.
debug
(
"开始缓存查询: key={}"
,
key
);
// 1. 先查本地缓存(Caffeine - L1缓存)
Object
localValue
=
caffeineCacheService
.
get
(
key
);
if
(
localValue
!=
null
)
{
if
(
NULL_VALUE_MARKER
.
equals
(
localValue
))
{
log
.
debug
(
"本地缓存命中(空值): {}"
,
key
);
return
null
;
}
log
.
debug
(
"本地缓存命中: {}"
,
key
);
// 检查Redis中是否也存在,如果不存在则回写
if
(!
redisCacheService
.
exists
(
key
))
{
try
{
// 使用默认TTL回写到Redis
int
defaultRedisTtl
=
cacheProperties
.
getDefaultTtl
();
redisCacheService
.
put
(
key
,
localValue
,
defaultRedisTtl
);
log
.
debug
(
"本地缓存命中但Redis缺失,已回写: key={}, ttl={}s"
,
key
,
defaultRedisTtl
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis回写失败: key={}, error={}"
,
key
,
e
.
getMessage
());
}
}
return
localValue
;
}
// 2. 本地缓存未命中,查分布式缓存(Redis - L2缓存)
log
.
debug
(
"Caffeine未命中,检查Redis分布式缓存: key={}"
,
key
);
Object
redisValue
=
redisCacheService
.
get
(
key
);
if
(
redisValue
!=
null
)
{
if
(
NULL_VALUE_MARKER
.
equals
(
redisValue
))
{
// 将空值标记同步到本地缓存,避免重复查询
caffeineCacheService
.
put
(
key
,
NULL_VALUE_MARKER
,
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
());
log
.
debug
(
"分布式缓存命中(空值),并同步到本地缓存: {}"
,
key
);
return
null
;
}
// 同步到本地缓存
caffeineCacheService
.
put
(
key
,
redisValue
,
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
());
log
.
debug
(
"分布式缓存命中,返回Redis数据: key={}"
,
key
);
return
redisValue
;
}
// 3. 两级缓存都未命中,需要查询数据库
log
.
debug
(
"两级缓存都未命中,将查询数据库: key={}"
,
key
);
return
null
;
}
@Override
public
void
put
(
String
key
,
Object
value
,
int
ttlSeconds
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
log
.
error
(
"缓存key不能为空"
);
return
;
}
if
(
ttlSeconds
<=
0
)
{
log
.
error
(
"缓存TTL必须大于0: key={}, ttl={}"
,
key
,
ttlSeconds
);
return
;
}
// 处理空值缓存
Object
actualValue
=
(
value
==
null
)
?
NULL_VALUE_MARKER
:
value
;
// 写入策略:先写Redis,再写Caffeine
boolean
redisSuccess
=
false
;
boolean
localSuccess
=
false
;
try
{
// 写入分布式缓存Redis,使用注解指定的TTL
redisCacheService
.
put
(
key
,
actualValue
,
ttlSeconds
);
redisSuccess
=
true
;
log
.
debug
(
"分布式缓存写入成功: key={}, ttl={}s"
,
key
,
ttlSeconds
);
}
catch
(
Exception
e
)
{
log
.
error
(
"分布式缓存写入失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
}
try
{
// 2. 写入本地缓存Caffeine,使用配置的过期时间
int
caffeineTtl
=
cacheProperties
.
getCaffeine
().
getExpireAfterWrite
();
caffeineCacheService
.
put
(
key
,
actualValue
,
caffeineTtl
);
localSuccess
=
true
;
log
.
debug
(
"本地缓存写入成功: key={}, ttl={}s"
,
key
,
caffeineTtl
);
}
catch
(
Exception
e
)
{
log
.
error
(
"本地缓存写入失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
}
if
(!
localSuccess
&&
!
redisSuccess
)
{
log
.
error
(
"缓存写入完全失败: key={}"
,
key
);
}
}
@Override
public
void
evict
(
String
key
)
{
// 双删策略:同时删除本地缓存和分布式缓存
try
{
caffeineCacheService
.
evict
(
key
);
log
.
debug
(
"本地缓存删除成功: {}"
,
key
);
}
catch
(
Exception
e
)
{
log
.
error
(
"本地缓存删除失败: key={}, error={}"
,
key
,
e
.
getMessage
());
}
try
{
redisCacheService
.
evict
(
key
);
log
.
debug
(
"分布式缓存删除成功: {}"
,
key
);
}
catch
(
Exception
e
)
{
log
.
error
(
"分布式缓存删除失败: key={}, error={}"
,
key
,
e
.
getMessage
());
}
}
@Override
public
void
evictByPrefix
(
String
prefix
)
{
// 双删策略:同时删除本地缓存和分布式缓存的前缀
try
{
caffeineCacheService
.
evictByPrefix
(
prefix
);
log
.
debug
(
"本地缓存前缀删除成功: {}"
,
prefix
);
}
catch
(
Exception
e
)
{
log
.
error
(
"本地缓存前缀删除失败: prefix={}, error={}"
,
prefix
,
e
.
getMessage
());
}
try
{
redisCacheService
.
evictByPrefix
(
prefix
);
log
.
debug
(
"分布式缓存前缀删除成功: {}"
,
prefix
);
}
catch
(
Exception
e
)
{
log
.
error
(
"分布式缓存前缀删除失败: prefix={}, error={}"
,
prefix
,
e
.
getMessage
());
}
}
@Override
public
void
clear
()
{
// 清空所有缓存
try
{
caffeineCacheService
.
clear
();
log
.
info
(
"本地缓存清空成功"
);
}
catch
(
Exception
e
)
{
log
.
error
(
"本地缓存清空失败: error={}"
,
e
.
getMessage
());
}
try
{
redisCacheService
.
clear
();
log
.
info
(
"分布式缓存清空成功"
);
}
catch
(
Exception
e
)
{
log
.
error
(
"分布式缓存清空失败: error={}"
,
e
.
getMessage
());
}
}
@Override
public
CacheStats
getStats
()
{
CacheStats
localStats
=
caffeineCacheService
.
getStats
();
CacheStats
redisStats
=
redisCacheService
.
getStats
();
return
CacheStats
.
builder
()
.
localHits
(
localStats
.
getLocalHits
())
.
localMisses
(
localStats
.
getLocalMisses
())
.
redisHits
(
redisStats
.
getRedisHits
())
.
redisMisses
(
redisStats
.
getRedisMisses
())
.
totalRequests
(
calculateTotalRequests
(
localStats
,
redisStats
))
.
hitRate
(
calculateHitRate
(
localStats
,
redisStats
))
.
localSize
(
localStats
.
getLocalSize
())
.
build
();
}
@Override
public
boolean
exists
(
String
key
)
{
// 只要任一缓存存在就返回true
return
caffeineCacheService
.
exists
(
key
)
||
redisCacheService
.
exists
(
key
);
}
@Override
public
long
getExpire
(
String
key
)
{
// 优先返回分布式缓存的过期时间
long
redisExpire
=
redisCacheService
.
getExpire
(
key
);
if
(
redisExpire
>=
0
)
{
return
redisExpire
;
}
// 如果Redis没有,则返回本地缓存的过期时间
return
caffeineCacheService
.
getExpire
(
key
);
}
private
long
calculateTotalRequests
(
CacheStats
localStats
,
CacheStats
redisStats
)
{
return
localStats
.
getLocalHits
()
+
localStats
.
getLocalMisses
()
+
redisStats
.
getRedisHits
()
+
redisStats
.
getRedisMisses
();
}
private
double
calculateHitRate
(
CacheStats
localStats
,
CacheStats
redisStats
)
{
long
totalHits
=
localStats
.
getLocalHits
()
+
redisStats
.
getRedisHits
();
long
totalRequests
=
calculateTotalRequests
(
localStats
,
redisStats
);
return
totalRequests
>
0
?
(
double
)
totalHits
/
totalRequests
:
0.0
;
}
}
src/main/java/com/infoloop/tianting/cache/RedisCacheService.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.data.redis.core.Cursor
;
import
org.springframework.data.redis.core.RedisCallback
;
import
org.springframework.data.redis.core.ScanOptions
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
java.time.Duration
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.concurrent.TimeUnit
;
/**
* Redis分布式缓存服务实现(优化版)
* 包含缓存穿透保护、性能优化等功能
*/
@Slf4j
public
class
RedisCacheService
implements
CacheService
{
private
final
StringRedisTemplate
redisTemplate
;
private
final
ObjectMapper
objectMapper
;
private
static
final
String
CACHE_PREFIX
=
"cache:"
;
private
static
final
String
NULL_VALUE_MARKER
=
"::NULL::"
;
public
RedisCacheService
(
StringRedisTemplate
redisTemplate
,
ObjectMapper
objectMapper
)
{
this
.
redisTemplate
=
redisTemplate
;
this
.
objectMapper
=
objectMapper
;
}
@Override
public
Object
get
(
String
key
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
log
.
warn
(
"Redis缓存key不能为空"
);
return
null
;
}
try
{
String
jsonValue
=
redisTemplate
.
opsForValue
().
get
(
CACHE_PREFIX
+
key
);
if
(
jsonValue
!=
null
)
{
// 处理null值标记,防止缓存穿透
if
(
NULL_VALUE_MARKER
.
equals
(
jsonValue
))
{
return
null
;
}
// 使用TypeReference保持类型信息
return
objectMapper
.
readValue
(
jsonValue
,
new
TypeReference
<
Object
>()
{});
}
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存读取失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
}
return
null
;
}
@Override
public
void
put
(
String
key
,
Object
value
,
int
ttlSeconds
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
log
.
warn
(
"Redis缓存key不能为空"
);
return
;
}
if
(
ttlSeconds
<=
0
)
{
log
.
warn
(
"Redis缓存TTL必须大于0: ttl={}"
,
ttlSeconds
);
return
;
}
try
{
String
jsonValue
;
if
(
value
==
null
)
{
// 缓存null值,防止缓存穿透
jsonValue
=
NULL_VALUE_MARKER
;
}
else
{
jsonValue
=
objectMapper
.
writeValueAsString
(
value
);
}
redisTemplate
.
opsForValue
().
set
(
CACHE_PREFIX
+
key
,
jsonValue
,
Duration
.
ofSeconds
(
ttlSeconds
));
log
.
debug
(
"Redis缓存设置成功: key={}, ttl={}s"
,
key
,
ttlSeconds
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存写入失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
}
}
@Override
public
void
evict
(
String
key
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
log
.
warn
(
"Redis缓存key不能为空"
);
return
;
}
try
{
Boolean
deleted
=
redisTemplate
.
delete
(
CACHE_PREFIX
+
key
);
log
.
debug
(
"Redis缓存删除: key={}, deleted={}"
,
key
,
deleted
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存删除失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
}
}
@Override
public
void
evictByPrefix
(
String
prefix
)
{
if
(
prefix
==
null
||
prefix
.
trim
().
isEmpty
())
{
log
.
warn
(
"Redis缓存前缀不能为空"
);
return
;
}
try
{
// 使用SCAN命令替代KEYS命令,避免阻塞Redis
List
<
String
>
keysToDelete
=
new
ArrayList
<>();
String
pattern
=
CACHE_PREFIX
+
prefix
+
"*"
;
// 使用RedisCallback执行SCAN操作
redisTemplate
.
execute
((
RedisCallback
<
Void
>)
connection
->
{
try
(
Cursor
<
byte
[]>
cursor
=
connection
.
scan
(
ScanOptions
.
scanOptions
()
.
match
(
pattern
)
.
count
(
100
)
.
build
()))
{
while
(
cursor
.
hasNext
())
{
String
key
=
new
String
(
cursor
.
next
());
keysToDelete
.
add
(
key
);
// 批量删除,避免一次性删除过多key
if
(
keysToDelete
.
size
()
>=
100
)
{
Long
deletedCount
=
redisTemplate
.
delete
(
keysToDelete
);
log
.
debug
(
"Redis缓存批量删除: count={}"
,
deletedCount
);
keysToDelete
.
clear
();
}
}
}
catch
(
Exception
e
)
{
log
.
error
(
"SCAN操作异常: {}"
,
e
.
getMessage
(),
e
);
}
return
null
;
});
// 删除剩余的key
if
(!
keysToDelete
.
isEmpty
())
{
Long
deletedCount
=
redisTemplate
.
delete
(
keysToDelete
);
log
.
debug
(
"Redis缓存前缀删除完成: prefix={}, total_deleted={}"
,
prefix
,
deletedCount
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存前缀删除失败: prefix={}, error={}"
,
prefix
,
e
.
getMessage
(),
e
);
}
}
@Override
public
void
clear
()
{
try
{
// 使用SCAN命令替代KEYS命令,避免阻塞Redis
List
<
String
>
keysToDelete
=
new
ArrayList
<>();
String
pattern
=
CACHE_PREFIX
+
"*"
;
long
totalDeleted
=
0
;
// 使用RedisCallback执行SCAN操作
Long
result
=
redisTemplate
.
execute
((
RedisCallback
<
Long
>)
connection
->
{
long
deleted
=
0
;
try
(
Cursor
<
byte
[]>
cursor
=
connection
.
scan
(
ScanOptions
.
scanOptions
()
.
match
(
pattern
)
.
count
(
1000
)
.
build
()))
{
while
(
cursor
.
hasNext
())
{
String
key
=
new
String
(
cursor
.
next
());
keysToDelete
.
add
(
key
);
// 批量删除,避免一次性删除过多key
if
(
keysToDelete
.
size
()
>=
1000
)
{
Long
deletedCount
=
redisTemplate
.
delete
(
keysToDelete
);
if
(
deletedCount
!=
null
)
{
deleted
+=
deletedCount
;
}
keysToDelete
.
clear
();
}
}
}
catch
(
Exception
e
)
{
log
.
error
(
"SCAN操作异常: {}"
,
e
.
getMessage
(),
e
);
}
return
deleted
;
});
if
(
result
!=
null
)
{
totalDeleted
+=
result
;
}
// 删除剩余的key
if
(!
keysToDelete
.
isEmpty
())
{
Long
deletedCount
=
redisTemplate
.
delete
(
keysToDelete
);
if
(
deletedCount
!=
null
)
{
totalDeleted
+=
deletedCount
;
}
}
if
(
totalDeleted
>
0
)
{
log
.
info
(
"Redis缓存清空成功,删除{}个key"
,
totalDeleted
);
}
else
{
log
.
info
(
"Redis缓存清空完成,无缓存数据"
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存清空失败: error={}"
,
e
.
getMessage
(),
e
);
}
}
@Override
public
CacheStats
getStats
()
{
try
{
// Redis没有内置统计信息,这里返回基本信息
return
CacheStats
.
builder
()
.
redisHits
(
0
)
.
redisMisses
(
0
)
.
totalRequests
(
0
)
.
hitRate
(
0.0
)
.
build
();
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存统计获取失败: error={}"
,
e
.
getMessage
(),
e
);
return
CacheStats
.
builder
().
build
();
}
}
@Override
public
boolean
exists
(
String
key
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
return
false
;
}
try
{
return
Boolean
.
TRUE
.
equals
(
redisTemplate
.
hasKey
(
CACHE_PREFIX
+
key
));
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存存在性检查失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
return
false
;
}
}
@Override
public
long
getExpire
(
String
key
)
{
if
(
key
==
null
||
key
.
trim
().
isEmpty
())
{
return
-
2
;
}
try
{
Long
expire
=
redisTemplate
.
getExpire
(
CACHE_PREFIX
+
key
,
TimeUnit
.
SECONDS
);
return
expire
!=
null
?
expire
:
-
2
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Redis缓存过期时间获取失败: key={}, error={}"
,
key
,
e
.
getMessage
(),
e
);
return
-
2
;
}
}
}
src/main/java/com/infoloop/tianting/cache/SpringCacheAdapter.java
0 → 100644
View file @
967fdec
package
com
.
infoloop
.
tianting
.
cache
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.cache.Cache
;
import
java.util.concurrent.Callable
;
/**
* Spring Cache适配器
* 将Spring Cache接口桥接到现有的MultiLevelCacheService
*/
@Slf4j
public
class
SpringCacheAdapter
implements
Cache
{
private
final
String
name
;
private
final
CacheService
cacheService
;
private
final
int
defaultTtl
;
public
SpringCacheAdapter
(
String
name
,
CacheService
cacheService
,
int
defaultTtl
)
{
this
.
name
=
name
;
this
.
cacheService
=
cacheService
;
this
.
defaultTtl
=
defaultTtl
;
}
@Override
public
String
getName
()
{
return
name
;
}
@Override
public
Object
getNativeCache
()
{
return
cacheService
;
}
@Override
public
ValueWrapper
get
(
Object
key
)
{
if
(
key
==
null
)
{
return
null
;
}
String
cacheKey
=
generateCacheKey
(
key
);
Object
value
=
cacheService
.
get
(
cacheKey
);
if
(
value
!=
null
)
{
log
.
debug
(
"Spring Cache命中: cache={}, key={}"
,
name
,
cacheKey
);
return
()
->
value
;
}
log
.
debug
(
"Spring Cache未命中: cache={}, key={}"
,
name
,
cacheKey
);
return
null
;
}
@Override
public
<
T
>
T
get
(
Object
key
,
Class
<
T
>
type
)
{
ValueWrapper
wrapper
=
get
(
key
);
if
(
wrapper
==
null
)
{
return
null
;
}
Object
value
=
wrapper
.
get
();
if
(
value
==
null
)
{
return
null
;
}
if
(
type
.
isInstance
(
value
))
{
return
type
.
cast
(
value
);
}
throw
new
IllegalStateException
(
"Cached value is not of required type ["
+
type
.
getName
()
+
"]: "
+
value
);
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
<
T
>
T
get
(
Object
key
,
Callable
<
T
>
valueLoader
)
{
ValueWrapper
wrapper
=
get
(
key
);
if
(
wrapper
!=
null
)
{
return
(
T
)
wrapper
.
get
();
}
// 缓存未命中,执行valueLoader
try
{
T
value
=
valueLoader
.
call
();
put
(
key
,
value
);
return
value
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"ValueLoader execution failed"
,
e
);
}
}
@Override
public
void
put
(
Object
key
,
Object
value
)
{
if
(
key
==
null
)
{
return
;
}
String
cacheKey
=
generateCacheKey
(
key
);
cacheService
.
put
(
cacheKey
,
value
,
defaultTtl
);
log
.
debug
(
"Spring Cache设置: cache={}, key={}, ttl={}s"
,
name
,
cacheKey
,
defaultTtl
);
}
@Override
public
void
evict
(
Object
key
)
{
if
(
key
==
null
)
{
return
;
}
String
cacheKey
=
generateCacheKey
(
key
);
cacheService
.
delete
(
cacheKey
);
log
.
debug
(
"Spring Cache清除: cache={}, key={}"
,
name
,
cacheKey
);
}
@Override
public
void
clear
()
{
// 清空整个缓存区域
cacheService
.
deleteByPattern
(
name
+
":*"
);
log
.
debug
(
"Spring Cache清空: cache={}"
,
name
);
}
/**
* 生成缓存key
* 格式: cacheName:key
*/
private
String
generateCacheKey
(
Object
key
)
{
return
name
+
":"
+
key
.
toString
();
}
}
src/main/java/com/infoloop/tianting/service/client/MeiZhongYiHeServiceClient.java
View file @
967fdec
...
...
@@ -10,6 +10,7 @@ import com.infoloop.rpc.meizhongyiheservice.GetCustomersByConditionRpcRequest;
import
com.infoloop.rpc.meizhongyiheservice.MeiZhongYiHeServiceRpcGrpc
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.cache.annotation.Cacheable
;
import
org.springframework.stereotype.Service
;
import
java.util.List
;
...
...
@@ -20,6 +21,7 @@ public class MeiZhongYiHeServiceClient {
private
final
MeiZhongYiHeServiceRpcGrpc
.
MeiZhongYiHeServiceRpcBlockingStub
meiZhongYiHeServiceRpcBlockingStub
;
@Cacheable
(
value
=
"customerAllergies"
,
key
=
"#hisCustomerId"
)
public
GetCustomerAllergiesByCustomerIdsRpcResponse
getHisCustomerAllergiesByCustomerId
(
String
hisCustomerId
)
{
final
var
request
=
GetCustomerAllergiesByCustomerIdsRpcRequest
.
newBuilder
()
...
...
@@ -28,6 +30,7 @@ public class MeiZhongYiHeServiceClient {
return
meiZhongYiHeServiceRpcBlockingStub
.
getCustomerAllergiesByCustomerIds
(
request
);
}
@Cacheable
(
value
=
"customerMedicalAdvices"
,
key
=
"#contractNo"
)
public
GetCustomerMedicalAdvicesByHospitalRecordIdsRpcResponse
getHisCustomerMedicalAdvicesByContractNo
(
String
contractNo
)
{
final
var
request
=
GetCustomerMedicalAdvicesByHospitalRecordIdsRpcRequest
.
newBuilder
()
.
addAllHospitalRecordIds
(
List
.
of
(
contractNo
))
...
...
@@ -35,6 +38,7 @@ public class MeiZhongYiHeServiceClient {
return
meiZhongYiHeServiceRpcBlockingStub
.
getCustomerMedicalAdvicesByHospitalRecordIds
(
request
);
}
@Cacheable
(
value
=
"customerDetails"
,
key
=
"#HISCustomerId + '_' + #contractNo"
)
public
Customer
getCustomerDetailById
(
String
HISCustomerId
,
String
contractNo
)
{
final
var
customer
=
meiZhongYiHeServiceRpcBlockingStub
.
getCustomerDetailById
(
GetCustomerDetailByIdRpcRequest
.
newBuilder
()
.
setCustomerId
(
HISCustomerId
)
...
...
src/main/java/com/infoloop/tianting/service/client/OperatorServiceRpcClient.java
View file @
967fdec
...
...
@@ -6,6 +6,7 @@ import com.infoloop.tianting.GetCOperatorByMobileOrEmailRpcRequest;
import
com.infoloop.tianting.SingleCOperatorRpcResponse
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.cache.annotation.Cacheable
;
import
org.springframework.stereotype.Service
;
@Service
...
...
@@ -14,6 +15,7 @@ public class OperatorServiceRpcClient {
private
final
COperatorServiceProtoRpcGrpc
.
COperatorServiceProtoRpcBlockingStub
cOperatorServiceProtoRpcBlockingStub
;
@Cacheable
(
value
=
"operators"
,
key
=
"'mobile_email_' + #keyword"
)
public
SingleCOperatorRpcResponse
getCOperatorByMobileOrEmail
(
String
keyword
)
{
final
var
request
=
GetCOperatorByMobileOrEmailRpcRequest
.
newBuilder
()
.
setKeyword
(
keyword
)
...
...
@@ -21,6 +23,7 @@ public class OperatorServiceRpcClient {
return
cOperatorServiceProtoRpcBlockingStub
.
getCOperatorByMobileOrEmail
(
request
).
getResponse
();
}
@Cacheable
(
value
=
"operators"
,
key
=
"'id_' + #id"
)
public
SingleCOperatorRpcResponse
getCOperatorById
(
int
id
)
{
final
var
request
=
GetCOperatorByIdRpcRequest
.
newBuilder
().
setId
(
id
).
build
();
return
cOperatorServiceProtoRpcBlockingStub
.
getCOperatorById
(
request
).
getResponse
();
...
...
src/main/java/com/infoloop/tianting/service/client/SkuServiceRpcClient.java
View file @
967fdec
...
...
@@ -6,6 +6,7 @@ import com.infoloop.tianting.SingleSkuResponse;
import
com.infoloop.tianting.SkuServiceProtoRpcGrpc
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.cache.annotation.Cacheable
;
import
org.springframework.stereotype.Service
;
import
java.util.List
;
...
...
@@ -15,6 +16,7 @@ import java.util.List;
public
class
SkuServiceRpcClient
{
private
final
SkuServiceProtoRpcGrpc
.
SkuServiceProtoRpcBlockingStub
skuServiceProtoRpcBlockingStub
;
@Cacheable
(
value
=
"skus"
,
key
=
"'dish_skus_' + T(String).join('_', #ids)"
)
public
GeDishSkuByIdsRpcResponse
getDishSkusByIds
(
List
<
Integer
>
ids
)
{
final
var
request
=
GetSkusByIdsRpcRequest
.
newBuilder
()
.
addAllIds
(
ids
)
...
...
@@ -23,6 +25,7 @@ public class SkuServiceRpcClient {
return
skuServiceProtoRpcBlockingStub
.
getDishSkusByIds
(
request
);
}
@Cacheable
(
value
=
"skus"
,
key
=
"'skus_' + T(String).join('_', #ids)"
)
public
List
<
SingleSkuResponse
>
getSkusByIds
(
List
<
Integer
>
ids
)
{
final
var
request
=
GetSkusByIdsRpcRequest
.
newBuilder
()
.
addAllIds
(
ids
)
...
...
src/main/resources/application.properties
View file @
967fdec
...
...
@@ -128,3 +128,26 @@ knife4j.setting.enable-swagger-models=true
knife4j.setting.enable-reload-cache-
parameter
=
true
knife4j.setting.enable-
version
=
true
# 缓存配置
cache.enabled
=
true
cache.default-
ttl
=
300
# Caffeine本地缓存配置
cache.caffeine.initial-
capacity
=
100
cache.caffeine.maximum-
size
=
10000
cache.caffeine.expire-after-
write
=
60
cache.caffeine.expire-after-
access
=
0
cache.caffeine.record-
stats
=
true
# Redis缓存配置
cache.redis.enabled
=
true
cache.redis.host
=
${spring.redis.host}
cache.redis.port
=
${spring.redis.port}
cache.redis.password
=
${spring.redis.password}
cache.redis.database
=
11
cache.redis.timeout
=
2000
# 缓存监控配置
cache.metrics.enabled
=
true
cache.metrics.step
=
1m
...
...
Please
register
or
login
to post a comment