douyin meituan

This commit is contained in:
2025-10-23 20:41:42 +08:00
parent 3a0d707a5a
commit b5c4e4226a
11 changed files with 465 additions and 4 deletions

13
pom.xml
View File

@ -243,8 +243,21 @@
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>4.44.0</version>
</dependency>
<dependency>
<groupId>com.douyin.openapi</groupId>
<artifactId>sdk</artifactId>
<version>1.0.6</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>douyin-openapi-repo</id>
<url>https://artifacts-cn-beijing.volces.com/repository/douyin-openapi/</url>
</repository>
</repositories>
<!-- Build Configuration -->
<build>
<finalName>sczx_order</finalName>

View File

@ -0,0 +1,107 @@
package com.sczx.order.config;
import com.aliyun.tea.TeaException;
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 java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 抖音 client_token 管理器
* 负责定时获取和缓存 client_token
*/
public class DouyinTokenManager {
// 应用凭证信息
private static final String CLIENT_KEY = "awomt6nnjlfc491m";
private static final String CLIENT_SECRET = "c678c411c7a68c6f97969f2dbd8ef8fc";
// Token 缓存
private static final AtomicReference<OauthClientTokenResponse> tokenCache =
new AtomicReference<>();
// 定时任务执行器
private static final ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r, "DouyinTokenRefreshThread");
t.setDaemon(false);
return t;
});
static {
// 初始化时立即获取一次 token
refreshClientToken();
// 每小时更新一次 token (3600秒)
scheduler.scheduleAtFixedRate(
DouyinTokenManager::refreshClientToken,
3600,
3600,
TimeUnit.SECONDS
);
}
/**
* 获取当前有效的 client_token
*
* @return 当前有效的 access_token
*/
public static String getCurrentToken() {
OauthClientTokenResponse response = tokenCache.get();
if (response != null && response.getData() != null) {
return response.getData().getAccessToken();
}
return null;
}
/**
* 刷新 client_token
*/
private static void refreshClientToken() {
try {
Config config = new Config()
.setClientKey(CLIENT_KEY)
.setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
OauthClientTokenRequest sdkRequest = new OauthClientTokenRequest();
sdkRequest.setClientKey(CLIENT_KEY);
sdkRequest.setClientSecret(CLIENT_SECRET);
sdkRequest.setGrantType("client_credential");
OauthClientTokenResponse sdkResponse = client.OauthClientToken(sdkRequest);
// 更新缓存
tokenCache.set(sdkResponse);
if (sdkResponse.getData() != null) {
System.out.println("抖音 client_token 更新成功,有效期至: " +
(System.currentTimeMillis() + sdkResponse.getData().getExpiresIn() * 1000));
}
} catch (TeaException e) {
System.err.println("获取抖音 client_token 失败 (TeaException): " + e.getMessage());
} catch (Exception e) {
System.err.println("获取抖音 client_token 失败 (Exception): " + e.getMessage());
}
}
/**
* 关闭定时任务
*/
public static void shutdown() {
scheduler.shutdown();
// 获取当前有效的 client_token
String token = DouyinTokenManager.getCurrentToken();
// 使用 token 调用抖音 API
if (token != null) {
// 调用订单查询等接口
}
}
}

View File

