| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- 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<UnifiedUserCenterResponseVO>() {
- }
- );
- // 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);
- }
- }
- }
- }
- }
|