BUGFIX:1、子账号查询,无法批量根据用户名或邮箱查
2、教育版子账号创建及修改涉及的积分问题
This commit is contained in:
@@ -380,15 +380,6 @@ public class RedisUtil {
|
|||||||
return redisTemplate.opsForValue().increment(key, 0);
|
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
|
* 记录任务的耗时到Redis
|
||||||
* @param taskKey 任务标识,如 "taskA"
|
* @param taskKey 任务标识,如 "taskA"
|
||||||
|
|||||||
743
src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java
Normal file
743
src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@ import com.ai.da.model.vo.AccountPreLoginVO;
|
|||||||
import com.ai.da.model.vo.BindEmailVO;
|
import com.ai.da.model.vo.BindEmailVO;
|
||||||
import com.ai.da.model.vo.PersonalHomepageVO;
|
import com.ai.da.model.vo.PersonalHomepageVO;
|
||||||
import com.ai.da.service.AccountService;
|
import com.ai.da.service.AccountService;
|
||||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
|
||||||
import io.minio.errors.MinioException;
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -26,7 +24,6 @@ import javax.annotation.Resource;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package com.ai.da.model.dto;
|
|||||||
import com.ai.da.model.vo.PageQueryBaseVo;
|
import com.ai.da.model.vo.PageQueryBaseVo;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class SubAccountPageDTO extends PageQueryBaseVo {
|
public class SubAccountPageDTO extends PageQueryBaseVo {
|
||||||
private String startTime;
|
private String startTime;
|
||||||
|
|
||||||
private String endTime;
|
private String endTime;
|
||||||
|
|
||||||
private String email;
|
private List<String> email;
|
||||||
|
|
||||||
private String userName;
|
private List<String> userName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2160,13 +2160,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
@Override
|
@Override
|
||||||
public Boolean addSubAccount(AddSubAccountDTO addSubAccountDTO) {
|
public Boolean addSubAccount(AddSubAccountDTO addSubAccountDTO) {
|
||||||
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
|
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
|
||||||
Account account = accountMapper.selectById(authPrincipalVo.getId());
|
Account adminAcc = accountMapper.selectById(authPrincipalVo.getId());
|
||||||
int subUserRole = getSubUserRole(account.getSystemUser());
|
int subUserRole = getSubUserRole(adminAcc.getSystemUser());
|
||||||
|
|
||||||
if (addSubAccountDTO.getId() == null) {
|
if (addSubAccountDTO.getId() == null) {
|
||||||
return createSubAccount(addSubAccountDTO, account, subUserRole, addSubAccountDTO.getCreditsUsageLimit(), null);
|
return createSubAccount(addSubAccountDTO, adminAcc, subUserRole);
|
||||||
} else {
|
} else {
|
||||||
return updateSubAccount(addSubAccountDTO, account, subUserRole);
|
return updateSubAccount(addSubAccountDTO, adminAcc, subUserRole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2182,135 +2182,184 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account account, int subUserRole,
|
public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) {
|
||||||
BigDecimal creditsLimit, BigDecimal creditsUsage) {
|
|
||||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||||
qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName());
|
qw.lambda().eq(Account::getOrganizationName, adminAcc.getOrganizationName());
|
||||||
List<Account> accounts = accountMapper.selectList(qw);
|
List<Account> accounts = accountMapper.selectList(qw);
|
||||||
|
|
||||||
// 校验子账号总数是否达上限
|
// 校验子账号总数是否达上限
|
||||||
if (account.getSubAccountNum() == null || account.getSubAccountNum() <= 0){
|
if (adminAcc.getSubAccountNum() == null || adminAcc.getSubAccountNum() <= 0){
|
||||||
throw new BusinessException("Error: Sub-account quota reached (Max: 0). Upgrade to create more.");
|
throw new BusinessException("Error: Sub-account quota reached (Max: 0). Upgrade to create more.");
|
||||||
}
|
}
|
||||||
if (accounts.size() >= account.getSubAccountNum()) {
|
if (accounts.size() >= adminAcc.getSubAccountNum()) {
|
||||||
throw new BusinessException("Error: Sub-account quota reached (Max: " + account.getSubAccountNum() + "). Upgrade to create more.");
|
throw new BusinessException("Error: Sub-account quota reached (Max: " + adminAcc.getSubAccountNum() + "). Upgrade to create more.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.isNullOrEmpty(addSubAccountDTO.getUserEmail())){
|
||||||
|
throw new BusinessException("email.cannot.be.empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.isNullOrEmpty(addSubAccountDTO.getUserPassword())){
|
||||||
|
throw new BusinessException("password.cannot.be.empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验邮箱是否已加入组织
|
// 校验邮箱是否已加入组织
|
||||||
if (isUserEmailExists(account.getOrganizationName(), addSubAccountDTO.getUserEmail())) {
|
if (isUserEmailExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserEmail())) {
|
||||||
throw new BusinessException("This organization already has an account with the same email.", ResultEnum.PROMPT.getCode());
|
throw new BusinessException("This organization already has an account with the same email.", ResultEnum.PROMPT.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验用户名是否同名
|
// 校验用户名是否同名
|
||||||
if (isUsernameExists(account.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
if (isUsernameExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
||||||
throw new BusinessException("This organization already has an account with the same username.");
|
throw new BusinessException("This organization already has an account with the same username.");
|
||||||
}
|
}
|
||||||
// 之后是否需要检验密码不能设置为空
|
|
||||||
|
|
||||||
// 校验当前账号邮箱是否有个人账号
|
// 校验当前账号邮箱是否有个人账号
|
||||||
Account subAccount = accountMapper.selectOne(new QueryWrapper<Account>().eq("user_email", addSubAccountDTO.getUserEmail()));
|
Account subAccount = accountMapper.selectOne(new QueryWrapper<Account>().eq("user_email", addSubAccountDTO.getUserEmail()));
|
||||||
List<Integer> personAccRole = Arrays.asList(0, 1, 2, 3);
|
List<Integer> personAccRole = Arrays.asList(0, 1, 2, 3);
|
||||||
List<Integer> orgAccRole = Arrays.asList(5, 6, 7, 8);
|
List<Integer> orgAccRole = Arrays.asList(5, 6, 7, 8);
|
||||||
|
|
||||||
|
BigDecimal remainingCredits = adminRemainingCredits(adminAcc);
|
||||||
|
// 将个人账号加入组织
|
||||||
if (Objects.nonNull(subAccount) && personAccRole.contains(subAccount.getSystemUser())) {
|
if (Objects.nonNull(subAccount) && personAccRole.contains(subAccount.getSystemUser())) {
|
||||||
log.info("将用户{} 加入组织{}", addSubAccountDTO.getUserEmail(), account.getOrganizationName());
|
log.info("将用户{} 加入组织{}", addSubAccountDTO.getUserEmail(), adminAcc.getOrganizationName());
|
||||||
subAccount.setUserName(addSubAccountDTO.getUserName());
|
subAccount.setUserName(addSubAccountDTO.getUserName());
|
||||||
subAccount.setUserPassword(addSubAccountDTO.getUserPassword());
|
subAccount.setUserPassword(addSubAccountDTO.getUserPassword());
|
||||||
subAccount.setSystemUser(subUserRole);
|
subAccount.setSystemUser(subUserRole);
|
||||||
subAccount.setOrganizationName(account.getOrganizationName());
|
subAccount.setOrganizationName(adminAcc.getOrganizationName());
|
||||||
subAccount.setParentId(account.getId());
|
subAccount.setParentId(adminAcc.getId());
|
||||||
if (Objects.nonNull(creditsLimit)){
|
if (Objects.nonNull(subAccount.getCreditsUsageLimit())){
|
||||||
subAccount.setCreditsUsageLimit(creditsLimit);
|
if (remainingCredits.compareTo(subAccount.getCreditsUsageLimit()) < 0) {
|
||||||
subAccount.setCreditsUsage(creditsUsage);
|
throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode());
|
||||||
if (Objects.nonNull(subAccount.getCredits())) {
|
|
||||||
subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits()));
|
|
||||||
}
|
}
|
||||||
}else {
|
subAccount.setCreditsUsageLimit(subAccount.getCreditsUsageLimit());
|
||||||
handleSubAccCredits(subAccount, account);
|
subAccount.setCreditsUsage(subAccount.getCreditsUsageLimit());
|
||||||
}
|
|
||||||
subAccount.setUpdateDate(new Date());
|
|
||||||
updateById(subAccount);
|
|
||||||
updateById(account);
|
|
||||||
} else if (Objects.nonNull(subAccount) && orgAccRole.contains(subAccount.getSystemUser())) {
|
|
||||||
throw new BusinessException("邮箱 " + addSubAccountDTO.getUserEmail() + " 已加入其他组织", ResultEnum.PROMPT.getCode());
|
|
||||||
} else {
|
|
||||||
subAccount = new Account();
|
|
||||||
subAccount.setUserName(addSubAccountDTO.getUserName());
|
|
||||||
subAccount.setUserEmail(addSubAccountDTO.getUserEmail());
|
|
||||||
subAccount.setUserPassword(addSubAccountDTO.getUserPassword());
|
|
||||||
if (Objects.nonNull(creditsLimit)){
|
|
||||||
subAccount.setCreditsUsageLimit(creditsLimit);
|
|
||||||
subAccount.setCreditsUsage(creditsUsage);
|
|
||||||
if (Objects.nonNull(subAccount.getCredits())) {
|
if (Objects.nonNull(subAccount.getCredits())) {
|
||||||
subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits()));
|
subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits()));
|
||||||
}else {
|
}else {
|
||||||
subAccount.setCredits(subAccount.getCreditsUsageLimit());
|
subAccount.setCredits(subAccount.getCreditsUsageLimit());
|
||||||
}
|
}
|
||||||
} else {
|
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(subAccount.getCreditsUsageLimit()));
|
||||||
handleSubAccCredits(subAccount, account);
|
adminAcc.setCredits(adminAcc.getCreditsUsageLimit().subtract(adminAcc.getCreditsUsage()));
|
||||||
updateById(account);
|
adminAcc.setUpdateDate(new Date());
|
||||||
|
}else {
|
||||||
|
handleSubAccCredits(subAccount, adminAcc);
|
||||||
|
}
|
||||||
|
subAccount.setUpdateDate(new Date());
|
||||||
|
updateById(subAccount);
|
||||||
|
updateById(adminAcc);
|
||||||
|
}
|
||||||
|
// 输入的账号已存在于其他组织
|
||||||
|
else if (Objects.nonNull(subAccount) && orgAccRole.contains(subAccount.getSystemUser())) {
|
||||||
|
throw new BusinessException("邮箱 " + addSubAccountDTO.getUserEmail() + " 已加入其他组织", ResultEnum.PROMPT.getCode());
|
||||||
|
}
|
||||||
|
// 完全新建一个账号
|
||||||
|
else {
|
||||||
|
subAccount = new Account();
|
||||||
|
|
||||||
|
subAccount.setUserName(addSubAccountDTO.getUserName());
|
||||||
|
subAccount.setUserEmail(addSubAccountDTO.getUserEmail());
|
||||||
|
subAccount.setUserPassword(addSubAccountDTO.getUserPassword());
|
||||||
|
|
||||||
|
// 指定积分上限
|
||||||
|
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())){
|
||||||
|
if (remainingCredits.compareTo(addSubAccountDTO.getCreditsUsageLimit()) < 0) {
|
||||||
|
throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode());
|
||||||
|
}
|
||||||
|
subAccount.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit());
|
||||||
|
subAccount.setCreditsUsage(Objects.isNull(addSubAccountDTO.getCreditsUsage()) ? BigDecimal.ZERO : addSubAccountDTO.getCreditsUsage());
|
||||||
|
if (Objects.nonNull(subAccount.getCredits())) {
|
||||||
|
subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits()));
|
||||||
|
}else {
|
||||||
|
subAccount.setCredits(subAccount.getCreditsUsageLimit());
|
||||||
|
}
|
||||||
|
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(subAccount.getCreditsUsageLimit()));
|
||||||
|
adminAcc.setCredits(adminAcc.getCreditsUsageLimit().subtract(adminAcc.getCreditsUsage()));
|
||||||
|
adminAcc.setUpdateDate(new Date());
|
||||||
|
}
|
||||||
|
// 未指定积分使用上限
|
||||||
|
else {
|
||||||
|
handleSubAccCredits(subAccount, adminAcc);
|
||||||
|
updateById(adminAcc);
|
||||||
}
|
}
|
||||||
subAccount.setSystemUser(subUserRole);
|
subAccount.setSystemUser(subUserRole);
|
||||||
subAccount.setLanguage(Language.ENGLISH.name());
|
subAccount.setLanguage(Language.ENGLISH.name());
|
||||||
subAccount.setCreateDate(new Date());
|
subAccount.setCreateDate(new Date());
|
||||||
subAccount.setIsTrial(0);
|
subAccount.setIsTrial(0);
|
||||||
subAccount.setIsBeginner(1);
|
subAccount.setIsBeginner(1);
|
||||||
subAccount.setParentId(account.getId());
|
subAccount.setParentId(adminAcc.getId());
|
||||||
subAccount.setOrganizationName(account.getOrganizationName());
|
subAccount.setOrganizationName(adminAcc.getOrganizationName());
|
||||||
accountMapper.insert(subAccount);
|
accountMapper.insert(subAccount);
|
||||||
}
|
}
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean updateSubAccount(AddSubAccountDTO addSubAccountDTO, Account account, int subUserRole) {
|
private Boolean updateSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) {
|
||||||
Account exAccountInfo = baseMapper.selectById(addSubAccountDTO.getId());
|
Account exAccountInfo = baseMapper.selectById(addSubAccountDTO.getId());
|
||||||
|
|
||||||
// 校验用户名是否同名
|
// 校验用户名是否同名
|
||||||
if (!exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName()) && isUsernameExists(account.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserName())
|
||||||
|
&& !exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())
|
||||||
|
&& isUsernameExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
||||||
throw new BusinessException("This organization already has an account with the same username.");
|
throw new BusinessException("This organization already has an account with the same username.");
|
||||||
}else if (!exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())){
|
}else if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserName())
|
||||||
|
&& !exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())){
|
||||||
exAccountInfo.setUserName(addSubAccountDTO.getUserName());
|
exAccountInfo.setUserName(addSubAccountDTO.getUserName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断积分变更是增加还是减少还是没变化
|
// 判断积分变更是增加还是减少还是没变化,需修改子账号的credits\creditsUsageLimit,管理员账号的creditUsage
|
||||||
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit()) && exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) < 0) {
|
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())
|
||||||
BigDecimal remainingCredits = adminRemainingCredits(account);
|
&& exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) < 0) {
|
||||||
|
BigDecimal remainingCredits = adminRemainingCredits(adminAcc);
|
||||||
// 新增加的积分
|
// 新增加的积分
|
||||||
BigDecimal addedCredits = addSubAccountDTO.getCreditsUsageLimit().subtract(exAccountInfo.getCreditsUsageLimit());
|
BigDecimal addedCredits = addSubAccountDTO.getCreditsUsageLimit().subtract(exAccountInfo.getCreditsUsageLimit());
|
||||||
if (remainingCredits.compareTo(addedCredits) >= 0) {
|
if (remainingCredits.compareTo(addedCredits) >= 0) {
|
||||||
// 更新管理员已分配的积分
|
// 更新管理员已分配的积分
|
||||||
account.setCreditsUsage(account.getCreditsUsage().add(addedCredits));
|
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(addedCredits));
|
||||||
// 更新子账号的积分上限
|
adminAcc.setCredits(adminAcc.getCreditsUsageLimit().subtract(adminAcc.getCreditsUsage()));
|
||||||
|
// 更新子账号的积分上限和目前所有积分总数
|
||||||
exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit());
|
exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit());
|
||||||
|
exAccountInfo.setCredits(exAccountInfo.getCredits().add(addedCredits));
|
||||||
} else {
|
} else {
|
||||||
throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode());
|
throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode());
|
||||||
}
|
}
|
||||||
} else if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit()) && exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) {
|
} else if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())
|
||||||
|
&& exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) {
|
||||||
if (exAccountInfo.getCreditsUsage().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) {
|
if (exAccountInfo.getCreditsUsage().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) {
|
||||||
throw new BusinessException("Usage alert: " + exAccountInfo.getCreditsUsage() + " credits consumed this month. New limit must be ≥ " + exAccountInfo.getCreditsUsage() + " .");
|
throw new BusinessException("Usage alert: " + exAccountInfo.getCreditsUsage() + " credits consumed this month. New limit must be ≥ " + exAccountInfo.getCreditsUsage() + " .");
|
||||||
} else {
|
} else {
|
||||||
// 减少的积分
|
// 减少的积分
|
||||||
BigDecimal subtractedCredits = exAccountInfo.getCreditsUsageLimit().subtract(addSubAccountDTO.getCreditsUsageLimit());
|
BigDecimal subtractedCredits = exAccountInfo.getCreditsUsageLimit().subtract(addSubAccountDTO.getCreditsUsageLimit());
|
||||||
// 更新管理员已分配的积分(积分回流)
|
// 更新管理员已分配的积分(积分回流)
|
||||||
account.setCreditsUsage(account.getCreditsUsage().subtract(subtractedCredits));
|
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().subtract(subtractedCredits));
|
||||||
// 更新子账号的积分上限
|
adminAcc.setCredits(adminAcc.getCreditsUsageLimit().subtract(adminAcc.getCreditsUsage()));
|
||||||
|
// 更新子账号的积分上限和目前所有积分总数
|
||||||
exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit());
|
exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit());
|
||||||
|
exAccountInfo.setCredits(exAccountInfo.getCredits().subtract(subtractedCredits));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验密码是否变更
|
||||||
|
if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserPassword())
|
||||||
|
&& !exAccountInfo.getUserPassword().equals(addSubAccountDTO.getUserPassword())){
|
||||||
|
exAccountInfo.setUserPassword(addSubAccountDTO.getUserPassword());
|
||||||
|
}
|
||||||
|
|
||||||
// 校验邮箱是否变更
|
// 校验邮箱是否变更
|
||||||
if (!exAccountInfo.getUserEmail().equals(addSubAccountDTO.getUserEmail())) {
|
if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserEmail())
|
||||||
|
&& !exAccountInfo.getUserEmail().equals(addSubAccountDTO.getUserEmail())) {
|
||||||
// 原账号的积分使用上限
|
// 原账号的积分使用上限
|
||||||
BigDecimal creditsLimit = exAccountInfo.getCreditsUsageLimit();
|
// BigDecimal creditsLimit = exAccountInfo.getCreditsUsageLimit();
|
||||||
|
addSubAccountDTO.setCreditsUsageLimit(exAccountInfo.getCreditsUsageLimit());
|
||||||
// 原账号已使用的积分
|
// 原账号已使用的积分
|
||||||
BigDecimal creditsUsage = exAccountInfo.getCreditsUsage();
|
// BigDecimal creditsUsage = exAccountInfo.getCreditsUsage();
|
||||||
|
addSubAccountDTO.setCreditsUsage(exAccountInfo.getCreditsUsage());
|
||||||
// 这里移除原账号,但是积分不回流,机构分配的积分会由下一个账号继续持有(包括积分上限和已使用的积分都保持不变)
|
// 这里移除原账号,但是积分不回流,机构分配的积分会由下一个账号继续持有(包括积分上限和已使用的积分都保持不变)
|
||||||
removeSubAccount(new AddSubAccountDTO(Collections.singletonList(addSubAccountDTO.getId())), false);
|
removeSubAccount(new AddSubAccountDTO(Collections.singletonList(addSubAccountDTO.getId())), false);
|
||||||
// 移入新子账号(可能是移入,也可能是新增)
|
// 移入新子账号(可能是移入,也可能是新增)
|
||||||
createSubAccount(addSubAccountDTO, account, subUserRole, creditsLimit, creditsUsage);
|
createSubAccount(addSubAccountDTO, adminAcc, subUserRole);
|
||||||
} else {
|
} else {
|
||||||
baseMapper.updateById(exAccountInfo);
|
baseMapper.updateById(exAccountInfo);
|
||||||
baseMapper.updateById(account);
|
baseMapper.updateById(adminAcc);
|
||||||
}
|
}
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
@@ -2364,6 +2413,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
subAcc.setCreditsUsageLimit(BigDecimal.ZERO);
|
subAcc.setCreditsUsageLimit(BigDecimal.ZERO);
|
||||||
}
|
}
|
||||||
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(subAcc.getCreditsUsageLimit()));
|
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(subAcc.getCreditsUsageLimit()));
|
||||||
|
adminAcc.setCredits(adminAcc.getCreditsUsageLimit().subtract(adminAcc.getCreditsUsage()));
|
||||||
adminAcc.setUpdateDate(new Date());
|
adminAcc.setUpdateDate(new Date());
|
||||||
log.debug("分配积分: subAccId={}, defaultCredits={}, remainingCredits={}", subAcc.getId(), defaultCredits, remainingCredits);
|
log.debug("分配积分: subAccId={}, defaultCredits={}, remainingCredits={}", subAcc.getId(), defaultCredits, remainingCredits);
|
||||||
|
|
||||||
@@ -2426,7 +2476,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
}
|
}
|
||||||
// 是否需要将积分回流
|
// 是否需要将积分回流
|
||||||
if (returnCredits && unusedCreditsTotal.compareTo(BigDecimal.ZERO) != 0){
|
if (returnCredits && unusedCreditsTotal.compareTo(BigDecimal.ZERO) != 0){
|
||||||
adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().subtract(unusedCreditsTotal));
|
BigDecimal subtracted = adminAcc.getCreditsUsage().subtract(unusedCreditsTotal);
|
||||||
|
adminAcc.setCreditsUsage(subtracted.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : subtracted);
|
||||||
adminAcc.setUpdateDate(new Date());
|
adminAcc.setUpdateDate(new Date());
|
||||||
baseMapper.updateById(adminAcc);
|
baseMapper.updateById(adminAcc);
|
||||||
}
|
}
|
||||||
@@ -2462,12 +2513,17 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
if (StringUtils.isNotBlank(subAccountPageDTO.getEndTime())) {
|
if (StringUtils.isNotBlank(subAccountPageDTO.getEndTime())) {
|
||||||
qw.lambda().le(Account::getCreateDate, subAccountPageDTO.getEndTime());
|
qw.lambda().le(Account::getCreateDate, subAccountPageDTO.getEndTime());
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(subAccountPageDTO.getEmail())) {
|
if (subAccountPageDTO.getEmail() != null && subAccountPageDTO.getEmail().size() == 1){
|
||||||
qw.lambda().like(Account::getUserEmail, subAccountPageDTO.getEmail());
|
qw.lambda().like(Account::getUserEmail, subAccountPageDTO.getEmail().get(0));
|
||||||
|
}else if (subAccountPageDTO.getEmail() != null && subAccountPageDTO.getEmail().size() > 1){
|
||||||
|
qw.lambda().in(Account::getUserEmail, subAccountPageDTO.getEmail());
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(subAccountPageDTO.getUserName())) {
|
if (subAccountPageDTO.getUserName() != null && subAccountPageDTO.getUserName().size() == 1){
|
||||||
qw.lambda().like(Account::getUserName, subAccountPageDTO.getUserName());
|
qw.lambda().like(Account::getUserName, subAccountPageDTO.getUserName().get(0));
|
||||||
|
}else if (subAccountPageDTO.getUserName() != null && subAccountPageDTO.getUserName().size() > 1){
|
||||||
|
qw.lambda().in(Account::getUserName, subAccountPageDTO.getUserName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行分页查询
|
// 执行分页查询
|
||||||
IPage<Account> page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw);
|
IPage<Account> page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.ai.da.common.response.PageBaseResponse;
|
|||||||
import com.ai.da.common.response.ResultEnum;
|
import com.ai.da.common.response.ResultEnum;
|
||||||
import com.ai.da.common.utils.CopyUtil;
|
import com.ai.da.common.utils.CopyUtil;
|
||||||
import com.ai.da.common.utils.RedisUtil;
|
import com.ai.da.common.utils.RedisUtil;
|
||||||
|
import com.ai.da.common.utils.RedisUtilEnhance;
|
||||||
import com.ai.da.common.utils.SendEmailUtil;
|
import com.ai.da.common.utils.SendEmailUtil;
|
||||||
import com.ai.da.mapper.primary.*;
|
import com.ai.da.mapper.primary.*;
|
||||||
import com.ai.da.mapper.primary.entity.*;
|
import com.ai.da.mapper.primary.entity.*;
|
||||||
@@ -298,6 +299,9 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
|||||||
return redisUtil.getAffiliateLinkViewCount(affiliateId);
|
return redisUtil.getAffiliateLinkViewCount(affiliateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisUtilEnhance redisUtilEnhance;
|
||||||
|
|
||||||
public void syncLinkViewCountToDB() {
|
public void syncLinkViewCountToDB() {
|
||||||
// 1、获取当前所有激活状态的affiliate
|
// 1、获取当前所有激活状态的affiliate
|
||||||
List<Affiliate> affiliateList = baseMapper.selectList(new QueryWrapper<Affiliate>().lambda().eq(Affiliate::getStatus, "Active"));
|
List<Affiliate> affiliateList = baseMapper.selectList(new QueryWrapper<Affiliate>().lambda().eq(Affiliate::getStatus, "Active"));
|
||||||
@@ -307,7 +311,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
|
|||||||
String redisKey = AFFILIATE_LINK_VIEW_KEY + affiliate.getId();
|
String redisKey = AFFILIATE_LINK_VIEW_KEY + affiliate.getId();
|
||||||
|
|
||||||
// 原子性获取并重置为0
|
// 原子性获取并重置为0
|
||||||
Long redisCount = redisUtil.getAndSetKey(redisKey, 0L);
|
Long redisCount = redisUtilEnhance.getAndSetKey(redisKey, 0L);
|
||||||
|
|
||||||
if (redisCount != null && redisCount > 0) {
|
if (redisCount != null && redisCount > 0) {
|
||||||
// 累加到数据库
|
// 累加到数据库
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user