package com.tailbet.openim; import com.tailbet.common.enums.ErrorCodeEnum; import com.tailbet.common.exception.BusinessException; import com.tailbet.config.OpenIMProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; /** * OpenIM adminToken 管理器 *
* 负责 OpenIM 管理员 Token 的获取与定期刷新:服务启动时通过 OpenIM * {@code /auth/get_admin_token} 接口获取一次,之后每 23 小时自动刷新一次 * (OpenIM 管理员 Token 默认 24 小时过期,提前 1 小时刷新以规避临界失效)。 *
** 本类不依赖 {@link OpenIMApiClient},避免出现 * OpenIMApiClient → OpenIMAdminTokenManager → OpenIMApiClient 的循环依赖, * 内部直接使用专用 RestTemplate 调用 OpenIM 接口。 *
* * @author socialapp团队 * @since 1.0.0 */ @RequiredArgsConstructor @Slf4j @Component public class OpenIMAdminTokenManager { /** * 定时刷新周期:23 小时(毫秒) */ private static final long REFRESH_INTERVAL_MS = 23 * 60 * 60 * 1000L; private final RestTemplate restTemplate; /** * OpenIM 集成配置属性 */ private final OpenIMProperties openIMProperties; /** * 当前有效的 adminToken,使用原子引用保证多线程读取的可见性与线程安全 */ private final AtomicReference* 若启动时获取失败,仅记录错误日志,不阻塞服务启动; * 后续定时任务会持续尝试刷新,业务调用方在 adminToken 为空时会因鉴权失败而收到明确异常。 *
*/ @PostConstruct public void init() { try { refreshToken(); } catch (Exception e) { log.error("【OpenIM】服务启动时获取 adminToken 失败: {}", e.getMessage(), e); } } /** * 每 23 小时自动刷新 adminToken(避免 24 小时过期) */ @Scheduled(fixedDelay = REFRESH_INTERVAL_MS) public void scheduledRefresh() { try { refreshToken(); } catch (Exception e) { log.error("【OpenIM】定时刷新 adminToken 失败: {}", e.getMessage(), e); } } /** * 获取当前有效的 adminToken ** 若 token 尚未初始化(null 或空),则尝试立即刷新一次,防止 NPE 及空 token 导致的鉴权失败。 *
* * @return adminToken 字符串,刷新失败时返回空字符串 */ public String getAdminToken() { String token = tokenRef.get(); if (token == null || token.isEmpty()) { log.warn("【OpenIM】adminToken 尚未初始化,尝试立即刷新"); refreshToken(); token = tokenRef.get(); } return token != null ? token : ""; } /** * 调用 OpenIM {@code /auth/get_admin_token} 接口刷新 adminToken ** 获取 adminToken 属于鉴权前置操作,请求本身不能携带 adminToken 请求头。 * 方法加锁,避免同进程内并发刷新互相顶掉(OpenIM 同 user 新 token 会使旧 token 变为 TokenKicked)。 *
*/ public synchronized void refreshToken() { String url = openIMProperties.resolveServerApiUrl() + "/auth/get_admin_token"; Map