Commit 691af29b by renyizhao

充值功能完成

parent 7bc08650
......@@ -9,6 +9,11 @@ public class NewApiProperties {
private String adminToken;
/**
* New API 用户ID(管理员)
*/
private String adminUserId = "1";
public String getBaseUrl() {
return baseUrl;
}
......@@ -24,4 +29,12 @@ public class NewApiProperties {
public void setAdminToken(String adminToken) {
this.adminToken = adminToken;
}
public String getAdminUserId() {
return adminUserId;
}
public void setAdminUserId(String adminUserId) {
this.adminUserId = adminUserId;
}
}
\ No newline at end of file
......@@ -14,4 +14,5 @@ public class AiTokenRespVO {
private Boolean hasToken;
private String apiKey;
private String message;
private String balance;
}
\ No newline at end of file
......@@ -2,13 +2,16 @@ package com.luhu.computility.module.apihub.controller.app;
import com.luhu.computility.framework.security.core.util.SecurityFrameworkUtils;
import com.luhu.computility.module.apihub.controller.admin.newapi.AiTokenRespVO;
import com.luhu.computility.module.apihub.controller.admin.newapi.NewApiResponse;
import com.luhu.computility.module.apihub.service.newapi.AiTokenService;
import com.luhu.computility.module.apihub.service.newapi.NewApiClient;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import com.luhu.computility.module.member.service.user.MemberUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
@Slf4j
@RestController
......@@ -21,6 +24,9 @@ public class AppAiTokenController {
@Resource
private MemberUserService memberUserService;
@Resource
private NewApiClient newApiClient;
@PostMapping("/get")
public AiTokenRespVO getToken() {
Long userId = SecurityFrameworkUtils.getLoginUserId();
......@@ -90,11 +96,33 @@ public class AppAiTokenController {
boolean hasToken = user.getNewapiKey() != null && !user.getNewapiKey().isEmpty();
log.info("[查询令牌] hasToken={}, apiKey={}", hasToken, hasToken ? "***" : null);
return AiTokenRespVO.builder()
// 获取 New API 真实余额
String balance = "0.00";
if (hasToken && user.getNewapiUserId() != null && user.getNewapiAccessToken() != null) {
try {
Integer newapiUserId = Integer.parseInt(user.getNewapiUserId().toString());
NewApiResponse<Map<String, Object>> infoResp = newApiClient.getUserInfo(
user.getNewapiAccessToken(), newapiUserId);
if (infoResp.getSuccess() && infoResp.getData() != null) {
Map<String, Object> data = infoResp.getData();
Long quota = data.get("quota") != null ? ((Number) data.get("quota")).longValue() : 0L;
// 1元 = 500000 quota
double yuan = quota / 500000.0;
balance = String.format("%.2f", yuan);
log.info("[查询令牌] New API 余额: quota={}, yuan={}", quota, balance);
}
} catch (Exception e) {
log.error("[查询令牌] 获取 New API 余额失败", e);
}
}
AiTokenRespVO.AiTokenRespVOBuilder builder = AiTokenRespVO.builder()
.success(true)
.hasToken(hasToken)
.apiKey(user.getNewapiKey())
.build();
.balance(balance);
return builder.build();
}
}
\ No newline at end of file
......@@ -233,6 +233,56 @@ public class NewApiClient {
}
}
/**
* 获取用户信息(包含 quota 余额)
*/
public NewApiResponse<Map<String, Object>> getUserInfo(String accessToken, Integer userId) {
String url = newApiProperties.getBaseUrl() + "/api/user/self";
log.info("[NewApiClient.getUserInfo] 请求URL: {}", url);
try {
HttpResponse response = HttpRequest.get(url)
.header(HEADER_USER, String.valueOf(userId))
.header(HEADER_AUTH, "Bearer " + accessToken)
.timeout(30000)
.execute();
log.info("[NewApiClient.getUserInfo] 响应状态: {}", response.getStatus());
log.info("[NewApiClient.getUserInfo] 响应内容: {}", response.body());
String body = response.body();
if (StrUtil.isBlank(body)) {
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("Empty response")
.httpResponse(response)
.build();
}
JSONObject json = JSONUtil.parseObj(body);
NewApiResponse<Map<String, Object>> result = new NewApiResponse<>();
result.setSuccess(json.getBool("success"));
result.setMessage(json.getStr("message"));
result.setHttpResponse(response);
if (json.containsKey("data") && !json.isNull("data")) {
Object data = json.get("data");
if (data instanceof Map) {
result.setData((Map<String, Object>) data);
} else {
result.setData((Map<String, Object>) JSONUtil.parseObj(data.toString()));
}
}
return result;
} catch (Exception e) {
log.error("[NewApiClient.getUserInfo] 请求异常", e);
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("请求异常: " + e.getMessage())
.build();
}
}
@SuppressWarnings("unchecked")
private <T> NewApiResponse<T> parseResponse(HttpResponse response, Class<T> dataClass) {
try {
......
package com.luhu.computility.module.member.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* New API 配置(会员模块专用)
*/
@Component("memberNewApiProperties")
@ConfigurationProperties(prefix = "computility.new-api")
public class MemberNewApiProperties {
private String baseUrl = "http://localhost:3000";
/**
* 管理员访问令牌
*/
private String adminToken;
/**
* New API 用户ID(管理员)
*/
private String adminUserId = "1";
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getAdminToken() {
return adminToken;
}
public void setAdminToken(String adminToken) {
this.adminToken = adminToken;
}
public String getAdminUserId() {
return adminUserId;
}
public void setAdminUserId(String adminUserId) {
this.adminUserId = adminUserId;
}
}
package com.luhu.computility.module.member.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* New API 通用响应(会员模块专用)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberNewApiResponse<T> {
private Boolean success;
private String message;
private T data;
}
......@@ -70,6 +70,15 @@ public class MemberRechargeWpgjPayController {
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) {
// 支付成功:更新充值状态为已支付
memberRechargeService.updateRechargeStatus(Long.parseLong(merOrderId), 1);
// 3. 执行充值兑换(调用 new API 增加额度)
try {
boolean redeemSuccess = memberRechargeService.executeRecharge(Long.parseLong(merOrderId));
log.info("[notifyWpgjPay][Member] 充值兑换结果: {}, 商户订单号: {}", redeemSuccess, merOrderId);
} catch (Exception e) {
log.error("[notifyWpgjPay][Member] 充值兑换异常,商户订单号: {}", merOrderId, e);
}
log.info("[notifyWpgjPay][Member] 支付成功处理完成,商户订单号: {}", merOrderId);
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) {
// 支付失败/关闭:更新充值状态为支付失败
......
......@@ -3,6 +3,7 @@ package com.luhu.computility.module.member.controller.admin.recharge.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 充值记录响应 VO")
......@@ -57,4 +58,16 @@ public class MemberRechargeRespVO {
@Schema(description = "展示模式")
private String displayMode;
@Schema(description = "兑换码")
private String redemptionCode;
@Schema(description = "兑换额度")
private Long redemptionQuota;
@Schema(description = "是否已兑换(0-未兑换,1-已兑换,-1-兑换失败)")
private Integer redeemed;
@Schema(description = "兑换状态名称")
private String redeemedName;
}
......@@ -71,4 +71,19 @@ public class MemberRechargeDO extends BaseDO {
*/
private String remark;
/**
* 兑换码
*/
private String redemptionCode;
/**
* 兑换额度(new api quota)
*/
private Long redemptionQuota;
/**
* 是否已兑换(0-未兑换,1-已兑换,-1-兑换失败)
*/
private Integer redeemed;
}
......@@ -48,4 +48,10 @@ public interface MemberRechargeService {
*/
PageResult<MemberRechargeRespVO> getRechargePage(MemberRechargePageReqVO reqVO);
/**
* 执行充值兑换(支付成功后调用)
* @return 是否兑换成功
*/
boolean executeRecharge(Long rechargeId);
}
package com.luhu.computility.module.member.service.recharge;
import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.module.member.config.MemberNewApiClient;
import com.luhu.computility.module.member.config.MemberNewApiProperties;
import com.luhu.computility.module.member.config.MemberNewApiResponse;
import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRechargePageReqVO;
import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRechargeRespVO;
import com.luhu.computility.module.member.dal.dataobject.recharge.MemberRechargeDO;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import com.luhu.computility.module.member.dal.mysql.recharge.MemberRechargeMapper;
import com.luhu.computility.module.member.service.user.MemberUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
......@@ -21,6 +26,9 @@ import java.util.stream.Collectors;
public class MemberRechargeServiceImpl implements MemberRechargeService {
private final MemberRechargeMapper rechargeMapper;
private final MemberUserService memberUserService;
private final MemberNewApiClient memberNewApiClient;
private final MemberNewApiProperties newApiProperties;
private static final Map<String, String> PAY_CHANNEL_NAMES = Map.of(
"wx", "微信支付",
......@@ -35,6 +43,12 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
2, "已退款"
);
private static final Map<Integer, String> REDEEM_STATUS_NAMES = Map.of(
-1, "兑换失败",
0, "未兑换",
1, "已兑换"
);
/**
* 业务类型:3-会员充值(用于生成订单号)
*/
......@@ -56,6 +70,7 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
.payStatus(0)
.transactionId(transactionId)
.callbackStatus(0)
.redeemed(0)
.build();
rechargeMapper.insert(recharge);
log.info("[创建充值记录] userId={}, amount={}, transactionId={}, orderId={}", userId, amount, transactionId, orderId);
......@@ -104,6 +119,121 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
return new PageResult<>(voList, pageResult.getTotal());
}
/**
* 执行充值兑换
* 流程:支付成功后调用
* 1. 用管理员权限创建兑换码
* 2. 用用户权限使用兑换码
*
* @param rechargeId 充值记录ID
* @return 是否兑换成功
*/
public boolean executeRecharge(Long rechargeId) {
log.info("[executeRecharge] 开始执行充值兑换,rechargeId={}", rechargeId);
// 1. 获取充值记录
MemberRechargeDO recharge = rechargeMapper.selectById(rechargeId);
if (recharge == null) {
log.error("[executeRecharge] 充值记录不存在,rechargeId={}", rechargeId);
return false;
}
// 2. 检查是否已兑换
if (recharge.getRedeemed() != null && recharge.getRedeemed() == 1) {
log.warn("[executeRecharge] 该充值记录已兑换过,rechargeId={}", rechargeId);
return true;
}
// 3. 获取用户信息
MemberUserDO user = memberUserService.getUser(recharge.getUserId());
if (user == null) {
log.error("[executeRecharge] 用户不存在,userId={}", recharge.getUserId());
updateRedeemedStatus(rechargeId, -1, "用户不存在");
return false;
}
// 4. 检查用户是否有 new api 信息
if (user.getNewapiAccessToken() == null || user.getNewapiUserId() == null) {
log.error("[executeRecharge] 用户未绑定new api账号,userId={}", recharge.getUserId());
updateRedeemedStatus(rechargeId, -1, "用户未绑定new api账号");
return false;
}
// 5. 转换额度:金额 -> quota
long quota = memberNewApiClient.convertToQuota(recharge.getAmount());
log.info("[executeRecharge] 转换额度:amount={} -> quota={}", recharge.getAmount(), quota);
// 6. 用管理员权限创建兑换码
String adminToken = newApiProperties.getAdminToken();
MemberNewApiResponse<String> createResp = memberNewApiClient.createRedemption(
adminToken, quota, recharge.getAmount(), 1, "充值-" + rechargeId);
if (createResp.getSuccess() == null || !createResp.getSuccess()) {
log.error("[executeRecharge] 创建兑换码失败:{}", createResp.getMessage());
updateRedeemedStatus(rechargeId, -1, "创建兑换码失败: " + createResp.getMessage());
return false;
}
// 7. 解析兑换码
String redemptionCode = createResp.getData();
if (redemptionCode == null || redemptionCode.isEmpty()) {
log.error("[executeRecharge] 兑换码为空");
updateRedeemedStatus(rechargeId, -1, "兑换码为空");
return false;
}
log.info("[executeRecharge] 创建兑换码成功,code={}", redemptionCode);
// 8. 用用户权限使用兑换码
String userAccessToken = user.getNewapiAccessToken();
Integer newapiUserId = Integer.parseInt(user.getNewapiUserId().toString());
MemberNewApiResponse<Long> useResp = memberNewApiClient.useRedemption(userAccessToken, newapiUserId, redemptionCode);
if (useResp.getSuccess() == null || !useResp.getSuccess()) {
log.error("[executeRecharge] 使用兑换码失败:{}", useResp.getMessage());
// 保存兑换码和失败状态
updateRedeemedWithCode(rechargeId, -1, redemptionCode, quota, "使用兑换码失败: " + useResp.getMessage());
return false;
}
log.info("[executeRecharge] 使用兑换码成功,增加额度:{}", useResp.getData());
// 9. 更新兑换状态
updateRedeemedWithCode(rechargeId, 1, redemptionCode, quota, null);
log.info("[executeRecharge] 充值兑换完成,rechargeId={}, quota={}", rechargeId, quota);
return true;
}
/**
* 更新兑换状态(不包含兑换码)
*/
private void updateRedeemedStatus(Long id, Integer redeemed, String remark) {
MemberRechargeDO updateDO = MemberRechargeDO.builder().build();
updateDO.setId(id);
updateDO.setRedeemed(redeemed);
if (remark != null) {
updateDO.setRemark(remark);
}
rechargeMapper.updateById(updateDO);
}
/**
* 更新兑换状态(包含兑换码)
*/
private void updateRedeemedWithCode(Long id, Integer redeemed, String code, Long quota, String remark) {
MemberRechargeDO updateDO = MemberRechargeDO.builder().build();
updateDO.setId(id);
updateDO.setRedeemed(redeemed);
updateDO.setRedemptionCode(code);
updateDO.setRedemptionQuota(quota);
if (remark != null) {
updateDO.setRemark(remark);
}
rechargeMapper.updateById(updateDO);
}
private MemberRechargeRespVO convertToRespVO(MemberRechargeDO recharge) {
MemberRechargeRespVO vo = new MemberRechargeRespVO();
vo.setId(recharge.getId());
......@@ -120,6 +250,12 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
vo.setRemark(recharge.getRemark());
vo.setCreateTime(recharge.getCreateTime());
vo.setUpdateTime(recharge.getUpdateTime());
vo.setRedeemed(recharge.getRedeemed());
if (recharge.getRedeemed() != null) {
vo.setRedeemedName(REDEEM_STATUS_NAMES.getOrDefault(recharge.getRedeemed(), "未知"));
}
vo.setRedemptionCode(recharge.getRedemptionCode());
vo.setRedemptionQuota(recharge.getRedemptionQuota());
return vo;
}
......
......@@ -213,6 +213,8 @@ wx:
computility:
new-api:
base-url: http://172.25.164.0:3000
admin-token: zpdZ5iRZmEnaENEPvZbwaaDwMSUl1JE=
admin-user-id: 1
captcha:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
security:
......@@ -245,7 +247,7 @@ computility:
-----END PRIVATE KEY-----
notify-url-compute: https://ric.admin.lijinqi.com/admin-api/compute/wpgj/notify # 算力资源模块WPGJ回调地址
notify-url-api: https://ric.admin.lijinqi.com/admin-api/apihub/wpgj/notify # APIHub模块WPGJ回调地址
notify-url-member: https://ric.admin.lijinqi.com/admin-api/member/wpgj/notify # 会员充值模块WPGJ回调地址
notify-url-member: https://favoring-bobtail-jugular.ngrok-free.dev/admin-api/member/wpgj/notify # 会员充值模块WPGJ回调地址
access-log: # 访问日志的配置项
enable: false
demo: false # 关闭演示模式
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment