package com.ylx.usercenter.service.impl; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.ylx.common.core.domain.R; import com.ylx.common.exception.ServiceException; import com.ylx.lottery.service.LotteryCountService; import com.ylx.massage.domain.TWxUser; import com.ylx.massage.service.TWxUserService; import com.ylx.usercenter.domain.dto.UnifiedUserCenterDTO; import com.ylx.usercenter.domain.vo.UnifiedUserCenterResponseVO; import com.ylx.usercenter.domain.vo.UnifiedUserClientListVO; import com.ylx.usercenter.service.UnifiedUserCenterService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @Slf4j @Service public class UnifiedUserCenterServiceImpl implements UnifiedUserCenterService { @Value("${remote.user-center.base-url}") private String userCenterBaseUrl; @Value("${remote.local-live.client-id}") private String clientId; // 定义接口路径常量 private static final String QUERY_BIND_PATH = "/userApp/queryBind"; private static final String BIND_PATH = "/userApp/bind"; private static final String UNBIND_PATH = "/userApp/unbind"; @Resource private TWxUserService wxUserService; @Resource private LotteryCountService lotteryCountService; @Override public R queryBind(UnifiedUserCenterDTO dto) { validateDto(dto); // dto.setClientId(clientId); // 只需要关注业务路径和日志描述即可 return executePost(QUERY_BIND_PATH, dto, "查询用户绑定信息"); } @Override public R bind(UnifiedUserCenterDTO dto) { try { // 1.检验参数信息 validateDto(dto); // 2.获取用户信息 TWxUser wxUser = this.wxUserService.getById(dto.getSourceUserId()); if (ObjUtil.isEmpty(wxUser)) { log.error("用户信息不存在, userId: {}", dto.getSourceUserId()); throw new ServiceException("用户信息不存在"); } // 3.添加用户绑定信息 R remoteResult = executePost(BIND_PATH, dto, "添加用户绑定信息"); // 4. 判断绑定成功 if (!remoteResult.getData().equals(Boolean.TRUE)) { log.error("远程绑定失败, userId: {}, msg: {}", dto.getSourceUserId(), remoteResult.getMsg()); return R.fail("用户绑定失败:" + remoteResult.getMsg()); } // 5. 更新本地数据库(独立事务) updateUserBindStatus(dto); // 6. 同步抽奖次数(异步处理) syncLotteryCount(dto); return R.ok(remoteResult.getData(), "用户绑定成功"); } catch (ServiceException e) { log.error("用户绑定业务异常, userId:{}", dto.getSourceUserId(), e); throw e; } catch (Exception e) { log.error("用户绑定系统异常, userId:{}", dto.getSourceUserId(), e); throw new ServiceException("系统异常,请稍后重试"); } } @Override public R unbind(UnifiedUserCenterDTO dto) { validateDto(dto); return executePost(UNBIND_PATH, dto, "解除用户绑定信息"); } /** * 通用 HTTP POST 请求执行模板 */ private R executePost(String path, UnifiedUserCenterDTO dto, String logDesc) { try { // 1. 构建 URL String url = userCenterBaseUrl + path; // 2. 转换参数 String jsonParam = JSON.toJSONString(dto); // 3. 发送请求 String result = HttpUtil.post(url, jsonParam); // 4. 校验结果 if (StrUtil.isEmpty(result)) { throw new RuntimeException(logDesc + "失败:接口返回空结果"); } // 5. 解析结果 UnifiedUserCenterResponseVO response = JSON.parseObject( result, new TypeReference() { } ); // 4. 处理业务状态 if (!response.isSuccess()) { log.warn("{}失败: data={},code={}, msg={}", logDesc, response.getData(), response.getCode(), response.getMessage()); return R.fail(response.getData(), response.getMessage()); } // 5. 处理 data 字段 Object dataObj = response.getData(); // 情况 A: data 是布尔值 false (虽然 code=0,但没数据) if (dataObj instanceof Boolean) { log.info("接口返回 data 为布尔值: {}", dataObj); return R.ok(response.getData()); } // 情况 B: data 是包含列表的对象 { "userClients": [...] } if (dataObj instanceof UnifiedUserClientListVO) { UnifiedUserClientListVO wrapper = (UnifiedUserClientListVO) dataObj; return R.ok(wrapper.getUserClients(), response.getMessage()); } return R.ok(); } catch (Exception e) { log.error("{}发生异常", logDesc, e); throw new ServiceException(logDesc + "异常" + e); } } /** * 参数校验提取 */ private void validateDto(UnifiedUserCenterDTO dto) { if (StrUtil.isEmpty(dto.getSourceUserId())) { throw new IllegalArgumentException("源用户ID不能为空"); } if (StrUtil.isEmpty(dto.getTargetUserId())) { throw new IllegalArgumentException("目标用户ID不能为空"); } if (StrUtil.isEmpty(dto.getClientId())) { throw new IllegalArgumentException("客户端ID不能为空"); } } // 独立事务方法 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void updateUserBindStatus(UnifiedUserCenterDTO dto) { try { TWxUser wxUser = this.wxUserService.getById(dto.getSourceUserId()); wxUser.setIsBind(1); boolean wxUserBool = this.wxUserService.updateById(wxUser); if (!wxUserBool) { log.error("更新用户绑定状态失败, userId: {}", dto.getSourceUserId()); throw new ServiceException("更新用户绑定状态失败"); } log.info("更新用户绑定一账通信息成功, userId:{}", dto.getSourceUserId()); } catch (Exception e) { log.error("更新用户绑定状态异常, userId: {}", dto.getSourceUserId(), e); // 如果本地更新失败,异步调用远程解绑接口进行补偿 asyncCompensateUnbind(dto); throw e; } } // 异步同步抽奖次数 @Async public void syncLotteryCount(UnifiedUserCenterDTO dto) { try { boolean lotteryCountBool = this.lotteryCountService.batchAdd(dto); if (lotteryCountBool) { log.info("同步用户抽奖次数到本地生活服务成功, userId:{}", dto.getTargetUserId()); } else { log.warn("同步用户抽奖次数到本地生活服务失败, userId:{}", dto.getTargetUserId()); // 同步失败,记录告警日志 log.error("【告警】同步用户抽奖次数到本地生活服务失败,需要人工处理, userId:{}", dto.getTargetUserId()); } } catch (Exception e) { log.error("同步用户抽奖次数到本地生活服务异常, userId:{}", dto.getTargetUserId(), e); // 异常情况,记录告警日志 log.error("【告警】同步用户抽奖次数到本地生活服务异常,需要人工处理, userId:{}", dto.getTargetUserId(), e); } } /** * 异步补偿:解除用户绑定 * 带重试机制,最多重试3次 */ @Async public void asyncCompensateUnbind(UnifiedUserCenterDTO dto) { final int maxRetries = 3; int retryCount = 0; boolean success = false; while (retryCount < maxRetries && !success) { retryCount++; try { log.info("异步补偿:尝试解除用户绑定, userId: {}, 重试次数: {}", dto.getSourceUserId(), retryCount); executePost(UNBIND_PATH, dto, "补偿:解除用户绑定信息"); log.info("异步补偿:成功解除用户绑定, userId: {}", dto.getSourceUserId()); success = true; } catch (Exception e) { log.error("异步补偿:解除用户绑定失败, userId: {}, 重试次数: {}", dto.getSourceUserId(), retryCount, e); if (retryCount < maxRetries) { try { // 等待一段时间后重试 Thread.sleep(1000 * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); log.error("异步补偿:线程被中断, userId: {}", dto.getSourceUserId(), ie); break; } } else { // 重试次数用完,记录告警日志 log.error("【告警】异步补偿:解除用户绑定失败,已达最大重试次数,需要人工处理, userId: {}", dto.getSourceUserId(), e); } } } } }