package com.cyksj.service.recharge.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.task.GlobalThreadPoolTaskExecutor; import com.cyksj.common.util.Jsons; import com.cyksj.mapper.*; import com.cyksj.model.entity.*; import com.cyksj.model.response.recharge.RedeemCloudCdkQueryResponse; import com.cyksj.model.response.recharge.RedeemCloudTaskResultResponse; import com.cyksj.redis.RedisService; import com.cyksj.server.recharge.dto.TaskResult; import com.cyksj.service.chatgpt.GptUserRechargeRecordService; import com.cyksj.service.recharge.GptRechargeCardKeyChannelService; import com.cyksj.service.recharge.RedeemCloudRechargeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Date; @Service @RequiredArgsConstructor @Slf4j public class RedeemCloudRechargeServiceImpl implements RedeemCloudRechargeService { private final GptRechargeCardKeyChannelService gptRechargeCardKeyChannelService; private final GroupsRelationMapper relationMapper; private final GroupsMapper groupsMapper; private final GoodsDonSkuMapper skuMapper; private final RedisService redisService; private final OrderDonMapper orderDonMapper; private final GptRechargeCardKeyMapper gptRechargeCardKeyMapper; private final GptUserRechargeRecordService gptUserRechargeRecordService; private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance(); private static final int SUCCESS_CODE = 200; private static final int LEGACY_SUCCESS_CODE = 10000; @Override public RedeemCloudCdkQueryResponse validateCardKey(String cardKey) throws Exception { String baseUrl = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String url = baseUrl + "/sys-cdk/query?cdkCode=" + encode(cardKey); HttpResponse response = HttpRequest.get(url) .setConnectionTimeout(10000) .execute(); String body = response.body(); log.info("redeemcloud validate card key response: {}", body); return Jsons.parseObject(body, RedeemCloudCdkQueryResponse.class); } @Override public String confirmRecharge(GroupsRelation relation, String gptCardKey, CmsUser cmsUser) { Long relationId = relation.getId(); String rechargePayload = relation.getGptToken(); String rechargeAccount = relation.getAccount(); String taskId; try { taskId = submitTask(gptCardKey, rechargePayload); } catch (Exception e) { setRechargeRelationStatusAutoError(relation); if (cmsUser == null) { throw BusinessRuntimeException.getInstance("充值失败,请联系客服开通"); } throw BusinessRuntimeException.getInstance("充值失败,{0}", e.getMessage()); } String operator = cmsUser != null ? cmsUser.getNickname() : null; String password = relation.getPassword(); relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(GroupsRelation::getAccount, rechargeAccount) .set(GroupsRelation::getOperator, operator) .set(StrUtil.isNotBlank(password), GroupsRelation::getPassword, password) .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging) .set(GroupsRelation::getCardKey, gptCardKey) .set(GroupsRelation::getGptToken, rechargePayload) .set(GroupsRelation::getGptTaskId, taskId) .eq(GroupsRelation::getId, relationId)); relation.setRechargeStatus(GroupsRelation.RechargeStatus.recharging); relation.setCardKey(gptCardKey); relation.setOperator(operator); return taskId; } private String submitTask(String cardKey, String rechargePayload) { log.info("start submit redeemcloud task - cardKey: {}", cardKey); try { String baseUrl = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String requestBody = buildExchangeBody(baseUrl, cardKey, rechargePayload); String url = baseUrl + "/sys-cdk/exchange?cdkCode=" + encode(cardKey); HttpResponse response = addBrowserHeaders(HttpRequest.post(url) .setConnectionTimeout(60000) .header("content-type", "application/json")) .body(requestBody) .execute(); String body = response.body(); log.info("redeemcloud submit response: {}", body); JSONObject jsonResponse = JSONUtil.parseObj(body); Object data = jsonResponse.get("data"); if (isSuccessResponse(jsonResponse) && data instanceof CharSequence && StrUtil.isNotBlank(data.toString())) { return data.toString(); } if (data instanceof JSONObject) { JSONObject dataObj = (JSONObject) data; String status = dataObj.getStr("status"); if ("FAILED".equalsIgnoreCase(status)) { throw BusinessRuntimeException.getInstance(getErrorMessage(dataObj, jsonResponse)); } } throw BusinessRuntimeException.getInstance(getErrorMessage(null, jsonResponse)); } catch (Exception e) { log.error("submit redeemcloud task failed: {}", e.getMessage(), e); throw new RuntimeException("提交任务失败: " + e.getMessage(), e); } } private String buildExchangeBody(String baseUrl, String cardKey, String rechargePayload) throws Exception { if (StrUtil.isBlank(rechargePayload) || !StrUtil.startWith(rechargePayload.trim(), "{")) { throw BusinessRuntimeException.getInstance("redeemcloud提交任务需要完整session JSON"); } JSONObject payload = JSONUtil.parseObj(rechargePayload); if (isExchangeBody(payload)) { return payload.toString(); } return verifyAccount(baseUrl, cardKey, rechargePayload).toString(); } private boolean isExchangeBody(JSONObject payload) { return payload.containsKey("cdkPackageName") && payload.containsKey("payload") && payload.containsKey("idp"); } private JSONObject verifyAccount(String baseUrl, String cardKey, String content) { String url = baseUrl + "/account/verify?cdkCode=" + encode(cardKey) + "&content=" + encode(content); HttpResponse response = addBrowserHeaders(HttpRequest.post(url) .setConnectionTimeout(60000)) .execute(); String body = response.body(); log.info("redeemcloud account verify response: {}", body); JSONObject jsonResponse = JSONUtil.parseObj(body); Object data = jsonResponse.get("data"); if (isSuccessResponse(jsonResponse) && data instanceof JSONObject) { return (JSONObject) data; } throw BusinessRuntimeException.getInstance(getErrorMessage(data instanceof JSONObject ? (JSONObject) data : null, jsonResponse)); } private HttpRequest addBrowserHeaders(HttpRequest request) { return request .header("accept", "*/*") .header("accept-language", "zh-CN") .header("origin", "https://redeemcloud.com") .header("referer", "https://redeemcloud.com/zh") .header("sec-ch-ua", "\"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"150\", \"Microsoft Edge\";v=\"150\"") .header("sec-ch-ua-mobile", "?0") .header("sec-ch-ua-platform", "\"Windows\"") .header("sec-fetch-dest", "empty") .header("sec-fetch-mode", "cors") .header("sec-fetch-site", "same-origin") .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0") .header("x-product-code", "GPT"); } private boolean isSuccessResponse(JSONObject jsonResponse) { Integer code = jsonResponse.getInt("code"); return code != null && (code == SUCCESS_CODE || code == LEGACY_SUCCESS_CODE) && !Boolean.FALSE.equals(jsonResponse.getBool("flag")); } private String encode(String value) { try { return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @Override public TaskResult getTaskResult(GroupsRelation relation) { String cardKey = relation.getCardKey(); String taskId = relation.getGptTaskId(); try { RedeemCloudTaskResultResponse taskResultResponse = queryTaskResult(cardKey, taskId); TaskResult taskResult = convertTaskResult(taskResultResponse); resetGroupsRelationRechargeStatus(relation, taskResult.getStatus(), taskResult.getError()); return taskResult; } catch (Exception e) { log.error("query redeemcloud task result failed: {}", e.getMessage(), e); throw new RuntimeException("获取任务结果失败: " + e.getMessage(), e); } } private RedeemCloudTaskResultResponse queryTaskResult(String cardKey, String taskId) throws Exception { String baseUrl = gptRechargeCardKeyChannelService.getCardKeyDomain(cardKey); String url = baseUrl + "/sys-cdk/exchange/result?taskId=" + encode(taskId); HttpResponse response = HttpRequest.get(url) .setConnectionTimeout(10000) .execute(); String body = response.body(); log.info("redeemcloud task result response: {}", body); return Jsons.parseObject(body, RedeemCloudTaskResultResponse.class); } private TaskResult convertTaskResult(RedeemCloudTaskResultResponse taskResultResponse) { TaskResult result = new TaskResult(); if (taskResultResponse == null || taskResultResponse.getCode() == null || taskResultResponse.getCode() != SUCCESS_CODE) { result.setStatus("failed"); result.setError(taskResultResponse != null ? taskResultResponse.getMessage() : "任务查询失败"); return result; } if (taskResultResponse.getData() == null) { result.setStatus("recharging"); result.setResult(taskResultResponse.getMessage()); return result; } String status = taskResultResponse.getData().getStatus(); if ("COMPLETED".equalsIgnoreCase(status)) { result.setStatus("completed"); } else if ("FAILED".equalsIgnoreCase(status) || "ERROR".equalsIgnoreCase(status)) { result.setStatus("failed"); } else { result.setStatus("recharging"); } result.setResult(status); result.setError(taskResultResponse.getData().getErrorMessage()); return result; } private String getErrorMessage(JSONObject data, JSONObject response) { if (data != null) { String error = data.getStr("error"); if (StrUtil.isNotBlank(error)) { return error; } String errorMessage = data.getStr("errorMessage"); if (StrUtil.isNotBlank(errorMessage)) { return errorMessage; } String result = data.getStr("result"); if (StrUtil.isNotBlank(result)) { return result; } } String message = response.getStr("message"); if (StrUtil.isBlank(message)) { message = response.getStr("msg"); } return StrUtil.isNotBlank(message) ? message : "提交任务失败"; } public void setRechargeRelationStatusAutoError(GroupsRelation relation) { TASK_EXECUTOR.execute(() -> relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(GroupsRelation::getAccount, relation.getAccount()) .set(GroupsRelation::getGptToken, relation.getGptToken()) .set(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharge_error) .eq(GroupsRelation::getId, relation.getId()))); } private void resetGroupsRelationRechargeStatus(GroupsRelation relation, String status, String result) { if (!"completed".equals(status) && !"failed".equals(status) && !"unknown".equals(status)) { return; } relation = relationMapper.selectById(relation.getId()); if (relation == null || relation.getRechargeStatus() != GroupsRelation.RechargeStatus.recharging) { return; } String taskId = relation.getGptTaskId(); Integer rechargeRemainNum = relation.getRechargeRemainNum(); GroupsRelation.RechargeStatus rechargeStatus = "completed".equals(status) ? GroupsRelation.RechargeStatus.complete : GroupsRelation.RechargeStatus.recharge_error; String cardKey = relation.getCardKey(); GptRechargeCardKey gptRechargeCardKey = gptRechargeCardKeyMapper.selectOne(Wrappers.lambdaQuery(GptRechargeCardKey.class) .eq(GptRechargeCardKey::getCardKey, cardKey) .last("limit 1")); if (rechargeStatus == GroupsRelation.RechargeStatus.recharge_error && gptRechargeCardKey != null) { gptRechargeCardKeyMapper.update(null, Wrappers.lambdaUpdate(GptRechargeCardKey.class) .set(GptRechargeCardKey::getStatus, Boolean.FALSE) .set(GptRechargeCardKey::getOrderId, null) .set(GptRechargeCardKey::getOrderNo, null) .eq(GptRechargeCardKey::getId, gptRechargeCardKey.getId()) .eq(GptRechargeCardKey::getStatus, Boolean.TRUE)); } if (rechargeStatus == GroupsRelation.RechargeStatus.recharge_error && !redisService.setNx(RedisService.key.GPT_RECHARGE_RECOVER_KEY.getName() + taskId, taskId, RedisService.key.GPT_RECHARGE_RECOVER_KEY.getTimeout())) { return; } Date startTime = relation.getStartTime(); DateTime expiryTime = null; if (rechargeStatus == GroupsRelation.RechargeStatus.complete && startTime == null) { GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); if (groupsTrips != null) { startTime = DateTime.now(); GoodsDonSku sku = skuMapper.selectById(groupsTrips.getSkuId()); if (sku.getDays() != null && sku.getDays() > 0) { expiryTime = DateUtil.offsetDay(startTime, sku.getDays()); } else { expiryTime = DateUtil.offsetMonth(startTime, sku.getMonths()); } } } String rechargeErrorInfo = rechargeStatus == GroupsRelation.RechargeStatus.recharge_error ? result : null; int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(rechargeStatus == GroupsRelation.RechargeStatus.recharge_error, GroupsRelation::getRechargeRemainNum, rechargeRemainNum + 1) .set(GroupsRelation::getRechargeStatus, rechargeStatus) .set(expiryTime != null, GroupsRelation::getStartTime, startTime) .set(expiryTime != null, GroupsRelation::getExpiryTime, expiryTime) .set(StrUtil.isNotEmpty(rechargeErrorInfo), GroupsRelation::getRechargeErrorInfo, rechargeErrorInfo) .eq(GroupsRelation::getId, relation.getId()) .le(GroupsRelation::getRechargeRemainNum, relation.getRechargeNum()) .eq(GroupsRelation::getRechargeStatus, GroupsRelation.RechargeStatus.recharging)); if (update == 0) { return; } if (rechargeStatus == GroupsRelation.RechargeStatus.complete) { String remark = StrUtil.isNotBlank(relation.getOperator()) ? "后台自动代充" : "自动代充"; gptUserRechargeRecordService.recordGptUserRechargeNum(relation.getCardKey(), relation.getUserId(), relation.getId(), relation.getAccount(), -1, relation.getOperator(), remark); if (gptRechargeCardKey != null) { Long orderId = gptRechargeCardKey.getOrderId(); OrderDon orderDon = orderDonMapper.selectById(orderId); if (orderDon != null && orderDon.getStatus() == OrderDon.Status.hasPayment) { orderDon.setStatus(OrderDon.Status.complete); orderDonMapper.updateById(orderDon); } } } } }