@ -65,6 +65,12 @@ public class ClientOrderController {
return Result.ok(orderService.depositFreePayRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "第三方订单")
@PostMapping("/thirdPlatformRentCarOrder")
public Result<RentCarOrderResultDTO> thirdPlatformRentCarOrder(@Valid @RequestBody RentCarThirdPlatformOrderReq rentCarOrderReq){
return Result.ok(orderService.thirdPlatformRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "续租车")
@PostMapping("/reRentalCarOrder")
public Result<RentCarOrderResultDTO> reRentalCarOrder(@Valid @RequestBody ReRentCarReq rentCarOrderReq){

View File

@ -0,0 +1,23 @@
package com.sczx.order.controller;
import com.sczx.order.common.Result;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Slf4j
public class MeiTuanController {
@ApiOperation(value = "接收需同步数据接口")
@GetMapping("/authorization")
public Result authorization(@RequestParam("code") String code, @RequestParam("sign") String sign, @RequestParam("developerId") Long developerId, @RequestParam("businessId") int businessId,
@RequestParam("state") String state){
log.info("接收美团授权数据 - code: {}, sign: {}, developerId: {}, businessId: {}, state: {}",
code, sign, developerId, businessId, state);
return null;
}
}

View File

@ -60,4 +60,10 @@ public class StoreOrderController {
public Result<OrderDTO> confirmReturnCar(@RequestBody ReturnCarReq returnCarReq){
return Result.ok(orderService.confirmReturnCar(returnCarReq));
}
@ApiOperation(value = "抖音团购核销")
@PostMapping("/verifyCar")
public Result<OrderDTO> verifyCar(@RequestBody ReturnCarReq returnCarReq){
return Result.ok(orderService.confirmReturnCar(returnCarReq));
}
}

View File

@ -1,9 +1,6 @@
package com.sczx.order.convert;
import com.sczx.order.dto.OrderDTO;
import com.sczx.order.dto.OrderDetailDTO;
import com.sczx.order.dto.RentCarOrderReq;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.dto.*;
import com.sczx.order.po.OrderMainPO;
import com.sczx.order.po.OrderSubPO;
import com.sczx.order.thirdpart.dto.*;
@ -46,6 +43,31 @@ public interface OrderConvert {
})
OrderMainPO subOrderToPo(RentCarOrderReq rentCarOrderReq, SimpleUserInfoDTO userInfoDTO, RentCarRuleDTO rentCarRuleDTO);
@Mappings({
@Mapping(target = "orderId", ignore = true),
@Mapping(target = "createTime", ignore = true),
@Mapping(target = "updateTime", ignore = true),
@Mapping(target = "delFlag", ignore = true),
@Mapping(source = "rentCarOrderReq.storeId", target = "storeId"),
@Mapping(source = "rentCarOrderReq.operatorId", target = "operatorId"),
@Mapping(source = "rentCarOrderReq.carModelId", target = "carModelId"),
@Mapping(source = "rentCarOrderReq.rentCarRuleId", target = "rentCarRuleId"),
@Mapping(source = "rentCarOrderReq.rentBatteyRuleId", target = "rentBatteyRuleId"),
@Mapping(source = "rentCarOrderReq.isDepositFree", target = "isDepositFree"),
@Mapping(source = "rentCarOrderReq.batteryType", target = "batteryType"),
@Mapping(source = "userInfoDTO.userId", target = "customerId"),
@Mapping(source = "userInfoDTO.userName", target = "customerName"),
@Mapping(source = "userInfoDTO.phoneNumber", target = "customerPhone"),
@Mapping(source = "rentCarRuleDTO.rentalType", target = "rentalType"),
@Mapping(source = "rentCarRuleDTO.rentalDays", target = "rentalDays"),
@Mapping(source = "rentCarRuleDTO.rentalPrice", target = "rentalPrice"),
@Mapping(source = "rentCarRuleDTO.depositPrice", target = "depositPrice"),
@Mapping(source = "rentCarRuleDTO.overdueFee", target = "overdueFee"),
@Mapping(source = "rentCarRuleDTO.overdueType", target = "overdueType")
})
OrderMainPO subOrderToPo(RentCarThirdPlatformOrderReq rentCarOrderReq, SimpleUserInfoDTO userInfoDTO, RentCarRuleDTO rentCarRuleDTO);
@Mappings({
@Mapping(source = "orderMainPO.orderId", target = "orderId"),

View File

@ -0,0 +1,62 @@
package com.sczx.order.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @Author: 张黎
* @Date: 2025/07/25/16:58
* @Description:
*/
@Data
@ApiModel(value = "租车订单请求参数")
public class RentCarThirdPlatformOrderReq {
@ApiModelProperty(value = "订单编号,租车不需要传,续租和逾期处理需要传")
private String orderNo;
@ApiModelProperty(value = "运营商id")
private Long operatorId;
@ApiModelProperty(value = "门店id")
@NotNull(message = "门店id不能为空")
private Long storeId;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty(value = "客户姓名")
private String customerName;
@ApiModelProperty(value = "客户电话")
private String customerPhone;
@ApiModelProperty(value = "第三方订单号")
private String thirdOrderNo;
@ApiModelProperty(value = "订单来源")
private String orderSource;
@ApiModelProperty("车型ID")
@NotNull(message = "车型ID不能为空")
private Long carModelId;
@ApiModelProperty(value = "租车套餐id")
@NotNull(message = "租车套餐id不能为空")
private Long rentCarRuleId;
@ApiModelProperty(value = "租电套餐id")
private Long rentBatteyRuleId;
@ApiModelProperty("选择的电池类型")
private String batteryType;
@ApiModelProperty("是否开通免押")
private Boolean isDepositFree = false;
}

View File

@ -0,0 +1,8 @@
package com.sczx.order.service;
public interface DouyinService {
String resolveShortUrlToGetObjectId(String shortUrl) throws Exception;
}

View File

@ -36,6 +36,14 @@ public interface OrderService {
*/
RentCarOrderResultDTO depositFreePayRentCarOrder(RentCarOrderReq rentCarOrderReq);
/**
* 第三方订单
* @param rentCarOrderReq
* @return
*/
RentCarOrderResultDTO thirdPlatformRentCarOrder(RentCarThirdPlatformOrderReq rentCarOrderReq);
/**
* 续租车
* @param rentCarOrderReq

View File

@ -0,0 +1,51 @@
package com.sczx.order.service.impl;
import com.sczx.order.exception.InnerException;
import com.sczx.order.service.DouyinService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.HttpURLConnection;
import java.net.URL;
@Slf4j
@Service
public class DouyinServiceImpl implements DouyinService {
@Transactional(rollbackFor = Exception.class)
@Override
public String resolveShortUrlToGetObjectId(String shortUrl) {
try{
URL url = new URL(shortUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false); // 禁止自动重定向
connection.connect();
String longUrl = connection.getHeaderField("Location");
connection.disconnect();
if (longUrl == null || longUrl.isEmpty()) {
return null;
}
// 使用正则表达式匹配object_id参数
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("object_id=([^&]*)");
java.util.regex.Matcher matcher = pattern.matcher(longUrl);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}catch (Exception e){
log.error("二维码不正确");
throw new InnerException("扫码核销失败,二维码不正确");
}
}
}

View File

@ -462,6 +462,161 @@ public class OrderServiceImpl implements OrderService {
}
@Transactional(rollbackFor = Exception.class)
@Override
public RentCarOrderResultDTO thirdPlatformRentCarOrder(RentCarThirdPlatformOrderReq rentCarOrderReq) {
LambdaQueryWrapper<OrderMainPO> currentOrderWrapper = new LambdaQueryWrapper<>();
currentOrderWrapper.eq(OrderMainPO::getCustomerId, rentCarOrderReq.getCustomerId())
.notIn(OrderMainPO::getOrderStatus, Arrays.asList(OrderStatusEnum.AUTO_END.getCode(), OrderStatusEnum.MANUAL_END.getCode()))
.eq(OrderMainPO::getDelFlag, "0");
List<OrderMainPO> currentOrderList = orderMainRepo.list(currentOrderWrapper);
OrderMainPO waitPayOrder = currentOrderList.stream().filter(order -> order.getOrderStatus().equals(OrderStatusEnum.WAIT_PAY.getCode())).findFirst().orElse(null);
if(!currentOrderList.isEmpty() && waitPayOrder==null){
throw new BizException("您有未完成的订单,请先完成订单");
}
if(waitPayOrder!=null){
log.info("存在待支付的订单,取消订单");
PayOrderReq payOrderReq = new PayOrderReq();
payOrderReq.setOrderNo(waitPayOrder.getOrderNo());
cancelOrder(payOrderReq,null);
}
//判断是否存有空闲车辆可用
LambdaQueryWrapper<CarPO> carWrapper = new LambdaQueryWrapper<>();
carWrapper.eq(CarPO::getModelId, rentCarOrderReq.getCarModelId())
.eq(CarPO::getDelFlag, "0")
.eq(CarPO::getBrsStatus, "0")
.eq(CarPO::getStoreId, rentCarOrderReq.getStoreId());
List<CarPO> carPOList = carRepo.list(carWrapper);
if(CollectionUtils.isEmpty(carPOList)){
throw new BizException("门店没有该车型的车辆可租");
}
//获取门店信息
// CompanyStoreDTO companyStoreDTO = storeInteg.getStoreById(Integer.valueOf(rentCarOrderReq.getStoreId().toString()));
// if(Objects.isNull(companyStoreDTO)){
// throw new BizException("运营商或门店已下架");
// }
CarModelSimpleDTO carModelSimpleDTO = carInteg.getCarModelByModelId(rentCarOrderReq.getCarModelId());
RentCarRuleDTO rentCarRuleDTO = carInteg.getRentCarRuleByCarRuleId(rentCarOrderReq.getRentCarRuleId());
RentBatteyRuleDTO rentBatteyRuleDTO = null;
if(rentCarOrderReq.getRentBatteyRuleId()!=null){
rentBatteyRuleDTO = carInteg.getRentBatteyRuleByBatteyRuleId(rentCarOrderReq.getRentBatteyRuleId());
}
String redisLockKey = RedisKeyConstants.ORDER_SUB_KEY + rentCarOrderReq.getCustomerId();
SimpleUserInfoDTO userInfoDTO = new SimpleUserInfoDTO();
userInfoDTO.setUserId(Math.toIntExact(rentCarOrderReq.getCustomerId()));
userInfoDTO.setUserName(rentCarOrderReq.getCustomerName());
userInfoDTO.setPhoneNumber(rentCarOrderReq.getCustomerPhone());
if(redisUtil.getRedisLock(redisLockKey, "租车下单")) {
try{
//TODO 这里保存订单要做事物处理
//生成订单主表
OrderMainPO orderMainPO = OrderConvert.INSTANCE.subOrderToPo(rentCarOrderReq, userInfoDTO, rentCarRuleDTO);
//orderMainPO.setOperatorId(Long.valueOf(companyStoreDTO.getOperatingCompanyId()));
orderMainPO.setOrderNo(OrderUtil.generateOrderNo());
orderMainPO.setOrderStatus(OrderStatusEnum.WAIT_PICK.getCode());
orderMainPO.setFirstOrderTime(LocalDateTime.now());
//设置预计还车时间
LocalDateTime endRentTime = OrderUtil.getEndRentTime(orderMainPO.getFirstOrderTime(),1,rentCarRuleDTO.getRentalDays(), rentCarRuleDTO.getRentalType());
orderMainPO.setEndRentTime(endRentTime);
//生成子表订单
String paymentType = rentCarOrderReq.getOrderSource();
List<OrderSubPO> orderSubPOList = new ArrayList<>();
//如果未开通免押则要生成押金订单
if(!rentCarOrderReq.getIsDepositFree()){
OrderSubPO depositOrder = new OrderSubPO();
depositOrder.setSuborderNo(OrderUtil.generateSubOrderNo(OrderUtil.YJ_PREFIX));
depositOrder.setSuborderType(SubOrderTypeEnum.DEPOSIT.getCode());
depositOrder.setAmount(orderMainPO.getDepositPrice());
depositOrder.setCreatedAt(LocalDateTime.now());
depositOrder.setPaymentMethod(paymentType);
depositOrder.setTransactionId(rentCarOrderReq.getThirdOrderNo());
orderSubPOList.add(depositOrder);
}
//如果选择了租电套餐,则还需要生成租电子订单
if(rentBatteyRuleDTO!=null){
rentBatteyRuleDTO = carInteg.getRentBatteyRuleByBatteyRuleId(rentCarOrderReq.getRentBatteyRuleId());
OrderSubPO depositOrder = new OrderSubPO();
depositOrder.setSuborderNo(OrderUtil.generateSubOrderNo(OrderUtil.ZD_PREFIX));
depositOrder.setSuborderType(SubOrderTypeEnum.RENTBATTEY.getCode());
depositOrder.setAmount(rentBatteyRuleDTO.getRentPrice());
depositOrder.setCreatedAt(LocalDateTime.now());
depositOrder.setPaymentMethod(paymentType);
depositOrder.setReturnTime(endRentTime);
depositOrder.setTransactionId(rentCarOrderReq.getThirdOrderNo());
orderSubPOList.add(depositOrder);
}
//生成租车订单
BigDecimal rentCarOrderAmount = OrderUtil.getRentCarAmount(rentCarRuleDTO.getRentalType(), rentCarRuleDTO.getRentalPrice(), rentCarRuleDTO.getRentalDays());
OrderSubPO rentOrder = new OrderSubPO();
rentOrder.setSuborderNo(OrderUtil.generateSubOrderNo(OrderUtil.ZC_PREFIX));
rentOrder.setSuborderType(SubOrderTypeEnum.RENTCAR.getCode());
rentOrder.setAmount(rentCarOrderAmount);
rentOrder.setCreatedAt(LocalDateTime.now());
rentOrder.setPaymentMethod(paymentType);
rentOrder.setReturnTime(endRentTime);
rentOrder.setTransactionId(rentCarOrderReq.getThirdOrderNo());
orderSubPOList.add(rentOrder);
BigDecimal orderAmount = orderSubPOList.stream().map(OrderSubPO::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
orderMainPO.setOrderAmount(orderAmount);
//发起支付返回预支付信息
String paymentId = OrderUtil.generateSubOrderNo(OrderUtil.ZF_PREFIX);
for(OrderSubPO orderSubPO : orderSubPOList){
orderSubPO.setPaymentId(paymentId);
orderSubPO.setPayStatus(PayStatusEnum.USERPAYING.getCode());
}
orderMainRepo.save(orderMainPO);
orderSubPOList.forEach(orderSubPO -> {
orderSubPO.setOrderId(orderMainPO.getOrderId());
});
orderSubRepo.saveBatch(orderSubPOList);
//返回订单信息
OrderDTO orderDTO = OrderConvert.INSTANCE.poToDto(orderMainPO);
//orderDTO.setCompanyStoreDTO(companyStoreDTO);
orderDTO.setCarModelSimpleDTO(carModelSimpleDTO);
RentCarOrderResultDTO rentCarOrderResultDTO = new RentCarOrderResultDTO();
rentCarOrderResultDTO.setOrderMainInfo(orderDTO);
//rentCarOrderResultDTO.setUnifiedPaymentInfo(unifiedPaymentInfoDTO);
return rentCarOrderResultDTO;
}catch (Exception e){
log.warn("下单失败", e);
throw e;
} finally {
redisUtil.deleteRedisLock(redisLockKey);
}
} else {
log.warn("下单失败,锁已被占用");
throw new InnerException("服务器正在处理,请稍后再试");
}
}
@Transactional(rollbackFor = Exception.class)
@Override