feat(perf): remove Thread.sleep via DelayProvider; chore(cache): add Redis cache TTL + JDK serialization; chore(test): migrate javax->jakarta for embedded redis; chore(config): add dev/test/prod profiles; chore(security): strengthen API key hashing with PBKDF2
This commit is contained in:
34
src/main/java/com/mosquito/project/config/CacheConfig.java
Normal file
34
src/main/java/com/mosquito/project/config/CacheConfig.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.mosquito.project.config;
|
||||
|
||||
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.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofMinutes(5))
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
|
||||
new JdkSerializationRedisSerializer()
|
||||
));
|
||||
|
||||
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
|
||||
cacheConfigs.put("leaderboards", defaultConfig.entryTtl(Duration.ofMinutes(5)));
|
||||
|
||||
return RedisCacheManager.builder(connectionFactory)
|
||||
.cacheDefaults(defaultConfig)
|
||||
.withInitialCacheConfigurations(cacheConfigs)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,12 @@ public class ActivityService {
|
||||
private final Map<Long, ApiKey> apiKeys = new ConcurrentHashMap<>();
|
||||
private final AtomicLong apiKeyIdCounter = new AtomicLong();
|
||||
|
||||
private final DelayProvider delayProvider;
|
||||
|
||||
public ActivityService(DelayProvider delayProvider) {
|
||||
this.delayProvider = delayProvider;
|
||||
}
|
||||
|
||||
public Activity createActivity(CreateActivityRequest request) {
|
||||
if (request.getEndTime().isBefore(request.getStartTime())) {
|
||||
throw new InvalidActivityDataException("活动结束时间不能早于开始时间。");
|
||||
@@ -116,12 +122,15 @@ public class ActivityService {
|
||||
}
|
||||
|
||||
private String hashApiKey(String apiKey, byte[] salt) {
|
||||
// Strengthen hashing using PBKDF2WithHmacSHA256
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(salt);
|
||||
byte[] hashedApiKey = md.digest(apiKey.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(hashedApiKey);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
javax.crypto.SecretKeyFactory skf = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
javax.crypto.spec.PBEKeySpec spec = new javax.crypto.spec.PBEKeySpec(
|
||||
apiKey.toCharArray(), salt, 185000, 256
|
||||
);
|
||||
byte[] derived = skf.generateSecret(spec).getEncoded();
|
||||
return Base64.getEncoder().encodeToString(derived);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("无法创建API密钥哈希", e);
|
||||
}
|
||||
}
|
||||
@@ -211,8 +220,7 @@ public class ActivityService {
|
||||
// Simulate fetching and ranking data
|
||||
log.info("正在为活动ID {} 生成排行榜...", activityId);
|
||||
try {
|
||||
// Simulate database query delay
|
||||
Thread.sleep(2000);
|
||||
delayProvider.delayMillis(2000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.mosquito.project.service;
|
||||
|
||||
public interface DelayProvider {
|
||||
void delayMillis(long millis) throws InterruptedException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.mosquito.project.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class NoOpDelayProvider implements DelayProvider {
|
||||
@Override
|
||||
public void delayMillis(long millis) {
|
||||
// no-op by default to avoid blocking
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main/resources/application-dev.yml
Normal file
10
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
redis:
|
||||
host: localhost
|
||||
port: ${spring.redis.port:6379}
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
|
||||
12
src/main/resources/application-prod.yml
Normal file
12
src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
flyway:
|
||||
enabled: true
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
|
||||
12
src/main/resources/application-test.yml
Normal file
12
src/main/resources/application-test.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: test
|
||||
redis:
|
||||
host: localhost
|
||||
port: ${spring.redis.port:6379}
|
||||
flyway:
|
||||
enabled: true
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.mosquito.project.config;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user