| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- package com.nuojing.media.auth.service.impl;
- import com.fasterxml.jackson.databind.JsonNode;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.nuojing.admin.common.BusinessException;
- import com.nuojing.admin.context.CurrentUser;
- import com.nuojing.admin.context.CurrentUserContext;
- import com.nuojing.media.auth.client.BytedanceMediaClient;
- import com.nuojing.media.auth.client.KuaishouMediaClient;
- import com.nuojing.media.auth.client.TencentMediaClient;
- import com.nuojing.media.auth.domain.dto.AuthUrlRequest;
- import com.nuojing.media.auth.domain.entity.MediaAdvertiserAccount;
- import com.nuojing.media.auth.domain.entity.MediaAuthAccount;
- import com.nuojing.media.auth.domain.entity.MediaAuthConfig;
- import com.nuojing.media.auth.domain.vo.AuthUrlVO;
- import com.nuojing.media.auth.domain.vo.MediaAuthConfigVO;
- import com.nuojing.media.auth.mapper.MediaAuthMapper;
- import com.nuojing.media.auth.service.MediaAuthService;
- import com.nuojing.media.callback.domain.vo.CallbackResultVO;
- import com.nuojing.media.common.enums.MediaType;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.util.StringUtils;
- import org.springframework.web.util.UriComponentsBuilder;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- @Slf4j
- @Service
- @RequiredArgsConstructor
- public class MediaAuthServiceImpl implements MediaAuthService {
- private final MediaAuthMapper mediaAuthMapper;
- private final BytedanceMediaClient bytedanceMediaClient;
- private final KuaishouMediaClient kuaishouMediaClient;
- private final TencentMediaClient tencentMediaClient;
- private final ObjectMapper objectMapper;
- @Override
- public List<MediaAuthConfigVO> configs() {
- return mediaAuthMapper.selectActiveConfigs(null).stream().map(config -> {
- MediaType media = MediaType.ofMediaId(config.getMediaType());
- MediaAuthConfigVO vo = new MediaAuthConfigVO();
- vo.setMediaId(media.getMediaId());
- vo.setMediaCode(media.getCode());
- vo.setMediaName(media.getName());
- vo.setAppId(config.getAppId());
- vo.setRedirectUri(config.getRedirectUri());
- vo.setCallbackReturnUrl(config.getCallbackReturnUrl());
- vo.setConfigured(StringUtils.hasText(config.getAuthUrl())
- && (StringUtils.hasText(config.getAppSecret()) || StringUtils.hasText(config.getClientSecret())));
- return vo;
- }).toList();
- }
- @Override
- public AuthUrlVO getAuthUrl(AuthUrlRequest request) {
- if (request.getMediaId() == null) {
- throw new BusinessException(400, "请输入媒体类型");
- }
- MediaType media = MediaType.ofMediaId(request.getMediaId());
- MediaAuthConfig config = findConfig(media);
- if (config == null || !StringUtils.hasText(config.getAuthUrl())) {
- throw new BusinessException(400, media.getName() + "授权配置未完成");
- }
- Long userId = currentUserId();
- String state = buildState(userId, config.getAppId());
- AuthUrlVO vo = new AuthUrlVO();
- vo.setMediaId(media.getMediaId());
- vo.setMediaCode(media.getCode());
- vo.setMediaName(media.getName());
- vo.setAppId(config.getAppId());
- vo.setUserId(userId);
- vo.setState(state);
- vo.setAuthUrl(appendState(config.getAuthUrl(), state));
- return vo;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public CallbackResultVO handleCallback(Integer mediaId, Map<String, String> params) {
- MediaType media = MediaType.ofMediaId(mediaId);
- CallbackResultVO result = new CallbackResultVO();
- result.setMediaType(media.getMediaId());
- result.setBindStatus(2);
- result.setState(params.get("state"));
- result.setTime(System.currentTimeMillis());
- result.setCallbackReturnUrl(callbackReturnUrl(media));
- try {
- validateCallbackParams(media, params);
- Long adminAdvertiserId = handleCallback(media, params);
- result.setCode(200);
- result.setAdminAdvertiserId(adminAdvertiserId);
- result.setMessage(media.getName() + "授权回调已接收");
- log.info("{} authorization callback received, state={}, params={}", media.getName(), params.get("state"), params);
- } catch (Exception ex) {
- result.setCode(500);
- result.setMessage(ex.getMessage());
- log.error("{} authorization callback failed, params={}", media.getName(), params, ex);
- }
- return result;
- }
- @Override
- public String buildReturnUrl(CallbackResultVO result) {
- String callbackReturnUrl = result.getCallbackReturnUrl();
- if (!StringUtils.hasText(callbackReturnUrl)) {
- callbackReturnUrl = callbackReturnUrl(MediaType.ofMediaId(result.getMediaType()));
- }
- UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(callbackReturnUrl)
- .queryParam("code", result.getCode())
- .queryParam("mediaType", result.getMediaType())
- .queryParam("bindStatus", result.getBindStatus())
- .queryParam("time", result.getTime());
- if (result.getAdminAdvertiserId() != null) {
- builder.queryParam("adminAdvertiserId", result.getAdminAdvertiserId());
- }
- if (StringUtils.hasText(result.getState())) {
- builder.queryParam("state", result.getState());
- }
- if (StringUtils.hasText(result.getMessage())) {
- builder.queryParam(result.getCode() == 200 ? "message" : "msg", result.getMessage());
- }
- return builder.build().encode().toUriString();
- }
- private MediaAuthConfig findConfig(MediaType media) {
- return mediaAuthMapper.selectActiveConfigs(media.getMediaId()).stream().findFirst().orElse(null);
- }
- private String callbackReturnUrl(MediaType media) {
- return mediaAuthMapper.selectActiveConfigs(media.getMediaId()).stream()
- .map(MediaAuthConfig::getCallbackReturnUrl)
- .filter(StringUtils::hasText)
- .findFirst()
- .orElseThrow(() -> new BusinessException(400, media.getName() + "前端回跳地址未配置"));
- }
- private Long currentUserId() {
- CurrentUser currentUser = CurrentUserContext.get();
- return currentUser == null ? null : currentUser.getUserId();
- }
- private String buildState(Long userId, Long appId) {
- return safe(userId) + "_" + safe(appId);
- }
- private String safe(Long value) {
- return value == null ? "" : String.valueOf(value);
- }
- private String appendState(String authUrl, String state) {
- UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(authUrl);
- builder.replaceQueryParam("state", state);
- return builder.build().encode().toUriString();
- }
- private void validateCallbackParams(MediaType media, Map<String, String> params) {
- if (!StringUtils.hasText(params.get("state"))) {
- throw new BusinessException(400, "state不能为空");
- }
- if (media == MediaType.BYTEDANCE && !StringUtils.hasText(params.get("app_id"))) {
- throw new BusinessException(400, "app_id不能为空");
- }
- if (media == MediaType.BYTEDANCE && !StringUtils.hasText(params.get("auth_code"))) {
- throw new BusinessException(400, "auth_code不能为空");
- }
- if (media == MediaType.KUAISHOU && !StringUtils.hasText(params.get("auth_code"))) {
- throw new BusinessException(400, "auth_code不能为空");
- }
- if (media == MediaType.TENCENT && !StringUtils.hasText(params.get("authorization_code"))) {
- throw new BusinessException(400, "authorization_code不能为空");
- }
- }
- private Long handleBytedanceCallback(Map<String, String> params) {
- Long appId = parseLong(params.get("app_id"), "app_id");
- MediaAuthConfig config = mediaAuthMapper.selectActiveConfigByAppId(MediaType.BYTEDANCE.getMediaId(), appId);
- if (config == null) {
- throw new BusinessException(400, "通过此appId未获取到相关配置信息,请联系技术");
- }
- JsonNode tokenResult = bytedanceMediaClient.exchangeToken(config, params.get("auth_code"));
- JsonNode tokenData = bytedanceMediaClient.requireSuccess(tokenResult, "获取字节token");
- String accessToken = tokenData.path("access_token").asText();
- String refreshToken = tokenData.path("refresh_token").asText();
- if (!StringUtils.hasText(accessToken)) {
- throw new BusinessException(500, "获取字节token失败:access_token为空");
- }
- JsonNode advertiserResult = bytedanceMediaClient.advertiserList(accessToken);
- JsonNode advertiserData = bytedanceMediaClient.requireSuccess(advertiserResult, "获取字节授权账户");
- JsonNode advertisers = advertiserData.path("list");
- if (!advertisers.isArray()) {
- throw new BusinessException(500, "获取字节授权账户list信息为空");
- }
- Long userId = parseStateUserId(params.get("state"));
- JsonNode adminAdvertiser = null;
- List<JsonNode> customerAccounts = new ArrayList<>();
- for (JsonNode advertiser : advertisers) {
- String accountRole = advertiser.path("account_role").asText();
- if ("CUSTOMER_ADMIN".equals(accountRole)) {
- adminAdvertiser = advertiser;
- }
- if ("CUSTOMER_ADMIN".equals(accountRole) || "CUSTOMER_OPERATOR".equals(accountRole)) {
- customerAccounts.add(advertiser);
- }
- }
- if (adminAdvertiser == null) {
- throw new BusinessException(500, "未获取到字节管家账户");
- }
- Long adminAdvertiserId = adminAdvertiser.path("advertiser_id").asLong();
- MediaAuthAccount authAccount = toAuthAccount(config, adminAdvertiser, accessToken, refreshToken, userId);
- applyUserInfo(authAccount, accessToken);
- mediaAuthMapper.replaceAuthAccount(authAccount);
- // /oauth2/advertiser/get 返回的是工作台(纵横组织)账号,需进一步展开组织下的真实投放账户
- syncBytedanceDeliveryAccounts(accessToken, customerAccounts, adminAdvertiserId);
- return adminAdvertiserId;
- }
- private void syncBytedanceDeliveryAccounts(String accessToken, List<JsonNode> customerAccounts, Long adminAdvertiserId) {
- for (JsonNode org : customerAccounts) {
- long ccAccountId = org.path("advertiser_id").asLong();
- try {
- int page = 1;
- int totalPage = 1;
- do {
- JsonNode data = bytedanceMediaClient.requireSuccess(
- bytedanceMediaClient.ccAdvertiserList(accessToken, ccAccountId, page),
- "获取字节纵横组织投放账户");
- JsonNode list = data.path("list");
- if (list.isArray()) {
- for (JsonNode item : list) {
- long advertiserId = item.path("advertiser_id").asLong(0);
- if (advertiserId > 0) {
- mediaAuthMapper.replaceAdvertiserAccount(toDeliveryAccount(MediaType.BYTEDANCE, adminAdvertiserId, item));
- }
- }
- }
- totalPage = Math.max(data.path("page_info").path("total_page").asInt(page), page);
- page++;
- } while (page <= totalPage);
- } catch (Exception ex) {
- log.warn("获取字节纵横组织投放账户失败,跳过该组织:ccAccountId={}", ccAccountId, ex);
- }
- }
- }
- private MediaAdvertiserAccount toDeliveryAccount(MediaType media, Long adminAdvertiserId, JsonNode item) {
- MediaAdvertiserAccount account = new MediaAdvertiserAccount();
- account.setMediaType(media.getMediaId());
- account.setAdminAdvertiserId(adminAdvertiserId);
- account.setAdvertiserId(item.path("advertiser_id").asLong());
- account.setAdvertiserName(item.path("advertiser_name").asText(null));
- account.setAccountRole(item.path("advertiser_type").asText(null));
- account.setIsValid(1);
- account.setRawResponse(toJson(item));
- return account;
- }
- private Long handleCallback(MediaType media, Map<String, String> params) {
- if (media == MediaType.BYTEDANCE) {
- return handleBytedanceCallback(params);
- }
- if (media == MediaType.KUAISHOU) {
- return handleKuaishouCallback(params);
- }
- if (media == MediaType.TENCENT) {
- return handleTencentCallback(params);
- }
- throw new BusinessException(400, media.getName() + "回调暂未接入");
- }
- private Long handleKuaishouCallback(Map<String, String> params) {
- MediaAuthConfig config = requireConfig(MediaType.KUAISHOU);
- JsonNode tokenResult = kuaishouMediaClient.exchangeToken(config, params.get("auth_code"));
- JsonNode tokenData = kuaishouMediaClient.requireSuccess(tokenResult, "获取快手token");
- String accessToken = tokenData.path("access_token").asText();
- String refreshToken = tokenData.path("refresh_token").asText(null);
- if (!StringUtils.hasText(accessToken)) {
- throw new BusinessException(500, "获取快手token失败:access_token为空");
- }
- JsonNode advertiserIds = tokenData.path("advertiser_ids");
- if (!advertiserIds.isArray() || advertiserIds.isEmpty()) {
- throw new BusinessException(500, "未获取到快手授权广告账户:advertiser_ids为空");
- }
- Long userId = parseStateUserId(params.get("state"));
- // 磁力引擎token响应未单独返回管家账户ID,优先取响应内的账户字段,否则以首个广告账户作为管家标识
- Long adminAdvertiserId = resolveKuaishouAdminId(tokenData, advertiserIds);
- MediaAuthAccount authAccount = baseAuthAccount(config, MediaType.KUAISHOU, userId, adminAdvertiserId, accessToken, refreshToken);
- authAccount.setRawResponse(toJson(tokenData));
- mediaAuthMapper.replaceAuthAccount(authAccount);
- for (JsonNode advertiserId : advertiserIds) {
- mediaAuthMapper.replaceAdvertiserAccount(toKuaishouAdvertiserAccount(adminAdvertiserId, advertiserId.asLong()));
- }
- return adminAdvertiserId;
- }
- private Long handleTencentCallback(Map<String, String> params) {
- MediaAuthConfig config = requireConfig(MediaType.TENCENT);
- JsonNode tokenResult = tencentMediaClient.exchangeToken(config, params.get("authorization_code"));
- JsonNode tokenData = tencentMediaClient.requireSuccess(tokenResult, "获取腾讯token");
- String accessToken = tokenData.path("access_token").asText();
- String refreshToken = tokenData.path("refresh_token").asText(null);
- if (!StringUtils.hasText(accessToken)) {
- throw new BusinessException(500, "获取腾讯token失败:access_token为空");
- }
- JsonNode authorizer = tokenData.path("authorizer_info");
- long accountId = authorizer.path("account_id").asLong(0);
- if (accountId <= 0) {
- throw new BusinessException(500, "未获取到腾讯授权账号ID");
- }
- Long adminAdvertiserId = accountId;
- Long userId = parseStateUserId(params.get("state"));
- MediaAuthAccount authAccount = baseAuthAccount(config, MediaType.TENCENT, userId, adminAdvertiserId, accessToken, refreshToken);
- authAccount.setAccountRole(authorizer.path("account_role_type").asText(null));
- authAccount.setRawResponse(toJson(authorizer));
- mediaAuthMapper.replaceAuthAccount(authAccount);
- syncTencentAdvertisers(accessToken, adminAdvertiserId);
- return adminAdvertiserId;
- }
- private void syncTencentAdvertisers(String accessToken, Long adminAdvertiserId) {
- int page = 1;
- int totalPage = 1;
- do {
- JsonNode relations;
- try {
- relations = tencentMediaClient.requireSuccess(
- tencentMediaClient.businessManagerRelations(accessToken, page), "获取腾讯商务管家广告主");
- } catch (Exception ex) {
- log.warn("获取腾讯商务管家广告主列表失败,仅保留管家授权信息:adminAdvertiserId={}", adminAdvertiserId, ex);
- return;
- }
- JsonNode list = relations.path("list");
- if (list.isArray()) {
- for (JsonNode item : list) {
- long advertiserId = item.path("account_id").asLong(0);
- if (advertiserId > 0) {
- mediaAuthMapper.replaceAdvertiserAccount(toTencentAdvertiserAccount(adminAdvertiserId, item));
- }
- }
- }
- totalPage = Math.max(relations.path("page_info").path("total_page").asInt(page), page);
- page++;
- } while (page <= totalPage);
- }
- private MediaAuthConfig requireConfig(MediaType media) {
- MediaAuthConfig config = findConfig(media);
- if (config == null) {
- throw new BusinessException(400, media.getName() + "授权配置不存在");
- }
- return config;
- }
- private Long resolveKuaishouAdminId(JsonNode tokenData, JsonNode advertiserIds) {
- for (String field : new String[]{"user_id", "account_id", "corporation_id"}) {
- long value = tokenData.path(field).asLong(0);
- if (value > 0) {
- return value;
- }
- }
- return advertiserIds.get(0).asLong();
- }
- private MediaAuthAccount baseAuthAccount(MediaAuthConfig config, MediaType media, Long userId, Long adminAdvertiserId, String accessToken, String refreshToken) {
- MediaAuthAccount account = new MediaAuthAccount();
- account.setMediaType(media.getMediaId());
- account.setAppId(config.getAppId());
- account.setUserId(userId);
- account.setAdminAdvertiserId(adminAdvertiserId);
- account.setIsValid(1);
- account.setAccessToken(accessToken);
- account.setRefreshToken(refreshToken);
- return account;
- }
- private MediaAdvertiserAccount toKuaishouAdvertiserAccount(Long adminAdvertiserId, Long advertiserId) {
- MediaAdvertiserAccount account = new MediaAdvertiserAccount();
- account.setMediaType(MediaType.KUAISHOU.getMediaId());
- account.setAdminAdvertiserId(adminAdvertiserId);
- account.setAdvertiserId(advertiserId);
- account.setIsValid(1);
- account.setRawResponse("{\"advertiser_id\":" + advertiserId + "}");
- return account;
- }
- private MediaAdvertiserAccount toTencentAdvertiserAccount(Long adminAdvertiserId, JsonNode item) {
- MediaAdvertiserAccount account = new MediaAdvertiserAccount();
- account.setMediaType(MediaType.TENCENT.getMediaId());
- account.setAdminAdvertiserId(adminAdvertiserId);
- account.setAdvertiserId(item.path("account_id").asLong());
- account.setAdvertiserName(item.path("corporation_name").asText(null));
- account.setAccountRole(item.path("account_type").asText(null));
- account.setIsValid(1);
- account.setRawResponse(toJson(item));
- return account;
- }
- private MediaAuthAccount toAuthAccount(MediaAuthConfig config, JsonNode advertiser, String accessToken, String refreshToken, Long userId) {
- MediaAuthAccount account = new MediaAuthAccount();
- account.setMediaType(MediaType.BYTEDANCE.getMediaId());
- account.setAppId(config.getAppId());
- account.setUserId(userId);
- account.setAdminAdvertiserId(advertiser.path("advertiser_id").asLong());
- account.setAdminAdvertiserName(advertiser.path("advertiser_name").asText(null));
- account.setAccountRole(advertiser.path("account_role").asText(null));
- account.setAdvertiserRole(advertiser.path("advertiser_role").isMissingNode() ? null : advertiser.path("advertiser_role").asInt());
- account.setIsValid(advertiser.path("is_valid").asBoolean(false) ? 1 : 0);
- account.setAccessToken(accessToken);
- account.setRefreshToken(refreshToken);
- account.setRawResponse(toJson(advertiser));
- return account;
- }
- private void applyUserInfo(MediaAuthAccount account, String accessToken) {
- try {
- JsonNode userInfoResult = bytedanceMediaClient.userInfo(accessToken);
- JsonNode data = bytedanceMediaClient.requireSuccess(userInfoResult, "获取字节用户信息");
- String email = data.path("email").asText(null);
- String displayName = data.path("display_name").asText(null);
- if (StringUtils.hasText(email)) {
- account.setEmail(email);
- }
- if (StringUtils.hasText(displayName)) {
- account.setAdminAdvertiserName(displayName);
- }
- } catch (Exception ex) {
- log.warn("获取字节用户信息失败,不影响授权入库:adminAdvertiserId={}", account.getAdminAdvertiserId(), ex);
- }
- }
- private Long parseStateUserId(String state) {
- if (!StringUtils.hasText(state)) {
- return null;
- }
- String[] parts = state.split("_");
- return parts.length == 0 ? null : parseNullableLong(parts[0]);
- }
- private Long parseLong(String value, String fieldName) {
- Long parsed = parseNullableLong(value);
- if (parsed == null) {
- throw new BusinessException(400, fieldName + "格式错误");
- }
- return parsed;
- }
- private Long parseNullableLong(String value) {
- if (!StringUtils.hasText(value)) {
- return null;
- }
- try {
- return Long.valueOf(value);
- } catch (NumberFormatException ex) {
- return null;
- }
- }
- private String toJson(JsonNode node) {
- try {
- return objectMapper.writeValueAsString(node);
- } catch (Exception ex) {
- return "{}";
- }
- }
- }
|