BUGFIX:1、子账号查询,无法批量根据用户名或邮箱查

2、教育版子账号创建及修改涉及的积分问题
This commit is contained in:
2025-08-20 16:40:44 +08:00
parent caa9985d11
commit 81c417ed76
7 changed files with 872 additions and 79 deletions

View File

@@ -380,15 +380,6 @@ public class RedisUtil {
return redisTemplate.opsForValue().increment(key, 0);
}
public Long getAndSetKey(String key, Long count) {
try {
String val = redisTemplate.opsForValue().getAndSet(key, count.toString());
return StringUtil.isNullOrEmpty(val) ? 0L : Long.parseLong(val);
} catch (NumberFormatException e) {
return 0L; // 或 throw new BusinessException("非法的数值格式");
}
}
/**
* 记录任务的耗时到Redis
* @param taskKey 任务标识,如 "taskA"
@@ -620,4 +611,4 @@ public class RedisUtil {
return count <= 3;
}
}
}

View File

@@ -0,0 +1,743 @@
package com.ai.da.common.utils;
import com.ai.da.model.dto.ProgressDTO;
import com.ai.da.python.vo.DesignPythonObject;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Component
public class RedisUtilEnhance {
@Resource
private RedisTemplate<String, Object> redisTemplate;
private ValueOperations<String, Object> valueOperations;
private SetOperations<String, Object> setOperations;
private HashOperations<String, Object, Object> hashOperations;
private ZSetOperations<String, Object> zSetOperations;
@PostConstruct
public void init() {
this.valueOperations = redisTemplate.opsForValue();
this.setOperations = redisTemplate.opsForSet();
this.hashOperations = redisTemplate.opsForHash();
this.zSetOperations = redisTemplate.opsForZSet();
}
public final static String FLUX_POLLING_URL = "Flux:";
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
//- - - - - - - - - - - - - - - - - - - - - ZSet类型 - - - - - - - - - - - - - - - - - - - -
/**
* 向ZSet中添加元素
*/
public void addToZSet(String key, Object value, Double score) {
zSetOperations.add(key, value, score);
}
/**
* 从ZSet中删除元素
*/
public void removeFromZSet(String key, Object value) {
zSetOperations.remove(key, value);
}
/**
* 获取指定元素的当前排列顺序
*/
public Long getRank(String key, Object value) {
return zSetOperations.rank(key, value);
}
/**
* 获取当前ZSet中的最大score
*/
public Double getMaxScore(String key) {
Set<ZSetOperations.TypedTuple<Object>> set = zSetOperations.reverseRangeWithScores(key, 0, 0);
if (!CollectionUtils.isEmpty(set)) {
Iterator<ZSetOperations.TypedTuple<Object>> iterator = set.iterator();
if (iterator.hasNext()) {
Double score = iterator.next().getScore();
return score != null ? score + 1.0 : 1.0;
}
}
return 1.0;
}
/**
* 判断元素是否存在
*/
public Boolean isElementExistsInZSet(String key, Object value) {
return zSetOperations.score(key, value) != null;
}
/**
* 获取当前ZSet中数据量的总和
*/
public Long getZSetTotalCount(String key) {
return zSetOperations.zCard(key);
}
public Set<Object> getZSetTotalData(String key) {
return zSetOperations.range(key, 0, -1);
}
//- - - - - - - - - - - - - - - - - - - - - set类型 - - - - - - - - - - - - - - - - - - - -
/**
* 将数据放入set缓存
*/
public void addToSet(String key, Object value) {
setOperations.add(key, value);
}
/**
* 弹出变量中的元素
*/
public void removeFromSet(String key, Object value) {
setOperations.remove(key, value);
}
/**
* 检查给定的元素是否在变量中。
*/
public Boolean isElementExistsInSet(String key, Object obj) {
return setOperations.isMember(key, obj);
}
//- - - - - - - - - - - - - - - - - - - - - hash类型 - - - - - - - - - - - - - - - - - - - -
/**
* 加入缓存
*/
public void addToMap(String key, Map<String, String> map) {
hashOperations.putAll(key, map);
}
/**
* 验证指定 key 下 有没有指定的 hashkey
*/
public Boolean isElementExistsInMap(String key, Object hashKey) {
return hashOperations.hasKey(key, hashKey);
}
/**
* 获取指定key的值string
*/
public String getMapValue(String key1, Object key2) {
Object value = hashOperations.get(key1, key2);
return value != null ? value.toString() : null;
}
/**
* 删除指定 hash 的 HashKey
*
* @return 删除成功的 数量
*/
public Long removeFromMap(String key, Object hashKeys) {
return hashOperations.delete(key, hashKeys);
}
//- - - - - - - - - - - - - - - - - - - - - String/Long/Integer类型支持 - - - - - - - - - - - - - - - - - - - -
/**
* 设置字符串值
*/
public void setString(String key, String value) {
valueOperations.set(key, value);
}
/**
* 设置字符串值并设置过期时间
*/
public void setString(String key, String value, long timeout, TimeUnit unit) {
valueOperations.set(key, value, timeout, unit);
}
/**
* 设置Long值
*/
public void setLong(String key, Long value) {
valueOperations.set(key, value);
}
/**
* 设置Long值并设置过期时间
*/
public void setLong(String key, Long value, long timeout, TimeUnit unit) {
valueOperations.set(key, value, timeout, unit);
}
/**
* 设置Integer值
*/
public void setInteger(String key, Integer value) {
valueOperations.set(key, value);
}
/**
* 设置Integer值并设置过期时间
*/
public void setInteger(String key, Integer value, long timeout, TimeUnit unit) {
valueOperations.set(key, value, timeout, unit);
}
/**
* 设置对象值
*/
public void setObject(String key, Object value, long timeout, TimeUnit unit) {
valueOperations.set(key, value, timeout, unit);
}
/**
* 获取字符串值
*/
public String getString(String key) {
Object value = valueOperations.get(key);
return value != null ? value.toString() : null;
}
/**
* 获取Long值
*/
public Long getLong(String key) {
Object value = valueOperations.get(key);
if (value instanceof Long) {
return (Long) value;
} else if (value instanceof Integer) {
return ((Integer) value).longValue();
} else if (value instanceof String) {
try {
return Long.parseLong((String) value);
} catch (NumberFormatException e) {
log.warn("无法将字符串转换为Long: key={}, value={}", key, value);
return null;
}
} else if (value != null) {
log.warn("不支持的类型转换到Long: key={}, type={}", key, value.getClass().getName());
}
return null;
}
/**
* 获取Long值带默认值
*/
public Long getLong(String key, Long defaultValue) {
try {
Long value = getLong(key);
return value != null ? value : defaultValue;
} catch (Exception e) {
log.warn("获取Long值失败: key={}", key, e);
return defaultValue;
}
}
/**
* 获取Integer值
*/
public Integer getInteger(String key) {
Object value = valueOperations.get(key);
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Long) {
return ((Long) value).intValue();
} else if (value instanceof String) {
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
log.warn("无法将字符串转换为Integer: key={}, value={}", key, value);
return null;
}
} else if (value != null) {
log.warn("不支持的类型转换到Integer: key={}, type={}", key, value.getClass().getName());
}
return null;
}
/**
* 获取Integer值带默认值
*/
public Integer getInteger(String key, Integer defaultValue) {
try {
Integer value = getInteger(key);
return value != null ? value : defaultValue;
} catch (Exception e) {
log.warn("获取Integer值失败: key={}", key, e);
return defaultValue;
}
}
/**
* 递增操作
*/
public Long increment(String key, long delta) {
return valueOperations.increment(key, delta);
}
/**
* 递增操作double
*/
public Double increment(String key, double delta) {
return valueOperations.increment(key, delta);
}
//- - - - - - - - - - - - - - - - - - - - - 原有方法适配 - - - - - - - - - - - - - - - - - - - -
public void addToString(String key, String value) {
setString(key, value);
}
public void addToString(String key, String value, Long expiresIn) {
setString(key, value, expiresIn, TimeUnit.SECONDS);
}
public String getFromString(String key) {
return getString(key);
}
public Set<String> getKeysFromString(String pattern) {
Set<String> keys = redisTemplate.keys(pattern);
return keys != null ? keys : Collections.emptySet();
}
public Long getSize(String key) {
return setOperations.size(key);
}
public List<Object> getMultiValue(Set<String> keys) {
return valueOperations.multiGet(keys);
}
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
public void removeFromString(String key) {
redisTemplate.delete(key);
}
public final static String PORTFOLIO_LIKE_KEY = "portfolio:like:";
public void likePost(Long portfolioId, Long userId) {
setOperations.add(PORTFOLIO_LIKE_KEY + portfolioId, userId.toString());
}
public Long getLikeCount(Long portfolioId) {
String key = PORTFOLIO_LIKE_KEY + portfolioId;
return setOperations.size(key);
}
public List<Long> getLikedPortfolios(Long userId) {
// 获取所有包含PORTFOLIO_LIKE_KEY的键
Set<String> likedPortfolios = redisTemplate.keys(PORTFOLIO_LIKE_KEY + "*");
// 如果没有喜欢的,返回空列表
if (likedPortfolios == null || likedPortfolios.isEmpty()) {
return new ArrayList<>();
}
// 过滤出包含指定用户ID的键并提取投资组合ID
return likedPortfolios.stream()
.filter(key -> setOperations.isMember(key, userId.toString()))
.map(key -> Long.valueOf(key.replace(PORTFOLIO_LIKE_KEY, "")))
.collect(Collectors.toList());
}
public void unLikePost(Long portfolioId, Long userId) {
setOperations.remove(PORTFOLIO_LIKE_KEY + portfolioId, userId.toString());
}
// 检查用户是否喜欢某个作品
public boolean isPostLikedByUser(Long portfolioId, Long userId) {
String key = PORTFOLIO_LIKE_KEY + portfolioId;
Boolean isMember = setOperations.isMember(key, userId.toString());
return isMember != null && isMember;
}
public final static String PORTFOLIO_VIEW_KEY = "portfolio:view:";
public void increaseViewCount(Long portfolioId) {
String key = PORTFOLIO_VIEW_KEY + portfolioId;
increment(key, 1);
}
public Long getViewCount(Long portfolioId) {
String key = PORTFOLIO_VIEW_KEY + portfolioId;
return getLong(key, 0L);
}
public Long getViewCount(String key) {
return getLong(key, 0L);
}
public final static String PERSONAL_HOMEPAGE_VIEW_KEY = "PersonalHomepage:view:";
public void increasePersonalHomepageViewCount(Long accountId) {
String key = PERSONAL_HOMEPAGE_VIEW_KEY + accountId;
increment(key, 1);
}
public Long getPersonalHomepageViewCount(Long accountId) {
String key = PERSONAL_HOMEPAGE_VIEW_KEY + accountId;
return getLong(key, 0L);
}
public final static String MOODBOARD_POSITION_KEY = "moodboard:position:";
public void saveMoodboardPosition(Long id, String moodboardPosition) {
setString(MOODBOARD_POSITION_KEY + id, moodboardPosition);
}
public String getMoodboardPosition(Long id) {
return getString(MOODBOARD_POSITION_KEY + id);
}
public final static String NICKNAME_MODIFY_TIMES = "NicknameModifyTimes:";
public void increaseCount(String key) {
increment(key, 1);
}
public Long getIncrementCount(String key) {
return getLong(key, 0L);
}
public void setKeyExpire(String key, Long expire) {
redisTemplate.expire(key, expire, TimeUnit.DAYS);
}
public final static String CHANGE_MAILBOX = "ChangeMailbox:";
// 每天允许通知3次
public final static String UPLOAD_TIMEOUT_REMINDER_COUNTER = "UploadTimeoutReminderCounter";
public void addProcessId(String processId, int progress) {
// Redis 中的键,可以通过 processId 来唯一标识
String redisKey = "process:progress:" + processId;
setInteger(redisKey, progress);
redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES);
}
public void addPathToCache(Long collectionId, Long userId, String path) {
// Redis 中的键,唯一标识由 collectionId 和 userId 组成
String redisKey = "path:cache:" + collectionId + ":" + userId;
// 增加路径的计数
hashOperations.increment(redisKey, path, 1);
// 设置过期时间为 2 小时7200 秒)
redisTemplate.expire(redisKey, 2, TimeUnit.HOURS);
}
public int getPathUsageCount(Long collectionId, Long userId, String path) {
String redisKey = "path:cache:" + collectionId + ":" + userId;
// 获取路径的使用次数
Object count = hashOperations.get(redisKey, path);
return count != null ? ((Number) count).intValue() : 0;
}
public void addAssembledObjects(Long collectionId, Set<DesignPythonObject> assembledObjects) {
// Redis 中的键,使用 collectionId 来唯一标识
String redisKey = "collection:assembledObjects:" + collectionId;
// 将 assembledObjects 转换为 JSON 格式存储,避免直接存储对象
String assembledObjectsJson = convertToJson(assembledObjects);
if (assembledObjectsJson != null) {
// 使用 Redis 的 set 操作更新集合
setString(redisKey, assembledObjectsJson);
// 设置过期时间为 5 分钟300 秒)
redisTemplate.expire(redisKey, 30, TimeUnit.MINUTES);
}
}
private String convertToJson(Set<DesignPythonObject> assembledObjects) {
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(assembledObjects);
} catch (JsonProcessingException e) {
log.error("JSON转换失败", e);
return null;
}
}
public Set<DesignPythonObject> getAssembledObjects(Long collectionId) {
String redisKey = "collection:assembledObjects:" + collectionId;
String assembledObjectsJson = getString(redisKey);
if (assembledObjectsJson == null) {
return new HashSet<>();
}
return convertFromJson(assembledObjectsJson);
}
private Set<DesignPythonObject> convertFromJson(String json) {
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, new TypeReference<Set<DesignPythonObject>>() {});
} catch (JsonProcessingException e) {
log.error("JSON解析失败", e);
return new HashSet<>();
}
}
public final static String PAYMENT_INFO_LAST_SCAN_TIME = "PaymentInfoLastScanTime";
public final static String AFFILIATE_LINK_VIEW_KEY = "AffiliateLink:view:";
public void increaseAffiliateLinkViewCount(Long accountId) {
String key = AFFILIATE_LINK_VIEW_KEY + accountId;
increment(key, 1);
}
public Long getAffiliateLinkViewCount(Long accountId) {
String key = AFFILIATE_LINK_VIEW_KEY + accountId;
return getLong(key, 0L);
}
public Long getAndSetKey(String key, Long count) {
Object oldValue = valueOperations.getAndSet(key, count);
if (oldValue instanceof Long) {
return (Long) oldValue;
} else if (oldValue instanceof Integer) {
return ((Integer) oldValue).longValue();
} else if (oldValue instanceof String) {
try {
return Long.parseLong((String) oldValue);
} catch (NumberFormatException e) {
log.warn("无法将字符串转换为Long: key={}, value={}", key, oldValue);
return 0L;
}
}
return 0L;
}
/**
* 记录任务的耗时到Redis
*/
public void recordTaskElapsedTime(String taskKey, long elapsedTime) {
String hashKey = "task:stats";
hashOperations.increment(hashKey, taskKey + ":totalTime", elapsedTime);
hashOperations.increment(hashKey, taskKey + ":count", 1);
}
/**
* 获取任务的平均耗时
*/
public double getTaskAverageTime(String taskKey) {
String hashKey = "task:stats";
Object totalTime = hashOperations.get(hashKey, taskKey + ":totalTime");
Object count = hashOperations.get(hashKey, taskKey + ":count");
if (totalTime == null || count == null) {
return 0;
}
try {
double total = ((Number) totalTime).doubleValue();
long cnt = ((Number) count).longValue();
return cnt > 0 ? total / cnt : 0;
} catch (Exception e) {
log.warn("计算平均耗时失败: taskKey={}", taskKey, e);
return 0;
}
}
/**
* 清除指定任务的统计数据
* @param taskKey 任务标识,如 "taskA"
*/
public void clearTaskStats(String taskKey) {
String hashKey = "task:stats";
// 删除总耗时和计数器
hashOperations.delete(hashKey, taskKey + ":totalTime", taskKey + ":count");
}
public void recordTaskElapsedTime(String taskKey, double elapsedTimeInSeconds) {
// 将耗时转换为 BigDecimal并四舍五入保留四位小数
BigDecimal elapsedTime = new BigDecimal(elapsedTimeInSeconds).setScale(4, RoundingMode.HALF_UP);
hashOperations.increment("task:stats", taskKey + ":totalTime", elapsedTime.doubleValue());
hashOperations.increment("task:stats", taskKey + ":count", 1);
}
// 获取第一部分Sketch耗时
public double getFirstSketchTime() {
Object time = hashOperations.get("task:stats", "firstSketchTime:totalTime");
return time != null ? ((Number) time).doubleValue() : 0.0;
}
// 获取第二部分(获取特征值)耗时
public double getGetAttributeRecognitionTime() {
Object time = hashOperations.get("task:stats", "getAttributeRecognitionTime:totalTime");
return time != null ? ((Number) time).doubleValue() : 0.0;
}
// 获取第三部分(搭配 Sketch耗时
public double getOtherSketchTime() {
Object time = hashOperations.get("task:stats", "otherSketchTime:totalTime");
return time != null ? ((Number) time).doubleValue() : 0.0;
}
// 清理三部分的缓存
public void clearTaskElapsedTimeCache() {
String hashKey = "task:stats";
Object[] keysToDelete = {
"firstSketchTime:totalTime", "firstSketchTime:count",
"getAttributeRecognitionTime:totalTime", "getAttributeRecognitionTime:count",
"otherSketchTime:totalTime", "otherSketchTime:count"
};
hashOperations.delete(hashKey, keysToDelete);
}
public boolean incrementLikeCount(Long userId, String sketchPath) {
String redisKey = "user_like_count:" + userId;
try {
hashOperations.increment(redisKey, sketchPath, 1);
return true;
} catch (Exception e) {
log.error("Error incrementing like count for userId {} and sketchPath {}: {}", userId, sketchPath, e.getMessage());
return false;
}
}
public int getLikeCount(Long userId, String sketchPath) {
String redisKey = "user_like_count:" + userId;
Object count = hashOperations.get(redisKey, sketchPath);
return count != null ? ((Number) count).intValue() : 0;
}
public void storeMaxLikeCount(Long userId, int maxLikeCount) {
String redisKey = "user_max_like_count:" + userId;
setInteger(redisKey, maxLikeCount);
}
public int getMaxLikeCount(Long userId) {
String redisKey = "user_max_like_count:" + userId;
return getInteger(redisKey, 0);
}
public final static String IMAGE_SEGMENTATION = "ImageSegmentation:";
public final static String STRIPE_EXCEPTION_LOG = "StripeException:";
public void batchDeleteKeysWithSamePrefix(String prefix) {
Set<String> keys = redisTemplate.keys(prefix + "*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
public void setTaskProgressDTO(String taskId, ProgressDTO dto) {
String key = "task:progress:" + taskId;
setString(key, JSON.toJSONString(dto));
redisTemplate.expire(key, 1, TimeUnit.DAYS);
}
public ProgressDTO getTaskProgressDTO(String taskId) {
String key = "task:progress:" + taskId;
String json = getString(key);
if (StringUtils.isBlank(json)) {
return null;
}
try {
return JSON.parseObject(json, ProgressDTO.class);
} catch (Exception e) {
log.warn("任务进度解析失败 key={}, json={}", key, json);
return new ProgressDTO(0, 0, false);
}
}
// Lua脚本
private static final String RATE_LIMIT_SCRIPT =
"local current = redis.call('INCR', KEYS[1])\n" +
"local ttl = redis.call('TTL', KEYS[1])\n" +
"if tonumber(current) == 1 or tonumber(ttl) == -1 then\n" +
" redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
"end\n" +
"return tonumber(current) <= tonumber(ARGV[2])";
/**
* 检查是否允许发送
* @param userId 用户ID
* @return true-允许发送false-已超限
*/
public boolean allowSend(Long userId) {
String hourKey = getCurrentHourKey(userId);
// 执行Lua脚本
List<String> keys = Collections.singletonList(hourKey);
// 1小时过期限制数量 一小时只能向普通用户发10封
String[] args = new String[]{"3600", "10"};
Boolean result = redisTemplate.execute(
new DefaultRedisScript<>(RATE_LIMIT_SCRIPT, Boolean.class),
keys,
args
);
return Boolean.TRUE.equals(result);
}
/**
* 获取当前小时的Key
*/
private String getCurrentHourKey(Long userId) {
String hour = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
return String.format("email_limit:%s:%s", userId, hour);
}
/**
* 获取当前已发送数量
*/
public int getCurrentCount(Long userId) {
String key = getCurrentHourKey(userId);
return getInteger(key, 0);
}
public boolean allowRequest(String apiKey) {
String key = "rate_limit:" + apiKey;
// 使用Redis的INCR命令
Long count = increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
return count <= 3;
}
// 新增方法安全删除key
public boolean safeDelete(String key) {
try {
return Boolean.TRUE.equals(redisTemplate.delete(key));
} catch (Exception e) {
log.error("删除Redis key失败: {}", key, e);
return false;
}
}
// 新增方法:批量设置过期时间
public void batchExpire(Set<String> keys, long timeout, TimeUnit unit) {
if (keys != null && !keys.isEmpty()) {
for (String key : keys) {
redisTemplate.expire(key, timeout, unit);
}
}
}
}