package com.tcm.training.admin.app.home.service; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.symmetric.SymmetricCrypto; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.tcm.training.admin.agent.entity.ProvincialAgent; import com.tcm.training.admin.agent.mapper.ProvincialAgentMapper; import com.tcm.training.admin.app.auth.service.AppAuthSessionService; import com.tcm.training.admin.app.home.vo.AppVzanLiveEntryVO; import com.tcm.training.admin.app.user.service.AppUserCenterService; import com.tcm.training.admin.app.user.vo.AppUserCenterVO; import com.tcm.training.admin.live.entity.Clinic; import com.tcm.training.admin.live.mapper.ClinicMapper; import com.tcm.training.admin.live.service.LiveRoomSettingService; import com.tcm.training.admin.live.vo.LiveRoomSettingVO; import com.tcm.training.admin.member.entity.AppUser; import com.tcm.training.admin.member.mapper.UserMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.nio.charset.StandardCharsets; @Slf4j @Service public class AppVzanLiveEntryService { private static final String VZAN_AUTH_SECRET = "2b4fefe91d8cba50"; private static final String VZAN_WEB_PAY_UA = "webchatPay/wx074c1f0cf5a0ecf4 InAppPay"; private static final int USER_STATUS_ENABLED = 1; private final LiveRoomSettingService liveRoomSettingService; private final UserMapper userMapper; private final ClinicMapper clinicMapper; private final ProvincialAgentMapper provincialAgentMapper; private final AppAuthSessionService appAuthSessionService; public AppVzanLiveEntryService(LiveRoomSettingService liveRoomSettingService, UserMapper userMapper, ClinicMapper clinicMapper, ProvincialAgentMapper provincialAgentMapper, AppAuthSessionService appAuthSessionService) { this.liveRoomSettingService = liveRoomSettingService; this.userMapper = userMapper; this.clinicMapper = clinicMapper; this.provincialAgentMapper = provincialAgentMapper; this.appAuthSessionService = appAuthSessionService; } public AppVzanLiveEntryVO createEntry(Long userId) { Long sessionUserId = null; try { sessionUserId = appAuthSessionService.getLoginUserId(); } catch (RuntimeException ex) { log.warn("微赞直播入口会话userId获取失败,请检查APP Token格式,前端userId={}", userId, ex); } log.info("微赞直播入口userId对比,请求体userId={},会话userId={},是否一致={}", userId, sessionUserId, java.util.Objects.equals(userId, sessionUserId)); LiveRoomSettingVO setting = liveRoomSettingService.current(); String liveRoomUrl = setting == null ? null : StrUtil.trim(setting.getLiveRoomUrl()); validateLiveRoomUrl(liveRoomUrl); AppUser user = requireEnabledUser(userId); String followPhone = resolveFollowPhone(user.getClinicId()); String avatar = resolveAvatar(user); JSONObject payload = new JSONObject(); payload.set("tuid", user.getId()); payload.set("followPhone", followPhone); payload.set("third_ts", System.currentTimeMillis() + 5000); payload.set("avatar", StrUtil.trim(avatar)); log.info("微赞入口参数已生成,{}", JSONUtil.toJsonStr(payload)); SymmetricCrypto sm4 = SmUtil.sm4(VZAN_AUTH_SECRET.getBytes(StandardCharsets.UTF_8)); String encryptedData = sm4.encryptBase64(payload.toString(), StandardCharsets.UTF_8); String entryUrl = UriComponentsBuilder.fromHttpUrl(liveRoomUrl) .replaceQueryParam("data", encryptedData) .build() .encode(StandardCharsets.UTF_8) .toUriString(); log.info("微赞直播生成URL: {}", entryUrl); return new AppVzanLiveEntryVO(entryUrl, VZAN_WEB_PAY_UA); } private String resolveFollowPhone(Long clinicId) { if (clinicId == null) { throw new IllegalArgumentException("当前用户未绑定诊所"); } Clinic clinic = clinicMapper.selectById(clinicId); if (clinic == null) { throw new IllegalArgumentException("当前诊所未绑定代理"); } String contactInfo = StrUtil.trim(clinic.getContactPhone()); if (StrUtil.isBlank(contactInfo)) { throw new IllegalArgumentException("所属代理未配置联系电话"); } return contactInfo; } private void validateLiveRoomUrl(String liveRoomUrl) { if (StrUtil.isBlank(liveRoomUrl)) { throw new IllegalArgumentException("直播间地址暂未配置"); } try { URI uri = URI.create(liveRoomUrl); if (!"https".equalsIgnoreCase(uri.getScheme()) || StrUtil.isBlank(uri.getHost())) { throw new IllegalArgumentException("直播间地址必须为有效的HTTPS地址"); } } catch (IllegalArgumentException ex) { throw new IllegalArgumentException("直播间地址必须为有效的HTTPS地址", ex); } } /** * 校验用户存在且可用。 */ private AppUser requireEnabledUser(Long userId) { AppUser user = userMapper.selectById(userId); if (user == null || user.getDeleted() == null || user.getDeleted().intValue() != 0) { throw new IllegalArgumentException("用户不存在"); } if (user.getStatus() == null || user.getStatus().intValue() != USER_STATUS_ENABLED) { throw new IllegalArgumentException("用户已禁用"); } return user; } /** * 优先使用本地头像,没有时回退微信头像。 */ private String resolveAvatar(AppUser user) { String avatar = user == null ? null : StrUtil.trim(user.getAvatar()); if (StrUtil.isNotBlank(avatar)) { return avatar; } String wechatAvatar = user == null ? null : StrUtil.trim(user.getWechatAvatar()); return StrUtil.isNotBlank(wechatAvatar) ? wechatAvatar : ""; } }