整理代码 抖音接口

This commit is contained in:
2025-10-28 17:40:32 +08:00
parent af71c55529
commit 4fb1d9b00c
8 changed files with 210 additions and 178 deletions

View File

@ -5,7 +5,11 @@ import com.douyin.openapi.client.Client;
import com.douyin.openapi.client.models.OauthClientTokenRequest;
import com.douyin.openapi.client.models.OauthClientTokenResponse;
import com.douyin.openapi.credential.models.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -14,31 +18,37 @@ import java.util.concurrent.atomic.AtomicReference;
* 抖音 client_token 管理器
* 负责定时获取和缓存 client_token
*/
@Slf4j
@Component
public class DouyinTokenManager {
// 应用凭证信息
private static final String CLIENT_KEY = "awomt6nnjlfc491m";
private static final String CLIENT_SECRET = "c678c411c7a68c6f97969f2dbd8ef8fc";
@Value("${coupon.douyin.client_key}")
private String CLIENT_KEY;
@Value("${coupon.douyin.client_secret}")
private String CLIENT_SECRET;
// Token 缓存
private static final AtomicReference<OauthClientTokenResponse> tokenCache =
private final AtomicReference<OauthClientTokenResponse> tokenCache =
new AtomicReference<>();
// 定时任务执行器
private static final ScheduledThreadPoolExecutor scheduler =
private final ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r, "DouyinTokenRefreshThread");
t.setDaemon(false);
return t;
});
static {
@PostConstruct
public void init() {
// 初始化时立即获取一次 token
refreshClientToken();
// 每小时更新一次 token (3600秒)
scheduler.scheduleAtFixedRate(
DouyinTokenManager::refreshClientToken,
this::refreshClientToken,
3600,
3600,
TimeUnit.SECONDS
@ -50,7 +60,7 @@ public class DouyinTokenManager {
*
* @return 当前有效的 access_token
*/
public static String getCurrentToken() {
public String getCurrentToken() {
OauthClientTokenResponse response = tokenCache.get();
if (response != null && response.getData() != null) {
return response.getData().getAccessToken();
@ -61,7 +71,7 @@ public class DouyinTokenManager {
/**
* 刷新 client_token
*/
private static void refreshClientToken() {
private void refreshClientToken() {
try {
Config config = new Config()
.setClientKey(CLIENT_KEY)
@ -80,28 +90,14 @@ public class DouyinTokenManager {
tokenCache.set(sdkResponse);
if (sdkResponse.getData() != null) {
System.out.println("抖音 client_token 更新成功,有效期至: " +
log.info("抖音 client_token 更新成功: {},有效期至: {}", sdkResponse.getData().getAccessToken(),
(System.currentTimeMillis() + sdkResponse.getData().getExpiresIn() * 1000));
}
} catch (TeaException e) {
System.err.println("获取抖音 client_token 失败 (TeaException): " + e.getMessage());
log.error("获取抖音 client_token 失败 (TeaException): " + e.getMessage());
} catch (Exception e) {
System.err.println("获取抖音 client_token 失败 (Exception): " + e.getMessage());
log.error("获取抖音 client_token 失败 (Exception): " + e.getMessage());
}
}
/**
* 关闭定时任务
*/
public static void shutdown() {
scheduler.shutdown();
// 获取当前有效的 client_token
String token = DouyinTokenManager.getCurrentToken();
// 使用 token 调用抖音 API
if (token != null) {
// 调用订单查询等接口
}
}
}

View File

@ -1,6 +1,5 @@
package com.sczx.order.controller;
import com.meituan.sdk.auth.MeituanTokenResponse;
import com.sczx.order.common.Result;
import com.sczx.order.service.DouyinService;
import com.sczx.order.service.MeiTuanService;
@ -18,9 +17,6 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/verify")
public class VerifyController {
@Autowired
private DouyinService douyinService;
@Autowired
private MeiTuanService meituanService;
@ -31,41 +27,9 @@ public class VerifyController {
log.info("接收美团授权数据 - code: {}, sign: {}, developerId: {}, businessId: {}, state: {}",
code, sign, developerId, businessId, state);
//接收code记录state门店编号
//MeituanTokenResponse response = meituanService.getAccessToken(code,state);
return Result.ok(meituanService.getAccessToken(code,state));
}
@ApiOperation(value = "接收需据接口")
@GetMapping("/prepare")
public MeituanTokenResponse prepare(@RequestParam("code") String code,@RequestParam("state") String state) throws Exception {
MeituanTokenResponse response = meituanService.getAccessToken( code,state);
return response;
}
@ApiOperation(value = "接收需据接口")
@GetMapping("/mtprepare")
public MeituanTokenResponse mtprepare(@RequestParam("code") String code) throws Exception {
MeituanTokenResponse response = meituanService.prepare( code);
return response;
}
@ApiOperation(value = "接收需据接口")
@GetMapping("/consume")
public MeituanTokenResponse consume(@RequestParam("code") String code) throws Exception {
MeituanTokenResponse response = meituanService.prepare( code);
return response;
}
}

View File

@ -1,17 +1,17 @@
package com.sczx.order.service;
import com.meituan.sdk.auth.MeituanTokenResponse;
import com.douyin.openapi.client.models.*;
import java.util.List;
public interface DouyinService {
String resolveShortUrlToGetObjectId(String shortUrl) throws Exception;
CertificatePrepareResponse prepare(CertificatePrepareRequest req);
CertificateVerifyResponse verify(CertificateVerifyRequest req);
CertificateCancelResponse cancel(CertificateCancelRequest req);
void prepare(List<String> orderIds) throws Exception;
MeituanTokenResponse getAccessToken(String code);
MeituanTokenResponse prepare(String code);
}

View File

@ -20,7 +20,7 @@ public interface OrderService {
* @param req
* @return
*/
OrderDetailDTO verifyGroupBuyCoupons(VerifyGroupBuyCouponsReq req);
OrderDetailDTO verifyGroupBuyCoupons(VerifyGroupBuyCouponsReq req) ;
/**
* 提交租车订单

View File

@ -16,9 +16,13 @@ import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPr
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPrepareResponse;
import com.sczx.order.common.Result;
import com.sczx.order.config.DouyinTokenManager;
import com.sczx.order.dto.VerifyGroupBuyCouponsReq;
import com.sczx.order.exception.InnerException;
import com.sczx.order.service.DouyinService;
import com.sczx.order.thirdpart.dto.CompanyStoreDTO;
import com.sczx.order.thirdpart.integration.StoreInteg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@ -26,36 +30,32 @@ import org.springframework.transaction.annotation.Transactional;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@Service
public class DouyinServiceImpl implements DouyinService {
//@Value("${douyin.appId}")
private String CLIENT_KEY = "awomt6nnjlfc491m";
@Value("${coupon.douyin.client_key}")
private String CLIENT_KEY;
//@Value("${douyin.appSecret}")
private String CLIENT_SECRET = "c678c411c7a68c6f97969f2dbd8ef8fc";
@Value("${coupon.douyin.client_secret}")
private String CLIENT_SECRET;
//@Value("${douyin.accountId}")
private String ACCOUNT_ID = "7442188302710065206";
@Value("${coupon.douyin.account_id}")
private String ACCOUNT_ID;
//@Value("${douyin.poiId}")
private String POI_ID = "7442188302710065206";
@Value("${coupon.douyin.poi_id}")
private String POI_ID;
private String meituanApiUrl = "https://api-open-cater.meituan.com/oauth/token";
@Autowired
private DouyinTokenManager douyinTokenManager;
private Long DeveloperId = 116997L;
private String Signkey = "n8xlhtshk7t1luvi";
private String accessToken = "V2-26b8d231854d3fdf6a5c06273377a65e441e4c9f1c8cea4959c510bf0dd19ed7657e34bebe4b74aa455b80d3c757cd8a66e3ece9148e6d6838778db5f9600cf383dbf89c0cc27f07f777c98649bd94fe";
@Transactional(rollbackFor = Exception.class)
@Override
public String resolveShortUrlToGetObjectId(String shortUrl) {
try {
URL url = new URL(shortUrl);
@ -66,8 +66,6 @@ public class DouyinServiceImpl implements DouyinService {
String longUrl = connection.getHeaderField("Location");
connection.disconnect();
if (longUrl == null || longUrl.isEmpty()) {
return null;
}
@ -88,104 +86,106 @@ public class DouyinServiceImpl implements DouyinService {
}
/**
* 抖音验券准备返回抖音验券对象
*/
@Override
public void prepare(List<String> orderIds) throws Exception {
public CertificatePrepareResponse prepare(CertificatePrepareRequest req) {
// 获取当前有效的access_token
String accessToken = DouyinTokenManager.getCurrentToken();
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
//抖音验券请求
try {
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET); // 改成自己的app_id跟secret
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
/* 构建请求参数,该代码示例中只给出部分参数,请用户根据需要自行构建参数值
token:
1.若用户自行维护token,将用户维护的token赋值给该参数即可
2.SDK包中有获取token的函数请根据接口path在《OpenAPI SDK 总览》文档中查找获取token函数的名字
在使用过程中请注意token互刷问题
header:
sdk中默认填充content-type请求头若不需要填充除content-type之外的请求头删除该参数即可
*/
CertificatePrepareRequest sdkRequest = new CertificatePrepareRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setAccountId(ACCOUNT_ID);
sdkRequest.setCode("");
sdkRequest.setEncryptedData("");
sdkRequest.setPoiId("");
String encryptedData = resolveShortUrlToGetObjectId(req.getEncryptedData());
sdkRequest.setEncryptedData(encryptedData);
sdkRequest.setPoiId(req.getPoiId());
CertificatePrepareResponse sdkResponse = client.CertificatePrepare(sdkRequest);
processCertificatePrepareResponse(sdkResponse);
} catch (TeaException e) {
System.out.println(e.getMessage());
System.out.println(JSONObject.toJSONString(sdkResponse));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public void processCertificatePrepareResponse(CertificatePrepareResponse response) {
String verifyToken = response.getData().getVerifyToken();
response.getData().getCertificates().forEach(certificate -> {
Long certificateId = certificate.getCertificateId();
String skuId = certificate.getSku().getSkuId();
certificate.getReserveInfo().getOrderReserveUserInfoList().forEach(orderReserveUserInfo -> {
});
});
}
@Override
public MeituanTokenResponse getAccessToken(String code) {
try {
// 使用美团SDK构建客户端
MeituanClient client = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
// 使用美团SDK的getOAuthToken方法获取token
MeituanTokenResponse response = client.getOAuthToken(58, code);
log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
return response;
} catch (Exception e) {
log.error("获取access_token异常", e);
return null;
}
}
@Override
public MeituanTokenResponse prepare(String code) {
try {
MeituanClient meituanClient = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
TuangouReceiptPrepareRequest tuangouReceiptPrepareRequest = new TuangouReceiptPrepareRequest();
tuangouReceiptPrepareRequest.setReceiptCode("0107945394376");
String appAuthToken = accessToken;
MeituanResponse<TuangouReceiptPrepareResponse> response = meituanClient.invokeApi(tuangouReceiptPrepareRequest, appAuthToken);
log.info("获取prepare响应结果: {}", JSONObject.toJSONString(response));
if (response.isSuccess()) {
TuangouReceiptPrepareResponse resp = response.getData();
System.out.println(resp);
} else {
System.out.println("调用失败");
if (sdkResponse.getExtra().getErrorCode() != 0){
throw new RuntimeException("券验证失败:" + sdkResponse.getExtra().getDescription());
}
return null;
return sdkResponse;
} catch (TeaException e) {
throw new RuntimeException("券验证失败");
} catch (Exception e) {
log.error("获取access_token异常", e);
return null;
throw new RuntimeException("券验证失败");
}
}
@Override
public CertificateVerifyResponse verify(CertificateVerifyRequest req) {
// 获取当前有效的access_token
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
try{
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
CertificateVerifyRequest sdkRequest = new CertificateVerifyRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setAccountId(ACCOUNT_ID);
sdkRequest.setPoiId(req.getPoiId());
sdkRequest.setVerifyToken(req.getVerifyToken());
CertificateVerifyResponse sdkResponse = client.CertificateVerify(sdkRequest);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public CertificateCancelResponse cancel(CertificateCancelRequest req) {
// 获取当前有效的access_token
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
try{
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
CertificateCancelRequest sdkRequest = new CertificateCancelRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setVerifyId(req.getVerifyId());
sdkRequest.setCertificateId(req.getCertificateId());
CertificateCancelResponse sdkResponse = client.CertificateCancel(sdkRequest);
System.out.println(JSONObject.toJSONString(sdkResponse));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -42,19 +42,19 @@ public class MeiTuanServiceImpl implements MeiTuanService {
// 使用美团SDK的getOAuthToken方法获取token
//MeituanTokenResponse response = client.getOAuthToken(58, code);
MeituanTokenResponse response = client.getOAuthToken(58, code);
//log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
//String token = response.getData().getAccessToken();
log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
String token = response.getData().getAccessToken();
storeInteg.recordToken(state, accessToken);
storeInteg.recordToken(state, token);
return null;
} catch (Exception e) {
throw new BizException("订单不存在");
throw new BizException("获取access_token失败");
}
}

View File

@ -3,6 +3,10 @@ package com.sczx.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.douyin.openapi.client.models.CertificatePrepareRequest;
import com.douyin.openapi.client.models.CertificatePrepareResponse;
import com.douyin.openapi.client.models.CertificateVerifyRequest;
import com.douyin.openapi.client.models.CertificateVerifyResponse;
import com.sczx.order.common.constant.RedisKeyConstants;
import com.sczx.order.common.enums.*;
import com.sczx.order.convert.OrderCarImgConvert;
@ -13,10 +17,7 @@ import com.sczx.order.exception.BizException;
import com.sczx.order.exception.InnerException;
import com.sczx.order.po.*;
import com.sczx.order.repository.*;
import com.sczx.order.service.GroupBuyCouponService;
import com.sczx.order.service.OrderDistribService;
import com.sczx.order.service.OrderService;
import com.sczx.order.service.PayService;
import com.sczx.order.service.*;
import com.sczx.order.thirdpart.dto.*;
import com.sczx.order.thirdpart.dto.req.*;
import com.sczx.order.thirdpart.integration.*;
@ -34,6 +35,7 @@ import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@Slf4j
@ -85,6 +87,12 @@ public class OrderServiceImpl implements OrderService {
@Autowired
private GroupBuyCouponService groupBuyCouponService;
@Autowired
private DouyinService douyinService;
@Autowired
private MeiTuanService meiTuanService;
@Override
public OrderMainPO queryOrderMainPoByOrderNo(String orderNo, String delFlag) {
LambdaQueryWrapper<OrderMainPO> currentOrderWrapper = new LambdaQueryWrapper<>();
@ -94,9 +102,67 @@ public class OrderServiceImpl implements OrderService {
}
@Override
public OrderDetailDTO verifyGroupBuyCoupons(VerifyGroupBuyCouponsReq req) {
//校验用户是否存在,是否实名认证
GroupBuyOrderInfoDto groupBuyOrderInfoDTO = groupBuyCouponService.getGroupBuyOrderInfoDto(req.getCouponCode(), req.getCouponType(), req.getMobile());
public OrderDetailDTO verifyGroupBuyCoupons(VerifyGroupBuyCouponsReq req){
if (req.getCouponType() == CouponTypeEnum.MT.getCode()){
//TODO 调用美团验券接口
}else if (req.getCouponType() == CouponTypeEnum.DY.getCode()){
//根据门店ID获取抖音门店ID
CompanyStoreDTO store = storeInteg.getStoreById(req.getStoreId().intValue());
String poiId = store.getDyStoreId();
//调用抖音验券接口
CertificatePrepareRequest prepareRequest = new CertificatePrepareRequest();
prepareRequest.setPoiId(poiId);
prepareRequest.setEncryptedData(req.getCouponCode());
CertificatePrepareResponse response =douyinService.prepare(prepareRequest);
//获取核销所需信息
String verifyToken = response.getData().getVerifyToken();
AtomicReference<String> phone = new AtomicReference<>("");
response.getData().getCertificates().forEach(certificate -> {
Long certificateId = certificate.getCertificateId();
String skuId = certificate.getSku().getSkuId();
certificate.getReserveInfo().getOrderReserveUserInfoList().forEach(orderReserveUserInfo -> {
phone.set(orderReserveUserInfo.getPhone());
System.out.println(orderReserveUserInfo.getPhone());
});
});
String finalPhone = phone.get();
//校验用户是否存在,是否实名认证
GroupBuyOrderInfoDto groupBuyOrderInfoDTO = groupBuyCouponService.getGroupBuyOrderInfoDto(req.getCouponCode(), req.getCouponType(), req.getMobile());
if (finalPhone != null && !finalPhone.isEmpty()) {
// 如果校验券没有返回手机号码,则使用前端所传手机号码
log.info("您所团购的订单未填写手机号,将使用您提交给门店的手机号生成租车订单");
}else{
// 如果校验券有返回手机号码,则使用前端所传手机号码与该脱敏手机号码比对
}
//核销抖音团购券
CertificateVerifyRequest verifyRequest = new CertificateVerifyRequest();
verifyRequest.setVerifyToken(verifyToken);
verifyRequest.setPoiId(poiId);
CertificateVerifyResponse verifyResponse =douyinService.verify(verifyRequest);
verifyResponse.getData().getVerifyResults().forEach(verifyResult -> {
//撤销验券需要下面两个值
verifyResult.getCertificateId();
verifyResult.getVerifyId();
});
//TODO 生成订单
}
return null;
}

View File

@ -65,4 +65,10 @@ public class CompanyStoreDTO {
@ApiModelProperty("以租代售分成比例")
private BigDecimal daishouRatio;
@ApiModelProperty("美团token")
private String mtToken;
@ApiModelProperty("抖音门店id")
private String dyStoreId;
}