/**
* 获取用户当前排队位置
* @return
*/
@Override
public Map<String, Object> getUserQueuePosition() {
Map<String, Object> result = new HashMap<>();
Long userId = null;
try {
UserDto user = storeUserService.getCurrentMember();
if (OtherUtil.isNull(user) || OtherUtil.isNull(user.getUserId())) {
result.put("message", "用户未登录");
result.put("canSubmit", false);
return result;
}
userId = user.getUserId();
String userIdStr = String.valueOf(userId);
// 1. 刷新心跳
redisTemplate.opsForZSet().add(AllClapConstUtil.QUEUE_HEARTBEAT_ZSET_KEY, userId, System.currentTimeMillis());
// 限流检查
if (!checkUserRequestRate(userIdStr)) {
result.put("message", "请求过于频繁,请稍后再试");
result.put("canSubmit", false);
return result;
}
String userTaskKey = AllClapConstUtil.USER_TASK_KEY_PREFIX + userId;
String taskId = (String) redisTemplate.opsForValue().get(userTaskKey);
// 使用分布式锁
RLock lock = redissonClient != null ?
redissonClient.getLock(AllClapConstUtil.USER_TASK_LOCK_KEY_PREFIX + "queue_position:" + userId) : null;
if (lock != null) {
try {
if (!lock.tryLock(2, 5, TimeUnit.SECONDS)) {
result.put("message", "系统繁忙,请稍后重试!");
result.put("canSubmit", false);
return result;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
result.put("message", "系统繁忙,请稍后重试!");
result.put("canSubmit", false);
return result;
}
}
try {
// 2. 直接获取队列全长(权威数据源)
Long totalQueueSizeLong = redisTemplate.opsForList().size(AllClapConstUtil.TASK_QUEUE_KEY);
int totalQueueSize = (totalQueueSizeLong == null) ? 0 : totalQueueSizeLong.intValue();
if (taskId == null) {
// === 用户没有任务,创建新任务 ===
// 创建任务ID
taskId = userIdStr;
redisTemplate.opsForValue().set(userTaskKey, taskId);
// 入队
redisTemplate.opsForList().rightPush(AllClapConstUtil.TASK_QUEUE_KEY, taskId);
// 更新总数(入队后 +1)
totalQueueSize = totalQueueSize + 1;
// 新用户的位置就是当前队列长度
int newPosition = totalQueueSize;
// log.info("创建排队任务,用户ID: {}, 任务ID: {}, 当前位置: {}, 总人数: {}", userId, taskId, newPosition, totalQueueSize);
result.put("inQueue", true);
result.put("canSubmit", newPosition <= queueCapacity);
// 显示逻辑
if (newPosition <= queueCapacity) {
result.put("position", newPosition);
} else {
result.put("position", newPosition - queueCapacity);
result.put("message", "您当前排队是第" + (newPosition - queueCapacity) + "位");
}
return result;
} else {
// === 用户已有任务,检查位置 ===
List<Object> queueTasks = redisTemplate.opsForList().range(AllClapConstUtil.TASK_QUEUE_KEY, 0, -1);
if (queueTasks == null || !queueTasks.contains(taskId)) {
// 任务不在队列中(已处理或异常),清理旧记录并重新排队(复用上面的逻辑,这里简化处理)
redisTemplate.delete(userTaskKey);
// ... 这里可以递归调用或者复制上面的创建逻辑,为节省篇幅略 ...
// 建议:提示用户重新排队
result.put("inQueue", false);
result.put("message", "任务已失效,请重新排队");
return result;
}
// 计算当前位置 (List 索引从 0 开始,位置从 1 开始)
int position = queueTasks.indexOf(taskId) + 1;
result.put("inQueue", true);
//result.put("totalQueueSize", totalQueueSize);
result.put("canSubmit", position <= queueCapacity);
// 显示逻辑
if (position <= queueCapacity) {
result.put("position", position);
//result.put("message", "当前共" + totalQueueSize + "个任务");
} else {
int waitingPosition = position - queueCapacity;
result.put("position", waitingPosition);
result.put("message", "您当前排队是第" + waitingPosition + "位");
}
return result;
}
} finally {
if (lock != null) {
try { lock.unlock(); } catch (Exception e) { log.warn("释放锁异常", e); }
}
}
} catch (Exception e) {
log.error("获取排队位置异常", e);
result.put("message", "获取排队位置失败!");
result.put("canSubmit", false);
}
return result;
}
@Override
public Object saveHiddenDangerInfo(HiddenDangerRecognitionDTO hiddenDangerRecognition, boolean iSQueueUp) {
String imageInfo = hiddenDangerRecognition.getImageInfo();
String info = null;
String attachmentName = imageInfo;
String realImage11 = RequestHeadTypeEnum.HTTP.getName() + minioConfig.getHost() + ":" + minioConfig.getPort() + imageInfo;
log.debug(">>> convertImageUrlToBase64:" + realImage11);
try {
if (!imageInfo.startsWith(RequestHeadTypeEnum.HTTPS.getName()))
imageInfo = AllClapConstUtil.ALL_URL_PREFIX + imageInfo;
log.debug(">>> imageInfo:" + imageInfo);
String imageLoadPath = imageInfo;
// UserDto user = storeUserService.getCurrentMember();
// 优先使用DTO中传递的用户信息
UserDto user = hiddenDangerRecognition.getCurrentUser();
if (user == null) {
throw new RuntimeException("用户信息缺失,无法完成识别");
}
// 验证用户排队任务并从队列中移除
if (iSQueueUp) {
log.info("用户: {}, 开始排队识别", user.getNickName());
validateAndRemoveUserTaskFromQueue(user);
}
// 创建final版本的finalUserTaskKey用于lambda表达式
final String finalUserTaskKey = AllClapConstUtil.USER_TASK_KEY_PREFIX + user.getUserId();
// HiddenDangerRecognition recognition = new HiddenDangerRecognition();
HiddenDangerRecognitionTipsVO recognition = new HiddenDangerRecognitionTipsVO();
String name = user.getNickName();
Long userId = user.getUserId();
Date date = new Date();
imageInfo = imageInfo.replace(AllClapConstUtil.ALL_URL_PREFIX, "");
recognition.setImageInfo(imageInfo);
recognition.setRecognizer(name);
recognition.setRecognizerId(userId);
recognition.setRecognitionTime(date);
recognition.setLongitude(hiddenDangerRecognition.getLongitude());
recognition.setLatitude(hiddenDangerRecognition.getLatitude());
// 异步处理逆地理编码服务
handleReverseGeocodingAsync(hiddenDangerRecognition, recognition);
final String realImageInfo = AllClapConstUtil.ALL_URL_PREFIX + imageInfo;
// 处理队列中的任务 - 直接调用AI模型
return callAiModelAsyncNew(realImage11, null)
.flatMap(result -> {
try {
log.info("====>>>调用模型返回结果!" + JsonUtil.objToJsonStr(result));
} catch (Exception e) {
log.debug("====>>>调用模型返回结果!" + e.getMessage());
e.printStackTrace();
}
if (OtherUtil.isNotNull(result) && result.contains(AllClapConstUtil.REQUEST_TOO_MANY)) {
// 清理任务信息
cleanUserTaskInfo(finalUserTaskKey);
return Mono.just(ApiResult.fail(AllClapConstUtil.REQUEST_TOO_MANY, null));
}
if (result != null && result.contains(AllClapConstUtil.SUCCESS_CODE)) {
if (JsonUtil.objToJsonStr(result).contains(AllClapConstUtil.IMAGE_NOT_ALLOW) ||
JsonUtil.objToJsonStr(result).contains(AllClapConstUtil.SAFETY_FENCE_INTERCEPTION)) {//防护栏拦截:此消息为
log.info("====>>>" + AllClapConstUtil.IMAGE_NOT_ALLOW);
try {
// 删除服务器文件
if (attachmentName != null) minIOService.deleteFile(attachmentName);
} catch (Exception e) {
log.warn("删除文件失败,可能文件已不存在", e);
}
// 清理任务信息
cleanUserTaskInfo(finalUserTaskKey);
return Mono.just(ApiResult.fail(AllClapConstUtil.IMAGE_NOT_ALLOW_DESTROY, null));
}
Map<String, String> resultMap = new HashMap<>();
if(configsType == AiModelTypeEnum.JIUAN.value()){//1: 久安 2: 千问
resultMap = ParseHazardResponse.extractMessageAndCountHazards(result);//久安
}else {
resultMap = ParseHazardResponse.extractMessageAndCountHazardsNew(result);//千问
}
// Map<String, String> resultMap = ParseHazardResponse.extractMessageAndCountHazards(result);//久安
// Map<String, String> resultMap = ParseHazardResponse.extractMessageAndCountHazardsNew(result);//千问
recognition.setDescription(resultMap.get("msg"));
recognition.setSessionId(resultMap.get("sessionId"));
recognition.setModelResponse(result);
recognition.setRecognitionCount(Integer.parseInt(resultMap.get("count")));
recognition.setRecognitionResult(RecognitionResultEnum.SUCCESS.value());
recognition.setHiddenDangerType(String.valueOf(SafetyHazardTypeEnum.OTHER.value()));
if (StringUtil.isEmpty(resultMap.get("msg"))) {
log.info("====>>>识别无隐患信息!");
// 清理任务信息
cleanUserTaskInfo(finalUserTaskKey);
return Mono.just(null);
}
/**
* 增加隐患类型标签、所属空间
*/
String tags = resultMap.get("tags"); // 标签
String spaceType = resultMap.get("spaceType");//空间类型
// tags="电动车多发 大沙发";
// spaceType="室内";
try {
if(StringUtil.isNotEmpty(tags)) {
if(tags.contains(";")) tags = tags.replace(";", "");
//如果是数字,或者-1
if(tags.matches("[0-9]+") || tags.equals(String.valueOf(SafetyHazardTypeEnum.OTHER.value()))){
recognition.setHiddenDangerType(tags);
}else{
if(OtherUtil.isNotNull(SafetyHazardTypeEnum.getValue(tags)))
recognition.setHiddenDangerType(String.valueOf(SafetyHazardTypeEnum.getValue(tags)));//-1 未找到安全隐患类型
}
}
} catch (Exception e) {
log.debug("=============>>>>>>>>>>>> 类型标签格式异常!"+e.getMessage());
e.printStackTrace();
}
try {
if(StringUtil.isNotEmpty(spaceType)) recognition.setBelongingSpace(BelongingSpaceEnum.getValue(spaceType) < 0 ? -1 : BelongingSpaceEnum.getValue(spaceType));//-1 未找到所属空间类型
} catch (Exception e) {
log.debug("=============>>>>>>>>>>>> 所属空间类型格式异常!"+e.getMessage());
e.printStackTrace();
}
boolean bool = HiddenDangerRecognitionServiceImpl.this.save(recognition);
if (bool) {
log.info("====>>>保存识别记录信息成功");
/**
* 根据久安返回的标签、空间类型,查找匹配小常识,形成推荐 (这里暂时不需要了)
*/
// Integer hiddenDangerType = SafetyHazardTypeEnum.getValue(tags);
// recognition.setSuggestSafetyTipsVOList(safetyTipsService.geSafetyTipsByTypeAndTagstList(String.valueOf(hiddenDangerType),recognition.getBelongingSpace()+""));
// 检查地理位置信息是否完整,如果不完整则异步补充
if (StringUtil.isEmpty(recognition.getProvinceName()) ||
StringUtil.isEmpty(recognition.getCityName()) ) {
log.info("====>>>地理位置信息不完整,启动异步补充,ID: {}", recognition.getId());
handleReverseGeocodingAsyncNew(hiddenDangerRecognition, recognition);
}
} else {
log.info("====>>>保存识别记录信息失败");
}
// 异步处理积分
// handlePointProcessingAsync(userId, name, recognition);
// 同步处理积分(在保存识别记录之后)
boolean isPointProcessed = handlePointProcessingSync(userId, name, recognition);
if(isPointProcessed) {
recognition.setIdentifying(true);
log.info("用户 {} 积分同步处理成功", userId);
}
// 清理任务信息
cleanUserTaskInfo(finalUserTaskKey);
recognition.setImageInfo(realImageInfo);
return Mono.just(recognition);
} else {
log.warn("====>>>>注意:已识别结果为空,不保存识别信息!");
// 清理任务信息
redisTemplate.delete(finalUserTaskKey);
return Mono.empty();
}
})
.onErrorResume(e -> {
log.error("调用返回异常", e);
cleanUserTaskInfo(finalUserTaskKey);
return Mono.just(null);
})
.block(); // 同步返回给 controller
} catch (Exception e) {
log.error("====>>>隐患识别异常!" + e.getMessage());
throw new ApiException(info != null ? info : e.getMessage());
}
}
/**
* 验证用户排队任务的有效性并从队列中移除
* @param user 当前用户
* @return 任务ID
*/
private String validateAndRemoveUserTaskFromQueue(UserDto user) {
// 获取用户任务ID
String userTaskKey = AllClapConstUtil.USER_TASK_KEY_PREFIX + user.getUserId();
// 在获取用户任务ID后,添加以下检查逻辑
String taskId = (String) redisTemplate.opsForValue().get(userTaskKey);
// 检查用户是否有有效的排队任务
if (taskId == null) {
throw new ApiException("您没有有效的排队任务,请先获取排队位置!");
}
// 验证任务是否在队列中
List<Object> queueTasks = redisTemplate.opsForList().range(AllClapConstUtil.TASK_QUEUE_KEY, 0, -1);
boolean taskInQueue = queueTasks != null && queueTasks.contains(taskId);
if (!taskInQueue) {
// 任务不在队列中,清理记录并提示用户重新排队
redisTemplate.delete(userTaskKey);
throw new ApiException("您的排队任务已失效,请重新获取排队位置!");
}
// 检查用户是否在可处理范围内
int position = -1;
for (int i = 0; i < queueTasks.size(); i++) {
if (taskId.equals(queueTasks.get(i))) {
position = i;
break;
}
}
// 根据queueCapacity判断用户是否可以提交
if (position >= queueCapacity) {
throw new ApiException("您当前排队位置为第" + (position + 1) + "位,请等待轮到您再提交!");
}
// 使用分布式锁防止重复提交
RLock userLock = redissonClient != null ? redissonClient.getLock(AllClapConstUtil.USER_TASK_LOCK_KEY_PREFIX + user.getUserId()) : null;
if (userLock != null) {
try {
// 尝试获取锁,最多等待3秒,持有锁5秒
if (!userLock.tryLock(3, 5, TimeUnit.SECONDS)) {
log.warn("获取用户任务锁超时,用户ID: {}", user.getUserId());
throw new ApiException("系统繁忙,请稍后再试!");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取用户任务锁被中断,用户ID: {}", user.getUserId(), e);
throw new ApiException("系统繁忙,请稍后再试!");
}
}
try {
// 改进的双重检查:检查用户是否仍在可处理范围内,而不仅仅是队列首位
queueTasks = redisTemplate.opsForList().range(AllClapConstUtil.TASK_QUEUE_KEY, 0, queueCapacity - 1);
boolean stillInProcessableRange = false;
if (queueTasks != null) {
for (Object task : queueTasks) {
if (taskId.equals(task)) {
stillInProcessableRange = true;
break;
}
}
}
if (!stillInProcessableRange) {
throw new ApiException("您的排队位置已变化,请刷新后重试!");
}
// 从队列中移除任务(表示开始处理)
Long removedCount = redisTemplate.opsForList().remove(AllClapConstUtil.TASK_QUEUE_KEY, 1, taskId);
if (removedCount == null || removedCount == 0) {
// 如果移除失败(返回0),说明任务不在队列中(可能已被其他线程处理或数据不一致)
// 此时应清理 Key 并阻止后续处理,防止产生孤儿任务
log.warn("===>>>>>任务移除失败,可能已被处理,清理用户任务Key: {}", queueTasks);
redisTemplate.delete(userTaskKey); // 清理 Key
throw new ApiException("任务已失效!请稍后再试");
}
// 在任务处理成功后,减少正在处理的用户数
// Integer processingCount = (Integer) redisTemplate.opsForValue().get(AllClapConstUtil.USER_POINT_COUNT);
// if (processingCount != null) {
// processingCount = processingCount - 1;
// if (processingCount <= 0) processingCount = 0;
// redisTemplate.opsForValue().set(AllClapConstUtil.USER_POINT_COUNT, processingCount);
// }
Long currentCount = redisTemplate.opsForValue().decrement(AllClapConstUtil.USER_POINT_COUNT);
if (currentCount != null && currentCount < 0) {
redisTemplate.opsForValue().set(AllClapConstUtil.USER_POINT_COUNT, 0);
}
return taskId;
} finally {
// 释放锁
if (userLock != null) {
try {
userLock.unlock();
} catch (Exception e) {
log.warn("释放用户任务锁异常,用户ID: {}", user.getUserId(), e);
}
}
}
}
private void cleanUserTaskInfo(String finalUserTaskKey) {
try {
// 1. 尝试获取 taskId (为了兜底清理队列)
String taskId = (String) redisTemplate.opsForValue().get(finalUserTaskKey);
// 2. 如果 taskId 存在,尝试从全局队列中移除 (防止并发或异常导致队列未清理)
if (StringUtil.isNotEmpty(taskId)) {
redisTemplate.opsForList().remove(AllClapConstUtil.TASK_QUEUE_KEY, 1, taskId);
}
// 3. 清理用户任务标记 Key
redisTemplate.delete(finalUserTaskKey);
log.info("任务信息已清理,任务键: {}", finalUserTaskKey);
} catch (Exception e) {
log.warn("清理任务信息失败,任务键: {}", finalUserTaskKey, e);
}
}