Initial commit

This commit is contained in:
2026-04-23 16:58:11 +08:00
commit 267eba1eca
2582 changed files with 273338 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>skins-service</artifactId>
<version>4.8.2</version>
</parent>
<artifactId>service-thirdparty</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
<!-- 后台管理模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>service-admin</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<!-- 如果是 Spring Boot 项目,通常不需要写 version由父工程管理 -->
<!-- 如果报错找不到版本,可以显式指定一个与 Spring Boot 3.x 匹配的版本,例如 2.15.x -->
<!-- <version>2.15.2</version> -->
</dependency>
<!-- 链接转二维码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
<!-- 支付宝SDK-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.161.ALL</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,26 @@
package com.ruoyi.thirdparty.MaYi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "mayi")
public class MYConfig {
private String apiKey;
private String gateway;
private String memberid;
private String notifyBaseUrl;
private String callBackUrl;
// (银行)通道编码
private String payBankCode;
// 下单接口
public static final String ApiPayAddOrder = "/Pay_AddOrder";
// 查询订单接口
public static final String ApiQueryTrans = "/Pay_Trade_query.html";
}

View File

@@ -0,0 +1,90 @@
package com.ruoyi.thirdparty.MaYi.controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UpdateUserCache;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.dto.mayi.PayNotifyRequest;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.thirdparty.MaYi.config.MYConfig;
import com.ruoyi.thirdparty.MaYi.service.MYService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
@Api(tags = "聚合支付")
@Slf4j
@RestController
@RequestMapping("api/mayi")
public class MYController extends BaseController {
@Autowired
private MYService myService;
@Autowired
private TtUserService userService;
// 下单
@ApiOperation("预下单")
@PostMapping("/ApiAddTrans")
@UpdateUserCache
public R ApiAddTrans(HttpServletRequest request, @RequestBody @Validated CreateOrderParam param) {
R checkLogin = checkLogin();
if (!checkLogin.getCode().equals(200)) return checkLogin;
Integer userId = ((Long) checkLogin.getData()).intValue();
TtUser user = userService.getById(userId);
// 是否实名认证0未认证1已认证'
if (user.getIsRealCheck().equals("0")) {
return R.fail("请实名认证后再进行充值!");
}
return myService.ApiAddTrans(param, user, request);
}
// 支付回调
@ApiOperation("支付回调")
@PostMapping("/notify")
public String addTransNotify(@RequestBody PayNotifyRequest data) {
log.info("聚合支付支付回调 {}", JSONUtil.toJsonStr(data));
return myService.payNotify(data);
}
// 查询订单接口
// @PostMapping("/ApiQueryTrans")
// public String ApiQueryTrans(HttpServletRequest request, Map<String, String> param) {
// return "success";
// }
//
// // 查询余额接口
// @PostMapping("/ApiQueryBalancePHP")
// public String ApiQueryBalancePHP(HttpServletRequest request, Map<String, String> param) {
// return "success";
// }
public R checkLogin() {
Long userId;
try {
userId = getUserId();
if (ObjectUtil.isEmpty(userId)) return R.fail(401,"登录过期,请重新登录。");
return R.ok(userId);
} catch (Exception e) {
return R.fail("登录过期,请重新登录。");
}
}
}

View File

@@ -0,0 +1,424 @@
package com.ruoyi.thirdparty.MaYi.service.Impl;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.admin.mapper.*;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.common.constant.PayType;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.common.constant.TtAccountRecordType;
import com.ruoyi.domain.dto.mayi.ApiPayAddOrderResponse;
import com.ruoyi.domain.dto.mayi.PayNotifyRequest;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtPromotionLevel;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.TtUserBlendErcash;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.MaYi.config.MYConfig;
import com.ruoyi.thirdparty.MaYi.service.MYService;
import com.ruoyi.thirdparty.MaYi.utils.signUtil;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Service
public class MYServiceImpl implements MYService {
@Autowired
private MYConfig myConfig;
@Autowired
private TtOrderMapper orderMapper;
@Autowired
private Executor customThreadPoolExecutor;
@Autowired
private TtUserService userService;
@Autowired
private TtUserMapper userMapper;
@Autowired
private TtRechargeProdMapper rechargeListMapper;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private TtRechargeProdMapper rechargeProdMapper;
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private ISysConfigService configService;
@Autowired
private TtPromotionLevelMapper ttPromotionLevelMapper;
@Autowired
private TtFirstRechargeMapper ttFirstRechargeMapper;
@Override
public R ApiAddTrans(@Validated CreateOrderParam param, TtUser user, HttpServletRequest request) {
// 查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, param.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) return R.fail("不存在的商品。");
if (goods.getPrice().compareTo(param.getGoodsPrice()) != 0) return R.fail("商品价格不一致。");
// 总价值
BigDecimal totalAmount = param.getGoodsPrice().multiply(new BigDecimal(param.getGoodsNum()));
// 构建完整参数
Map<String, String> map = buildBaseParam(new HashMap<>(), request);
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
String orderId = String.valueOf(snowflake.nextId());
map.put("pay_orderid", orderId);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String tranDateTime = dateFormat.format(new Date());
map.put("pay_applydate", tranDateTime);
map.put("pay_bankcode", myConfig.getPayBankCode());
// 回调地址
map.put("pay_notifyurl", myConfig.getNotifyBaseUrl());
// 同步跳转
map.put("pay_callbackurl", myConfig.getCallBackUrl() + "/api/mayi/notify");
map.put("pay_amount", totalAmount.toString());
// 签名
String sign = signUtil.getSign(map, myConfig.getApiKey());
map.put("pay_md5sign", sign);
map.put("pay_clientip", IpUtils.getIpAddr());
map.put("pay_productname", goods.getName());
map.put("type", "json");
// System.out.println(JSONUtil.toJsonStr(map));
// 发送请求
HttpRequest post = HttpUtil.createPost(myConfig.getGateway() + MYConfig.ApiPayAddOrder);
post.header("Content-Type","application/x-www-form-urlencoded");
post.formStr(map);
HttpResponse res = post.execute();
// 解析响应
ApiPayAddOrderResponse resBody = JSONUtil.toBean(res.body(), ApiPayAddOrderResponse.class);
if (!resBody.getStatus().equals("1")) {
return R.fail(resBody);
}
// 创建订单
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setOutTradeNo(resBody.getPay_orderid());
order.setUserId(user.getUserId());
order.setType(PayType.JU_HE_ZHI_FU.getCode());
order.setGoodsId(param.getGoodsId());
order.setGoodsPrice(param.getGoodsPrice());
order.setGoodsNum(param.getGoodsNum());
order.setTotalAmount(totalAmount);
order.setSign(sign);
order.setStatus(PayOrderStatus.NO_PAY.getCode());
order.setPayUrl(resBody.getPayUrl());
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
orderMapper.insert(order);
return R.ok(order);
}
@Override
public String payNotify(PayNotifyRequest data) {
TtOrder order = null;
// TODO: 2024/4/12 最好再查询一下第三方平台的订单信息
try {
// 查询订单信息
order = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getOrderId, data.getOrderid())
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.one();
if (ObjectUtil.isEmpty(order)) {
log.warn("支付回调异常,不存在的有效订单。");
return "fail";
}
// 查询用户信息
TtUser user = new LambdaQueryChainWrapper<>(userMapper)
.eq(TtUser::getUserId, order.getUserId())
.eq(TtUser::getDelFlag, 0)
.one();
if (ObjectUtil.isEmpty(user)) {
log.warn("支付回调异常,不存在的有效用户。");
return "fail";
}
if (order.getStatus().equals(PayOrderStatus.PAY_COMPLE.getCode()) || order.getStatus().equals(PayOrderStatus.PAY_YET.getCode())) {
log.warn("重复的回调!该订单已完成。");
return "success";
}
// 查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeListMapper)
.eq(TtRechargeProd::getId, order.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) {
log.warn("支付回调异常,不存在的商品。");
return "fail";
}
// 账户结算
if (ObjectUtil.isNull(goods.getProductA())) goods.setProductA(BigDecimal.ZERO);
if (ObjectUtil.isNull(goods.getProductC())) goods.setProductC(BigDecimal.ZERO);
payNotifyAccounting(order, user, goods, order.getGoodsNum());
// 首充赠送
firstChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广等级充值赠送
promotionLevelChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广充值返佣
promotionChargeCommission(user, goods, order.getGoodsNum());
// 更新订单
new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
// 发送充值成功通知
rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(user.getUserId().toString(), order.getGoodsPrice());
return "success";
} catch (Exception e) {
e.printStackTrace();
log.warn("聚合支付,回调异常");
if (ObjectUtil.isNotNull(order)) {
new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.CALL_BACK_ERRO.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
}
return "fail";
}
}
@Override
public R ApiQueryTrans(Map<String, String> param, TtUser user) {
return null;
}
@Override
public R ApiPropayTrans(Map<String, String> param, TtUser user) {
return null;
}
@Override
public R ApiQueryBalancePHP(Map<String, String> param, TtUser user) {
return null;
}
// 构建基本请求信息
private Map<String, String> buildBaseParam(Map<String, String> map, HttpServletRequest request) {
map.put("pay_memberid", myConfig.getMemberid());
return map;
}
// 账户结算
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber) {
// 加钱
// BigDecimal totalAmount = order.getTotalAmount();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal totalCredits = goods.getProductC().multiply(new BigDecimal(goodsNumber));
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, user.getUserId())
.setSql("account_amount = account_amount + " + totalAmount.toString()
+ ",account_credits = account_credits + " + totalCredits.toString());
userService.update(userUpdate);
user = userService.getById(user.getUserId());
// 综合消费日志
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(user.getUserId())
.amount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? totalAmount : null)
.finalAmount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountAmount().add(totalAmount) : null)
.credits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? totalCredits : null)
.finalCredits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountCredits().add(totalCredits) : null)
.total(totalAmount.add(totalCredits)) // 收支合计
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.RECHARGE.getCode())
.remark(TtAccountRecordSource.RECHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.updateTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
// 更新VIP等级
userService.updateUserVIPLevel(user.getUserId());
}
/**
* 首充赠送
*/
public void firstChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.判断是否为首充
LambdaQueryWrapper<TtUserBlendErcash> wrapper = new LambdaQueryWrapper<>();
wrapper
.eq(TtUserBlendErcash::getSource, 1)
.eq(TtUserBlendErcash::getUserId, ttUser.getUserId());
List<TtUserBlendErcash> ttUserBlendErcashes = userBlendErcashMapper.selectList(wrapper);
if (ttUserBlendErcashes.size() > 0) {
return;
}
// 2.加钱
// 充值金额
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
// 查询赠送比例
BigDecimal firstChargeAmountRatio = ttFirstRechargeMapper.selectRatioByMinAmount(totalAmount);
if (Objects.isNull(firstChargeAmountRatio)) {
return;
}
// 计算赠送金额
BigDecimal giftAmount = totalAmount.multiply(firstChargeAmountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.FIRST_CHARGE.getCode())
.remark(TtAccountRecordSource.FIRST_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广等级充值赠送
*/
public void promotionLevelChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.查询用户推广等级对应的赠送比例
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
// 2.加钱
BigDecimal amountRatio = ttPromotionLevel.getRechargeGiftRatio();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getCode())
.remark(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广充值返佣
*/
private void promotionChargeCommission(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
if (Objects.isNull(ttUser.getParentId())) {
return;
}
// 查询用户推广等级信息
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
// 给上级加钱
BigDecimal amountRatio = ttPromotionLevel.getCommissions();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getParentId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 记录
ttUser = userService.getById(ttUser.getParentId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getCode())
.remark(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.thirdparty.MaYi.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.dto.mayi.PayNotifyRequest;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
public interface MYService {
R ApiAddTrans(CreateOrderParam param, TtUser user, HttpServletRequest request);
R ApiQueryTrans(Map<String,String> param, TtUser user);
R ApiPropayTrans(Map<String,String> param, TtUser user);
R ApiQueryBalancePHP(Map<String,String> param, TtUser user);
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber);
String payNotify(PayNotifyRequest data);
/**
* <20>׳<EFBFBD><D7B3><EFBFBD><EFBFBD><EFBFBD>
*/
public void firstChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber);
/**
* <20>ƹ<EFBFBD>ȼ<EFBFBD><C8BC><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD>
*/
public void promotionLevelChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber);
}

View File

@@ -0,0 +1,47 @@
package com.ruoyi.thirdparty.MaYi.utils;
// zy返回值
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ZYHTTPRes {
public enum resCode{
success("0000"),
DOING("0001");
private String code;
resCode(String code){
this.code = code;
}
public String getCode(){
return this.code;
}
}
private String respCode;
//返回码说明
private String respDesc;
//返回码
private String payCode;
private String payDesc;
private String amt;
private String tranTime;
private String tranType;
//扩展信息
private String msgExt;
private String payUrl;
// 交易流水号
private String merReqNo;
//服务端响应流水号
private String serverRspNo;
private String sign;
}

View File

@@ -0,0 +1,39 @@
package com.ruoyi.thirdparty.MaYi.utils;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import java.security.MessageDigest;
import java.util.*;
public class signUtil {
public static String getSign(Map<String, String> map, String key) {
// 排序
List<Map.Entry<String, String>> entryList = new ArrayList<>(map.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
String str = "";
for (int i = 0; i < entryList.size(); i++) {
// if (i == entryList.size()-1) {
// str = str + entryList.get(i).getKey() + "=" + entryList.get(i).getValue();
// continue;
// }
str = str + entryList.get(i).getKey() + "=" + entryList.get(i).getValue() + "&";
}
str = str + "key=" +key;
// MD5加密
Digester md5 = new Digester(DigestAlgorithm.MD5);
return md5.digestHex(str).toUpperCase();
}
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.thirdparty.alipay.config;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@EnableConfigurationProperties(value = AliProperties.class)
public class AliCilent {
@Bean
@Qualifier("RealNameAuthentication")
public AlipayClient RealNameAuthentication(AliProperties aliProperties) {
List<MerchantConfig> merchants = aliProperties.getMerchants();
if (merchants.isEmpty()) throw new IllegalArgumentException("No merchant configuration provided");
MerchantConfig merchantConfig = merchants.get(1);
return new DefaultAlipayClient(
aliProperties.getServerUrl(),
merchantConfig.getAppId(),
merchantConfig.getPrivateKey(),
aliProperties.getFormat(),
aliProperties.getCharset(),
merchantConfig.getAlipayPublicKey(),
aliProperties.getSignType()
);
}
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.thirdparty.alipay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "alipay")
public class AliProperties {
private String serverUrl;
private String format;
private String charset;
private String signType;
private List<MerchantConfig> merchants;
private String notifyUrl;
private String realNameAuthenticationReturnUrl;
private String authenticationHost;
private String authenticationPath;
private String authenticationAppcode;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.alipay.config;
import lombok.Data;
@Data
public class MerchantConfig {
private String appId;
private String privateKey;
private String alipayPublicKey;
}

View File

@@ -0,0 +1,55 @@
package com.ruoyi.thirdparty.alipay.controller;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UserPermission;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.thirdparty.alipay.service.AliPayService;
import com.ruoyi.thirdparty.wechat.service.ApiTokenService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Api(tags = "支付宝")
@RestController
@RequestMapping("/api/alipay")
public class AliPayController {
private final ApiTokenService tokenService;
private final AliPayService aliPayService;
private final TtUserService ttUserService;
public AliPayController(ApiTokenService apiTokenService,
AliPayService aliPayService,
TtUserService ttUserService) {
this.tokenService = apiTokenService;
this.aliPayService = aliPayService;
this.ttUserService = ttUserService;
}
@ApiOperation("支付")
@UserPermission
@PostMapping(value = "/trade/page/pay")
public R pay(@RequestBody @Validated CreateOrderParam param) {
Long userId = SecurityUtils.getUserId();
TtUser ttUser = ttUserService.getById(userId);
// 是否实名认证0未认证 1已认证
if ("0".equals(ttUser.getIsRealCheck())) {
return R.fail("未实名认证");
}
String ip = IpUtils.getIpAddr();
return aliPayService.pay(param, ttUser, ip);
}
@PostMapping(value = "/callBack")
public String callBack(@RequestParam Map<String, String> params) {
return aliPayService.callBack(params);
}
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.alipay.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import java.util.Map;
public interface AliPayService {
R pay(CreateOrderParam param, TtUser ttUser, String ip);
String callBack(Map<String, String> params);
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.alipay.service;
public interface RealNameAuthenticationService {
String authInitialize(String realName, String idNum);
String startCertify(String certifyId);
String queryCertifyResult(String certifyId);
String authentication(String realName, String idNum);
String authentication2(String realName, String idNum, String phoneNum);
}

View File

@@ -0,0 +1,389 @@
package com.ruoyi.thirdparty.alipay.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.admin.mapper.*;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.common.constant.PayType;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.common.constant.TtAccountRecordType;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtPromotionLevel;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.TtUserBlendErcash;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.alipay.config.AliProperties;
import com.ruoyi.thirdparty.alipay.service.AliPayService;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {
@Autowired
private TtRechargeProdMapper rechargeProdMapper;
@Autowired
private AliProperties aliProperties;
@Autowired
private TtOrderMapper orderMapper;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private TtUserService userService;
@Autowired
private TtUserMapper userMapper;
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private TtRechargeProdMapper rechargeListMapper;
@Autowired
private ISysConfigService configService;
@Autowired
private TtPromotionLevelMapper ttPromotionLevelMapper;
@Autowired
private TtFirstRechargeMapper ttFirstRechargeMapper;
@Override
public R pay(CreateOrderParam param, TtUser ttUser, String ip) {
// 查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, param.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) {
return R.fail("不存在的商品。");
}
if (goods.getPrice().compareTo(param.getGoodsPrice()) != 0) {
return R.fail("商品价格不一致。");
}
// 计算总价值
BigDecimal totalAmount = param.getGoodsPrice().multiply(new BigDecimal(param.getGoodsNum()));
// 构建支付
String serverUrl = aliProperties.getServerUrl();
String appId = aliProperties.getMerchants().get(0).getAppId();
String privateKey = aliProperties.getMerchants().get(0).getPrivateKey();
String format = aliProperties.getFormat();
String charset = aliProperties.getCharset();
String alipayPublicKey = aliProperties.getMerchants().get(0).getAlipayPublicKey();
String signType = aliProperties.getSignType();
String notifyUrl = aliProperties.getNotifyUrl();
// 初始化SDK
AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType);
// 构造请求参数以调用接口
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
// 设置通知地址
request.setNotifyUrl(notifyUrl);
// 设置订单标题
model.setSubject(goods.getName());
// 设置商户订单号
String orderId = String.valueOf(IdUtil.getSnowflake(1, 1).nextId());
model.setOutTradeNo(orderId);
// 设置订单总金额
model.setTotalAmount(totalAmount.toString());
request.setBizModel(model);
try {
AlipayTradePrecreateResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
// 创建订单
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setOutTradeNo(response.getOutTradeNo());
order.setUserId(ttUser.getUserId());
order.setType(PayType.ZFB.getCode());
order.setGoodsId(param.getGoodsId());
order.setGoodsPrice(param.getGoodsPrice());
order.setGoodsNum(param.getGoodsNum());
order.setTotalAmount(totalAmount);
order.setStatus(PayOrderStatus.NO_PAY.getCode());
order.setCreateTime(new Date());
orderMapper.insert(order);
JSONObject jsonObject = JSON.parseObject(response.getBody());
return R.ok(jsonObject);
}
} catch (AlipayApiException e) {
e.printStackTrace();
}
return R.fail("支付失败");
}
@Override
public String callBack(Map<String, String> params) {
log.info("支付成功通知");
String result = "failure";
String alipayPublicKey = aliProperties.getMerchants().get(0).getAlipayPublicKey();
try {
// 异步通知校验
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey,
AlipayConstants.CHARSET_UTF8,
AlipayConstants.SIGN_TYPE_RSA2);
if (!signVerified) {
log.error("支付成功,异步通知验签失败!");
return result;
}
// 进行二次校验
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus)) {
log.error("交易失败");
return result;
}
// 处理业务
// 查询订单信息
String outTradeNo = params.get("out_trade_no");
TtOrder order = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getOutTradeNo, outTradeNo)
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.one();
// 防止重复通知
if (!PayOrderStatus.NO_PAY.getCode().equals(order.getStatus())) {
return result;
}
// 查询用户信息
TtUser user = new LambdaQueryChainWrapper<>(userMapper)
.eq(TtUser::getUserId, order.getUserId())
.eq(TtUser::getDelFlag, 0)
.one();
// 查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeListMapper)
.eq(TtRechargeProd::getId, order.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
// 账户结算
if (ObjectUtil.isNull(goods.getProductA())) goods.setProductA(BigDecimal.ZERO);
if (ObjectUtil.isNull(goods.getProductC())) goods.setProductC(BigDecimal.ZERO);
payNotifyAccounting(order, user, goods, order.getGoodsNum());
try {
// 首充赠送
firstChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广等级充值赠送
promotionLevelChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广充值返佣
promotionChargeCommission(user, goods, order.getGoodsNum());
} catch (Exception e) {
e.printStackTrace();
}
// 更新订单
boolean update = new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
if (update) {
result = "success";
}
// 发送充值成功通知
rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(order.getUserId().toString(), order.getGoodsPrice());
} catch (AlipayApiException e) {
e.printStackTrace();
}
return result;
}
// 账户结算
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber) {
// 加钱
// BigDecimal totalAmount = order.getTotalAmount();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal totalCredits = goods.getProductC().multiply(new BigDecimal(goodsNumber));
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, user.getUserId())
.setSql("account_amount = account_amount + " + totalAmount.toString()
+ ",account_credits = account_credits + " + totalCredits.toString());
userService.update(userUpdate);
user = userService.getById(user.getUserId());
// 综合消费日志
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(user.getUserId())
.amount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? totalAmount : null)
.finalAmount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountAmount().add(totalAmount) : null)
.credits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? totalCredits : null)
.finalCredits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountCredits().add(totalCredits) : null)
.total(totalAmount.add(totalCredits)) // 收支合计
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.RECHARGE.getCode())
.remark(TtAccountRecordSource.RECHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.updateTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
// 更新VIP等级
userService.updateUserVIPLevel(user.getUserId());
}
/**
* 首充赠送
*/
private void firstChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.判断是否为首充
LambdaQueryWrapper<TtUserBlendErcash> wrapper = new LambdaQueryWrapper<>();
wrapper
.eq(TtUserBlendErcash::getSource, 1)
.eq(TtUserBlendErcash::getUserId, ttUser.getUserId());
List<TtUserBlendErcash> ttUserBlendErcashes = userBlendErcashMapper.selectList(wrapper);
if (ttUserBlendErcashes.size() > 0) {
return;
}
// 2.加钱
// 充值金额
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
// 查询赠送比例
BigDecimal firstChargeAmountRatio = ttFirstRechargeMapper.selectRatioByMinAmount(totalAmount);
if (Objects.isNull(firstChargeAmountRatio)) {
return;
}
// 计算赠送金额
BigDecimal giftAmount = totalAmount.multiply(firstChargeAmountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.FIRST_CHARGE.getCode())
.remark(TtAccountRecordSource.FIRST_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广等级充值赠送
*/
private void promotionLevelChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.查询用户推广等级对应的赠送比例
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
if (Objects.isNull(ttPromotionLevel))
return;
// 2.加钱
BigDecimal amountRatio = ttPromotionLevel.getRechargeGiftRatio();
if (Objects.isNull(amountRatio))
return;
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getCode())
.remark(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广充值返佣
*/
private void promotionChargeCommission(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
if (Objects.isNull(ttUser.getParentId())) {
return;
}
// 查询用户推广等级信息
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
// 给上级加钱
BigDecimal amountRatio = ttPromotionLevel.getCommissions();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getParentId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 记录
ttUser = userService.getById(ttUser.getParentId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getCode())
.remark(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
}

View File

@@ -0,0 +1,219 @@
package com.ruoyi.thirdparty.alipay.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayUserCertifyOpenCertifyRequest;
import com.alipay.api.request.AlipayUserCertifyOpenInitializeRequest;
import com.alipay.api.request.AlipayUserCertifyOpenQueryRequest;
import com.alipay.api.response.AlipayUserCertifyOpenCertifyResponse;
import com.alipay.api.response.AlipayUserCertifyOpenInitializeResponse;
import com.alipay.api.response.AlipayUserCertifyOpenQueryResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.thirdparty.alipay.config.AliProperties;
import com.ruoyi.thirdparty.alipay.service.RealNameAuthenticationService;
import com.ruoyi.thirdparty.alipay.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Service
@Slf4j
public class RealNameAuthenticationServiceImpl implements RealNameAuthenticationService {
@Qualifier("RealNameAuthentication")
private final AlipayClient RealNameAuthentication;
private final AliProperties aliProperties;
@Autowired
private RestTemplate restTemplate;
public RealNameAuthenticationServiceImpl(AlipayClient realNameAuthentication,
AliProperties aliProperties) {
RealNameAuthentication = realNameAuthentication;
this.aliProperties = aliProperties;
}
@Override
public String authInitialize(String realName, String idNum) {
AlipayUserCertifyOpenInitializeRequest request = new AlipayUserCertifyOpenInitializeRequest();
JSONObject identityObj = new JSONObject();
identityObj.put("identity_type", "CERT_INFO");
identityObj.put("cert_type", "IDENTITY_CARD");
identityObj.put("cert_name", realName);
identityObj.put("cert_no", idNum);
JSONObject merchantConfigObj = new JSONObject();
try {
merchantConfigObj.put("return_url", aliProperties.getRealNameAuthenticationReturnUrl());
JSONObject bizContentObj = new JSONObject();
bizContentObj.put("outer_order_no", IdUtil.simpleUUID());
bizContentObj.put("biz_code", "FACE");
bizContentObj.put("identity_param", identityObj);
bizContentObj.put("merchant_config", merchantConfigObj);
request.setBizContent(bizContentObj.toString());
AlipayUserCertifyOpenInitializeResponse response = RealNameAuthentication.execute(request);
if (response.isSuccess()) {
return response.getCertifyId();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public String startCertify(String certifyId) {
AlipayUserCertifyOpenCertifyRequest request = new AlipayUserCertifyOpenCertifyRequest();
JSONObject bizContentObj = new JSONObject();
bizContentObj.put("certify_id", certifyId);
request.setBizContent(bizContentObj.toString());
try {
AlipayUserCertifyOpenCertifyResponse response = RealNameAuthentication.pageExecute(request, "GET");
if (response.isSuccess()) {
return "alipays://platformapi/startapp?appId=20000067&url=" + URLEncoder.encode(response.getBody(), "UTF-8");
} else {
return null;
}
} catch (AlipayApiException | UnsupportedEncodingException e) {
return null;
}
}
@Override
public String queryCertifyResult(String certifyId) {
AlipayUserCertifyOpenQueryRequest request = new AlipayUserCertifyOpenQueryRequest();
JSONObject bizContentObj = new JSONObject();
bizContentObj.put("certify_id", certifyId);
request.setBizContent(bizContentObj.toString());
try {
AlipayUserCertifyOpenQueryResponse response = RealNameAuthentication.execute(request);
if (StrUtil.equals(response.getCode(), "40004")) {
return response.getSubMsg();
}
if (response.isSuccess()) {
String body = response.getBody();
log.info("返回的结构体:{}", body);
if (StringUtils.isNotBlank(body)) {
JSONObject jsonObject = JSONObject.parseObject(body);
JSONObject queryResponse = jsonObject.getJSONObject("alipay_user_certify_open_query_response");
if (StrUtil.equals(queryResponse.getString("code"), "10000") && StrUtil.equals(queryResponse.getString("passed"), "T")) {
return "";
} else return "认证失败,请重新认证!";
}
} else return "认证查询服务调用失败!";
} catch (AlipayApiException e) {
return "开始认证服务调用失败!";
}
return "认证失败,请重新认证!";
}
@Override
public String authentication(String realName, String idNum) {
log.info("realName is {}, idNum is {}", realName, idNum);
String method = "POST";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + aliProperties.getAuthenticationAppcode());
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("idCardNo", idNum);
bodys.put("name", realName);
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(aliProperties.getAuthenticationHost(), aliProperties.getAuthenticationPath(), method, headers, querys, bodys);
log.info("authentication response code is {}", response.getStatusLine().getStatusCode());
//获取response的body
String result = EntityUtils.toString(response.getEntity());
log.info("authentication result is {}", result);
JSONObject jsonObject = JSONObject.parseObject(result);
int code = jsonObject.getIntValue("code");
if (code == 400) {
return "身份证格式不正确!";
}
if (code != 200) {
return "认证失败,请重新认证!";
}
JSONObject data = jsonObject.getJSONObject("data");
int res = data.getIntValue("result");
if (res == 1) {
return "认证失败,请重新认证!";
}
} catch (Exception e) {
log.error("authentication is {}", e.getMessage(), e);
return "认证失败,请重新认证!";
}
return "";
}
@Override
public String authentication2(String realName, String idNum, String phoneNum) {
log.info("realName is {}, idNum is {}, phoneNum is {}", realName, idNum, phoneNum);
try {
HttpHeaders headers = new HttpHeaders();
headers.set("X-APISpace-Token", "3q25hxre8uu9rbeuqtfwf4sxmjh4dach");
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("name", realName);
map.add("idNum", idNum);
map.add("mobile", phoneNum);
// 5. 组装请求实体
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
// 6. 发送 POST 请求并获取响应
ResponseEntity<String> response = restTemplate.postForEntity("https://eolink.o.apispace.com/carriers/carriers-auth",
request, String.class);
log.info("authentication response code is {}", response.getStatusCode());
log.info("authentication result is {}", response.getBody());
JSONObject jsonObject = JSONObject.parseObject(response.getBody());
if (jsonObject == null) {
return "认证失败,请重新认证!";
}
String code = jsonObject.getString("code");
if (!Objects.equals(code, "200000")) {
return "认证失败,请重新认证!";
}
JSONObject data = jsonObject.getJSONObject("data");
String res = data.getString("result");
if (!Objects.equals(res, "01")) {
return "认证失败,请重新认证!";
}
} catch (Exception e) {
log.error("authentication is {}", e.getMessage(), e);
return "认证失败,请重新认证!";
}
return "";
}
}

View File

@@ -0,0 +1,311 @@
package com.ruoyi.thirdparty.alipay.utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtils {
/**
* get
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doGet(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
/**
* post form
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param bodys
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
Map<String, String> bodys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
/**
* Post String
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
/**
* Post stream
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
/**
* Put String
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
/**
* Put stream
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
/**
* Delete
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doDelete(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (0 < sbQuery.length()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
}
}
}
if (0 < sbQuery.length()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (KeyManagementException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.baidu.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "baidu")
public class BaiduConfig {
private String baseUrl;
private String token;
private String logidUrl;
}

View File

@@ -0,0 +1,9 @@
package com.ruoyi.thirdparty.baidu.domain.body;
import lombok.Data;
@Data
public class BaiduConversionType {
private String logidUrl;
private Integer newType;
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.thirdparty.baidu.domain.body;
import lombok.Data;
import java.util.List;
@Data
public class BaiduUploadConvertData {
private String token;
private List<BaiduConversionType> conversionTypes;
}

View File

@@ -0,0 +1,6 @@
package com.ruoyi.thirdparty.baidu.service;
public interface BaiduService {
void upload(Integer userId, Integer newType, String bdVid);
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.thirdparty.baidu.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.common.constant.TtAccountRecordType;
import com.ruoyi.domain.entity.TtUserBlendErcash;
import com.ruoyi.thirdparty.baidu.config.BaiduConfig;
import com.ruoyi.thirdparty.baidu.domain.body.BaiduConversionType;
import com.ruoyi.thirdparty.baidu.domain.body.BaiduUploadConvertData;
import com.ruoyi.thirdparty.baidu.service.BaiduService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Slf4j
@Service
public class BaiduServiceImpl implements BaiduService {
@Autowired
private BaiduConfig baiduConfig = new BaiduConfig();
@Autowired
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
//注册49
//购买10
@Override
public void upload(Integer userId, Integer newType, String bdVid) {
log.info("userId is {}, newType is {}, bdVid is {}", userId, newType, bdVid);
if (StringUtils.isBlank(bdVid) || newType == null || userId == null) {
return;
}
if (newType != 10 && newType != 49) {
return;
}
if (Objects.equals(10, newType)) {
LambdaQueryWrapper<TtUserBlendErcash> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(TtUserBlendErcash::getUserId, userId)
.eq(TtUserBlendErcash::getType, TtAccountRecordType.INPUT.getCode())
.eq(TtUserBlendErcash::getSource, TtAccountRecordSource.RECHARGE.getCode())
;
// 大于1非首次充值
List<TtUserBlendErcash> ercashes = ttUserBlendErcashMapper.selectList(queryWrapper);
if (ercashes.size() != 1) {
return;
}
}
// 构建请求头
Map<String, String> headers = new HashMap<>();
BaiduUploadConvertData data = new BaiduUploadConvertData();
data.setToken(baiduConfig.getToken());
BaiduConversionType type = new BaiduConversionType();
type.setNewType(newType);
type.setLogidUrl(baiduConfig.getLogidUrl() + bdVid);
data.setConversionTypes(Collections.singletonList(type));
String body = JSON.toJSONString(data);
String result = HttpUtils.sendPostJSONString(baiduConfig.getBaseUrl(), body, headers);
log.info("body is {}, result is {}", body, result);
}
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.thirdparty.baiweipay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "baiweipay")
public class BaiweiPayConfig {
/**
* 服务器URL
*/
private String serverUrl;
/**
* 商户ID
*/
private String pid;
/**
* 签名
*/
private String sign;
/**
* 通知URL
*/
private String notifyUrl;
/**
* 返回URL
*/
private String returnUrl;
}

View File

@@ -0,0 +1,50 @@
package com.ruoyi.thirdparty.baiweipay.controller;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UserPermission;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.thirdparty.baiweipay.service.BaiweiPayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Api(tags = "百威支付")
@Slf4j
@RestController
@RequestMapping("api/baiweipay")
public class BaiweiPayController {
@Autowired
private TtUserService ttUserService;
@Autowired
private BaiweiPayService baiweiPayService;
@ApiOperation("支付")
@UserPermission
@PostMapping(value = "/pay")
public R pay(@RequestBody @Validated CreateOrderParam param) {
Long userId = SecurityUtils.getUserId();
TtUser ttUser = ttUserService.getById(userId);
// 是否实名认证0未认证 1已认证
if ("0".equals(ttUser.getIsRealCheck())) {
return R.fail("未实名认证");
}
String ip = IpUtils.getIpAddr();
return baiweiPayService.pay(param, ttUser, ip);
}
@GetMapping("/notify")
public String notify(@RequestParam Map<String, String> params) {
return baiweiPayService.notify(params);
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.thirdparty.baiweipay.domain;
import lombok.Data;
@Data
public class BaiweiPayResponse {
private String code;
private String msg;
private String tradeNo;
private String payurl;
private String qrcode;
private String urlscheme;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.baiweipay.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import java.util.Map;
public interface BaiweiPayService {
R pay(CreateOrderParam param, TtUser ttUser, String ip);
String notify(Map<String, String> params);
}

View File

@@ -0,0 +1,419 @@
package com.ruoyi.thirdparty.baiweipay.service.impl;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.admin.mapper.*;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.common.constant.PayType;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.common.constant.TtAccountRecordType;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtPromotionLevel;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.TtUserBlendErcash;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.domain.other.TtRechargeRecord;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.baiweipay.config.BaiweiPayConfig;
import com.ruoyi.thirdparty.baiweipay.service.BaiweiPayService;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import com.ruoyi.thirdparty.baiweipay.domain.BaiweiPayResponse;
import com.ruoyi.thirdparty.baiweipay.util.BaiweiPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
@Slf4j
@Service
public class BaiweiPayServiceImpl implements BaiweiPayService {
@Autowired
private TtRechargeProdMapper rechargeProdMapper;
@Autowired
private BaiweiPayConfig baiweiPayConfig;
@Autowired
private TtOrderMapper orderMapper;
@Autowired
private TtUserMapper userMapper;
@Autowired
private TtRechargeProdMapper rechargeListMapper;
@Autowired
private TtUserService userService;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private TtRechargeRecordMapper ttRechargeRecordMapper;
@Autowired
private ISysConfigService configService;
@Autowired
private TtPromotionLevelMapper ttPromotionLevelMapper;
@Autowired
private TtFirstRechargeMapper ttFirstRechargeMapper;
@Autowired
private RestTemplate restTemplate;
@Override
public R pay(CreateOrderParam param, TtUser ttUser, String ip) {
// 1.查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, param.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) {
return R.fail("不存在的商品");
}
if (goods.getPrice().compareTo(param.getGoodsPrice()) != 0) {
return R.fail("商品价格不一致");
}
// 计算总价值
BigDecimal totalAmount = param.getGoodsPrice().multiply(new BigDecimal(param.getGoodsNum()));
// 2.构建支付参数
Map<String, String> map = new HashMap<>();
map.put("pid", baiweiPayConfig.getPid());
map.put("type", "alipay");
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
String orderId = String.valueOf(snowflake.nextId());
map.put("out_trade_no", orderId);
map.put("notify_url", baiweiPayConfig.getNotifyUrl());
map.put("return_url", baiweiPayConfig.getReturnUrl());
map.put("name", goods.getName());
map.put("money", String.valueOf(totalAmount));
map.put("clientip", ip);
String key = baiweiPayConfig.getSign();
BaiweiPayUtil signUtil = new BaiweiPayUtil(key);
String sign = signUtil.getSign(map);
map.put("sign", sign);
map.put("sign_type", "MD5");
// 3.发送请求
// HttpRequest post = HttpUtil.createPost(baiweiPayConfig.getServerUrl());
// post.header("Content-Type","application/x-www-form-urlencoded");
// post.formStr(map);
// HttpResponse res = post.execute();
// 切换请求
// 获取服务器 URL
String url = baiweiPayConfig.getServerUrl();
// 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 构建表单数据
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
formParams.add(entry.getKey(), entry.getValue());
}
// 构建请求实体
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formParams, headers);
// 发送 POST 请求
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
// 获取响应
String result = response.getBody();
// 4.解析响应
BaiweiPayResponse resBody = JSONUtil.toBean(result, BaiweiPayResponse.class);
if (!resBody.getCode().equals("1")) {
return R.fail(resBody.getMsg());
}
// 5.创建订单
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setOutTradeNo(resBody.getTradeNo());
order.setUserId(ttUser.getUserId());
order.setType(PayType.GFHT_PAY.getCode());
order.setGoodsId(param.getGoodsId());
order.setGoodsPrice(param.getGoodsPrice());
order.setGoodsNum(param.getGoodsNum());
order.setTotalAmount(totalAmount);
order.setSign(sign);
order.setStatus(PayOrderStatus.NO_PAY.getCode());
order.setCreateTime(new Date());
orderMapper.insert(order);
return R.ok(resBody);
}
@Override
public String notify(Map<String, String> params) {
log.info("支付成功通知");
String result = "failure";
// 校验
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus)) {
log.error("交易失败");
return result;
}
// 查询订单信息
String outTradeNo = params.get("trade_no");
TtOrder order = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getOutTradeNo, outTradeNo)
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.one();
// 防止重复通知
if (!PayOrderStatus.NO_PAY.getCode().equals(order.getStatus())) {
return result;
}
// 查询用户信息
TtUser user = new LambdaQueryChainWrapper<>(userMapper)
.eq(TtUser::getUserId, order.getUserId())
.eq(TtUser::getDelFlag, 0)
.one();
// 3.查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeListMapper)
.eq(TtRechargeProd::getId, order.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
// 4.账户结算
if (ObjectUtil.isNull(goods.getProductA())) goods.setProductA(BigDecimal.ZERO);
if (ObjectUtil.isNull(goods.getProductC())) goods.setProductC(BigDecimal.ZERO);
payNotifyAccounting(order, user, goods, order.getGoodsNum());
try {
// 首充赠送
firstChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广等级充值赠送
promotionLevelChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广充值返佣
promotionChargeCommission(user, goods, order.getGoodsNum());
} catch (Exception e) {
e.printStackTrace();
}
// 5.更新订单
boolean update = new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
if (update) {
result = "success";
}
// 6.发送充值通知
rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(order.getUserId().toString(), order.getGoodsPrice());
return result;
}
// 账户结算
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber) {
// 加钱
// BigDecimal totalAmount = order.getTotalAmount();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal totalCredits = goods.getProductC().multiply(new BigDecimal(goodsNumber));
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, user.getUserId())
.setSql("account_amount = account_amount + " + totalAmount.toString()
+ ",account_credits = account_credits + " + totalCredits.toString());
userService.update(userUpdate);
user = userService.getById(user.getUserId());
// 综合消费日志
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(user.getUserId())
.amount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? totalAmount : null)
.finalAmount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountAmount().add(totalAmount) : null)
.credits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? totalCredits : null)
.finalCredits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountCredits().add(totalCredits) : null)
.total(totalAmount.add(totalCredits)) // 收支合计
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.RECHARGE.getCode())
.remark(TtAccountRecordSource.RECHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.updateTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
// 充值记录
TtRechargeRecord ttRechargeRecord = TtRechargeRecord.builder().build();
ttRechargeRecord.setUserId(order.getUserId());
ttRechargeRecord.setParentId(user.getParentId());
ttRechargeRecord.setArrivalAmount(totalAmount);
ttRechargeRecord.setAmountActuallyPaid(totalAmount);
ttRechargeRecord.setFinallyPrice(user.getAccountAmount());
ttRechargeRecord.setOrderId(order.getOrderId());
ttRechargeRecord.setOutTradeNo(order.getOutTradeNo());
ttRechargeRecord.setStatus("0");
ttRechargeRecord.setChannelType("1");
ttRechargeRecord.setCreateTime(DateUtils.getNowDate());
ttRechargeRecordMapper.insert(ttRechargeRecord);
// 更新VIP等级
userService.updateUserVIPLevel(user.getUserId());
}
/**
* 首充赠送
*/
private void firstChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.判断是否为首充
LambdaQueryWrapper<TtUserBlendErcash> wrapper = new LambdaQueryWrapper<>();
wrapper
.eq(TtUserBlendErcash::getSource, 1)
.eq(TtUserBlendErcash::getUserId, ttUser.getUserId());
List<TtUserBlendErcash> ttUserBlendErcashes = userBlendErcashMapper.selectList(wrapper);
if (ttUserBlendErcashes.size() > 0) {
return;
}
// 2.加钱
// 充值金额
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
// 查询赠送比例
BigDecimal firstChargeAmountRatio = ttFirstRechargeMapper.selectRatioByMinAmount(totalAmount);
if (Objects.isNull(firstChargeAmountRatio)) {
return;
}
// 计算赠送金额
BigDecimal giftAmount = totalAmount.multiply(firstChargeAmountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.FIRST_CHARGE.getCode())
.remark(TtAccountRecordSource.FIRST_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广等级充值赠送
*/
@Async
public void promotionLevelChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.查询用户推广等级对应的赠送比例
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
if (Objects.isNull(ttPromotionLevel))
return;
// 2.加钱
BigDecimal amountRatio = ttPromotionLevel.getRechargeGiftRatio();
if (Objects.isNull(amountRatio))
return;
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getCode())
.remark(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广充值返佣
*/
private void promotionChargeCommission(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
if (Objects.isNull(ttUser.getParentId())) {
return;
}
// 查询用户推广等级信息
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
// 给上级加钱
BigDecimal amountRatio = ttPromotionLevel.getCommissions();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getParentId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 记录
ttUser = userService.getById(ttUser.getParentId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getCode())
.remark(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
}

View File

@@ -0,0 +1,76 @@
package com.ruoyi.thirdparty.baiweipay.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class BaiweiPayUtil {
private String key;
public BaiweiPayUtil(String key) {
this.key = key;
}
/**
* 生成MD5签名
*
* @param params 参数列表
* @return 生成的签名
*/
public String getSign(Map<String, String> params) {
// 排序参数
Map<String, String> sortedParams = new TreeMap<>(params);
sortedParams.remove("sign");
sortedParams.remove("sign_type");
// 构建签名字符串
StringBuilder signStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
signStr.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
if (signStr.length() > 0) {
signStr.setLength(signStr.length() - 1); // 移除最后一个&
}
// 添加商户密钥
signStr.append(this.key);
// 计算MD5签名
return md5(signStr.toString()).toLowerCase();
}
/**
* 计算MD5哈希值
*
* @param input 输入字符串
* @return MD5哈希值
*/
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
return bytesToHex(messageDigest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not found", e);
}
}
/**
* 将字节数组转换为十六进制字符串
*
* @param bytes 字节数组
* @return 十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.thirdparty.common.NetworkDataLoader;
import cn.hutool.core.util.HashUtil;
import com.ruoyi.common.core.domain.entity.SysDictData;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.List;
@Data
public abstract class BaseNetworkDataLoader {
public String name;
protected List<SysDictData> allEnum;
// 根据类型和标签获取code
protected String getCodeByNameOrHash(List<SysDictData> typeList, String nameOrHash){
for (SysDictData type : typeList){
if (type.getDictLabel().equals(nameOrHash)) return type.getDictValue();
if (type.getDictType().equals(nameOrHash)) return type.getDictValue();
}
return null;
}
protected Long createOrnamentId(String MarketHashName){
Long id = Long.valueOf(HashUtil.rsHash(MarketHashName));
if (id < 0) id = Math.abs(id);
return id;
}
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.thirdparty.common.NetworkDataLoader;
import com.ruoyi.domain.entity.TtOrnament;
import java.util.List;
import java.util.Set;
public interface NetworkDataLoader {
Set<TtOrnament> load();
}

View File

@@ -0,0 +1,157 @@
package com.ruoyi.thirdparty.common.NetworkDataLoader;
import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.domain.dto.zbt.OrnamentZBT;
import com.ruoyi.domain.entity.TtOrnament;
import com.ruoyi.thirdparty.zbt.param.ProductListParams;
import com.ruoyi.thirdparty.zbt.param.SearchParams;
import com.ruoyi.thirdparty.zbt.result.ResultZbt;
import com.ruoyi.thirdparty.zbt.result.product.AvailableMarket;
import com.ruoyi.thirdparty.zbt.result.product.AvailableMarketList;
import com.ruoyi.thirdparty.zbt.result.product.SearchData;
import com.ruoyi.thirdparty.zbt.service.ZBTService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@AllArgsConstructor
// @Data
public class ZBTNetworkDataLoader extends BaseNetworkDataLoader implements NetworkDataLoader{
private ZBTService zbtService;
@Override
public Set<TtOrnament> load() {
Set<TtOrnament> res = new HashSet<>();
for (SysDictData type : allEnum){
// 根据指定的饰品类型加载数据
if (!type.getDictType().equals("ornaments_type")) continue;
log.info("正在加载{}类型的饰品资源~",type.getDictLabel());
Set<TtOrnament> data = ZBTLoadOrnamentByType(type);
res.addAll(data);
}
return res;
}
private Set<TtOrnament> ZBTLoadOrnamentByType(SysDictData type){
HashSet<TtOrnament> result = new HashSet<>();
// 分批请求api
int page = 1;
boolean stopFlag = false;
while (true) {
// 结束标记
if (stopFlag) break;
//1 请求平台数据
SearchParams param = SearchParams.builder()
.page(String.valueOf(page))
.category(type.getDictLabel())
.limit("200")
.build();
ResultZbt<SearchData> response = zbtService.search(param);
// 解析平台数据
SearchData data = response.getData();
if (!response.getSuccess() || ObjectUtil.isEmpty(data)) break;
List<OrnamentZBT> list = JSONUtil.toList(JSONUtil.toJsonStr(data.getList()), OrnamentZBT.class);
if (list.size() < 200) stopFlag = !stopFlag;
// 转换为通用类型
Set<TtOrnament> collect = list.stream().map(item -> {
// 生成本平台id
Long id = createOrnamentId(item.getMarketHashName());
TtOrnament ornament = TtOrnament.builder()
.id(id)
.zbtId(item.getItemId())
.marketHashName(item.getMarketHashName())
.name(item.getItemName())
.shortName(item.getShortName())
// 价格信息
.price(ObjectUtil.isNotEmpty(item.getPriceInfo()) ? item.getPriceInfo().getPrice() : null)
.quantity(ObjectUtil.isNotEmpty(item.getPriceInfo()) ? item.getPriceInfo().getQuantity() : null)
.imageUrl(item.getImageUrl())
.type(type.getDictValue())
.typeName(item.getTypeName())
.typeHashName(item.getType())
.quality(getCodeByNameOrHash(allEnum,item.getQuality()))
.qualityName(item.getQualityName())
.qualityHashName(item.getQuality())
.qualityColor(item.getQualityColor())
.rarity(getCodeByNameOrHash(allEnum,item.getRarity()))
.rarityName(item.getRarityName())
.rarityHashName(item.getRarity())
.rarityColor(item.getRarityColor())
.exterior(getCodeByNameOrHash(allEnum,item.getExterior()))
.exteriorHashName(item.getExterior())
.exteriorName(item.getExteriorName())
.updateTime(new Date())
.build();
// 补充价格信息
AvailableMarket sellInfo = apiSellInfo(String.valueOf(ornament.getZbtId()));
if (ObjectUtil.isNotEmpty(sellInfo)) {
ornament.setUsePrice(sellInfo.getCnyPrice());
}
return ornament;
}).collect(Collectors.toSet());
result.addAll(collect);
// log.info("加载【扎比特】{}条类型为{}的饰品数据。",page*collect.size(),typeHash);
System.out.print("\r加载【扎比特】"+page*collect.size()+"条类型为"+type.getDictLabel()+"的饰品数据。请耐心等待");
page++;
}
return result;
}
// 查询价格信息
private AvailableMarket apiSellInfo(String itemId){
ProductListParams p = ProductListParams.builder()
.itemId(itemId)
.page("1")
.limit("1")
.language("zh_CN")
.build();
ResultZbt<AvailableMarketList> zbtData = zbtService.productList(p);
if (!zbtData.getSuccess()) return null;
List<AvailableMarket> list = zbtData.getData().getList();
if (list.isEmpty()) return null;
return list.get(0);
}
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.thirdparty.common.config;
import com.rabbitmq.client.Channel;
import com.ruoyi.common.rabbitmq.config.DelayedQueueConfig;
import com.ruoyi.thirdparty.common.service.DeliverGoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
public class DeliveryRabbitConsumer {
private final DeliverGoodsService deliverGoodsService;
public DeliveryRabbitConsumer(DeliverGoodsService deliverGoodsService) {
this.deliverGoodsService = deliverGoodsService;
}
// 自动提货业务
@RabbitListener(queues = DelayedQueueConfig.DELIVERY_QUEUE)
public void deliveryReceive(Message message, Channel channel) throws IOException {
Integer userId = Integer.valueOf(new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
try {
deliverGoodsService.autoDelivery(userId);
} catch (Exception e) {
log.error(e.getMessage());
channel.basicRecover(false);
}
}
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.thirdparty.common.config;
// 网络资源加载器配置
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.system.mapper.SysDictDataMapper;
import com.ruoyi.thirdparty.common.NetworkDataLoader.NetworkDataLoader;
import com.ruoyi.thirdparty.common.NetworkDataLoader.ZBTNetworkDataLoader;
import com.ruoyi.thirdparty.zbt.service.ZBTService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Configuration
public class NetworkDataLoaderConfig {
@Autowired
private ZBTService zbtService;
@Autowired
private SysDictDataMapper dictDataMapper;
@Bean
public List<NetworkDataLoader> networkDataLoaderList(){
// 加载必要的枚举数据
List<String> dictTypes = Arrays.asList(
"ornaments_type", "ornaments_type_name",
"ornaments_exterior", "ornaments_exterior_name",
"ornaments_quality", "ornaments_quality_name",
"ornaments_rarity", "ornaments_rarity_name");
List<SysDictData> enumList = dictDataMapper.selectDictDataByTypes(dictTypes);
ArrayList<NetworkDataLoader> networkDataLoaders = new ArrayList<>();
// zbt加载器
ZBTNetworkDataLoader zbtNetworkDataLoader = new ZBTNetworkDataLoader(zbtService);
zbtNetworkDataLoader.setAllEnum(enumList);
zbtNetworkDataLoader.setName("扎比特加载器");
networkDataLoaders.add(zbtNetworkDataLoader);
return networkDataLoaders;
}
}

View File

@@ -0,0 +1,31 @@
package com.ruoyi.thirdparty.common.controller;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.thirdparty.common.service.ApiCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Api(tags = "第三方公共接口")
@RestController
@RequestMapping("/api/thirdparty/common")
public class ApiCommonController {
private final ApiCommonService commonService;
public ApiCommonController(ApiCommonService commonService) {
this.commonService = commonService;
}
@ApiOperation("生成二维码")
@PostMapping("/skipLinkToQRCode")
public R<Object> skipLinkToQRCode(HttpServletResponse response, @RequestParam("contents") String contents) throws IOException {
return commonService.writerPayImage(response, contents);
}
}

View File

@@ -0,0 +1,84 @@
package com.ruoyi.thirdparty.common.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.service.TtOrderService;
import com.ruoyi.admin.service.TtRechargeConfigService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.common.constant.PartyType;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.dto.recharge.RechargeConfigOrderVo;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtRechargeConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Api(tags = "充值配置接口")
@RestController
@RequestMapping("/api/recharge/config")
public class ApiRechargeConfigController extends BaseController {
@Autowired
private TtRechargeConfigService ttRechargeConfigService;
@Autowired
private TtOrderService ttOrderService;
@ApiOperation("充值结果列表")
@GetMapping("/order")
public R<List<RechargeConfigOrderVo>> order() {
startPage();
LambdaQueryWrapper<TtOrder> wrapper = Wrappers.lambdaQuery();
wrapper.eq(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode());
wrapper.eq(TtOrder::getUserId, getUserId());
wrapper.eq(TtOrder::getThirdParty, PartyType.INNER_PICTURE_PAY.getCode());
wrapper.orderByDesc(TtOrder::getCreateTime);
List<TtOrder> list = ttOrderService.list(wrapper);
if (CollectionUtils.isEmpty(list)) {
return R.ok(new ArrayList<>());
}
List<RechargeConfigOrderVo> vos = new ArrayList<>();
List<Integer> goodsIds = new ArrayList<>();
for (TtOrder ttOrder : list) {
RechargeConfigOrderVo vo = new RechargeConfigOrderVo();
BeanUtils.copyProperties(ttOrder, vo);
goodsIds.add(ttOrder.getGoodsId());
vos.add(vo);
}
LambdaQueryWrapper<TtRechargeConfig> wrapper1 = Wrappers.lambdaQuery();
wrapper1.in(TtRechargeConfig::getId, goodsIds);
List<TtRechargeConfig> configs = ttRechargeConfigService.list(wrapper1);
Map<Integer, TtRechargeConfig> configMap = configs.stream().collect(Collectors.toMap(TtRechargeConfig::getId, Function.identity()));
for (RechargeConfigOrderVo vo : vos) {
if (configMap.get(vo.getGoodsId()) != null) {
vo.setPicture(configMap.get(vo.getGoodsId()).getPicture());
}
}
return R.ok(vos);
}
@ApiOperation("充值列表")
@GetMapping("/list")
public R<List<TtRechargeConfig>> list() {
startPage();
LambdaQueryWrapper<TtRechargeConfig> wrapper = Wrappers.lambdaQuery();
wrapper.eq(TtRechargeConfig::getStatus, "0");
List<TtRechargeConfig> list = ttRechargeConfigService.list(wrapper);
return R.ok(list);
}
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.thirdparty.common.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.service.TtRechargeProdService;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UpdateUserCache;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.common.service.ApiRechargeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Api(tags = "充值")
@RestController
@RequestMapping("/api/recharge")
public class ApiRechargeController extends BaseController {
private final ISysConfigService sysConfigService;
private final TtUserService userService;
private final ApiRechargeService apiRechargeService;
private final TtRechargeProdService rechargeListService;
public ApiRechargeController(ISysConfigService sysConfigService,
TtUserService userService,
ApiRechargeService apiRechargeService,
TtRechargeProdService rechargeListService) {
this.sysConfigService = sysConfigService;
this.userService = userService;
this.apiRechargeService = apiRechargeService;
this.rechargeListService = rechargeListService;
}
@ApiOperation("卡支付")
@UpdateUserCache
@PostMapping("/cardPay")
public R<Boolean> cardPay(String password) {
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
if ("1".equals(websiteMaintenance)) {
return R.fail("网站维护中......");
}
TtUser ttUser = userService.getById(getUserId());
String msg = apiRechargeService.cardPay(password, ttUser);
return StringUtils.isEmpty(msg) ? R.ok(true, "充值成功!") : R.fail(false, msg);
}
@ApiOperation("充值列表")
// @UpdateUserCache
@GetMapping("/list")
public R<List<TtRechargeProd>> list() {
startPage();
LambdaQueryWrapper<TtRechargeProd> wrapper = Wrappers.lambdaQuery();
wrapper.eq(TtRechargeProd::getStatus, "0");
List<TtRechargeProd> list = rechargeListService.list(wrapper);
return R.ok(list);
}
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.thirdparty.common.controller;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.common.model.ApiSmsBody;
import com.ruoyi.thirdparty.common.service.ApiSmsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "短信")
@RestController
@RequestMapping("/api/sms")
public class ApiSmsController extends BaseController {
private final ISysConfigService sysConfigService;
private final ApiSmsService apiSmsService;
public ApiSmsController(ISysConfigService sysConfigService,
ApiSmsService apiSmsService) {
this.sysConfigService = sysConfigService;
this.apiSmsService = apiSmsService;
}
@ApiOperation("获取短信验证码")
@Anonymous
@PostMapping("/getVerifyCode")
public R<Object> getVerifyCode(@RequestBody ApiSmsBody smsBody) {
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
if ("1".equals(websiteMaintenance)) {
return R.fail("网站维护中......");
}
String msg = apiSmsService.getVerifyCode(smsBody);
return StringUtils.isEmpty(msg) ? R.ok("获取验证码成功,请注意短信通知!") : R.fail(msg);
}
}

View File

@@ -0,0 +1,82 @@
package com.ruoyi.thirdparty.common.controller;
import com.ruoyi.domain.dto.deliver.TradeBuyParam;
import com.ruoyi.domain.dto.deliver.TradeManualConfirmParam;
import com.ruoyi.domain.vo.AvailableMarketOrnamentVO;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.thirdparty.common.service.DeliverGoodsService;
import com.ruoyi.thirdparty.zbt.param.ProductListParams;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "管理端发货管理")
@RestController
@RequestMapping("/admin/deliverGoods")
public class DeliverGoodsController extends BaseController {
private final DeliverGoodsService deliverGoodsService;
public DeliverGoodsController(DeliverGoodsService deliverGoodsService) {
this.deliverGoodsService = deliverGoodsService;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class GetAvailableMarketListParam{
// private List<String> hashNameList;
private Integer partyType;
private List<Long> ornamentsId;
}
// 获取饰品在各个平台的在售信息
@ApiOperation("获取饰品在各个平台的在售信息")
@PostMapping("/getAvailableMarketList")
public R getAvailableMarketList(@RequestBody GetAvailableMarketListParam param) {
return deliverGoodsService.getAvailableMarketList(param);
}
// 根据饰品hashName获取所有平台的在售列表弃用接口
@ApiOperation("根据饰品hashName获取所有平台的在售列表弃用接口")
@GetMapping("/getAvailableMarketListByHashName/{marketHashName}")
public R<List<AvailableMarketOrnamentVO>> getAvailableMarketListByHashName(@PathVariable("marketHashName") String marketHashName,
ProductListParams productListParams) {
List<AvailableMarketOrnamentVO> list = deliverGoodsService.getAvailableMarketListByHashName(marketHashName, productListParams);
if (StringUtils.isNull(list)) return R.fail("获取饰品所有在售列表异常!");
return R.ok(list);
}
@ApiOperation("购买发货")
@PostMapping("/tradeBuy")
public R tradeBuy(@RequestBody TradeBuyParam param) {
return deliverGoodsService.tradeBuy(param);
}
@ApiOperation("手动确认发货")
@PostMapping("/tradeConfirm")
public R tradeConfirm(@RequestBody TradeManualConfirmParam param) {
return deliverGoodsService.tradeConfirm(param);
}
@ApiOperation("同步状态")
@GetMapping("/synchronousStatus")
public R<Boolean> synchronousStatus(String outTradeNo) {
String msg = deliverGoodsService.synchronousStatus(outTradeNo);
return StringUtils.isEmpty(msg) ? R.ok(true, "同步成功!") : R.fail(false, msg);
}
@ApiOperation("一键同步状态")
@GetMapping("/syncAllStatus")
public R syncAllStatus() {
int row = deliverGoodsService.syncAllStatus();
return row > 0 ? R.ok() : R.fail("一键同步失败");
}
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.thirdparty.common.mapper;
import com.ruoyi.domain.other.TtNotice;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
@Mapper
@Component("thirdpartyApiNoticeMapper")
public interface ApiNoticeMapper {
int addNotice(TtNotice ttNotice);
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.thirdparty.common.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class ApiSmsBody {
private String phoneNumber;
@ApiModelProperty("1注册 2登录 3更换手机号 4找回密码")
private String type;
}

View File

@@ -0,0 +1,73 @@
package com.ruoyi.thirdparty.common.scheduled;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.admin.service.TtOrnamentService;
import com.ruoyi.domain.entity.TtOrnament;
import com.ruoyi.thirdparty.common.NetworkDataLoader.BaseNetworkDataLoader;
import com.ruoyi.thirdparty.common.NetworkDataLoader.NetworkDataLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Slf4j
@Configuration // 1.主要用于标记配置类兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class ThirdPartyTask {
@Autowired
private TtOrnamentService ttOrnamentService;
@Value("${mkcsgo.startLoadOrnaments}")
private Boolean startLoadOrnaments;
// 资源加载器集合
@Autowired
private List<NetworkDataLoader> networkDataLoaderList;
// 定时加载发货平台的物品数据
@Scheduled(cron = "0 50 23 * * ?")
// @Scheduled(cron = "0 6 17 * * ?")
public void networkDataLoad(){
log.info("是否加载网络ornaments资源{}", startLoadOrnaments);
if (!startLoadOrnaments) return;
// 1 加载各个平台数据
Set<TtOrnament> ttOrnaments = new HashSet<>();
for (NetworkDataLoader loader : networkDataLoaderList) {
log.info("【{}】开始加载网络资源~",((BaseNetworkDataLoader)loader).name);
Set<TtOrnament> load = loader.load();
ttOrnaments.addAll(load);
}
if (ObjectUtil.isEmpty(ttOrnaments) || ttOrnaments.isEmpty()) {
log.warn("加载饰品网络资源失败。");
return;
}
// 2 写db
log.info("db allData ing~~~");
for (TtOrnament item : ttOrnaments) {
try {
ttOrnamentService.saveOrUpdate(item);
} catch (DuplicateKeyException e) {
// log.info("过滤重复数据 【id】{}【hash】{}save。",item.getId(),item.getMarketHashName());
} catch (Exception e) {
e.printStackTrace();
log.info("保存数据异常。");
break;
}
}
log.info("加载饰品网络资源成功。");
}
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.thirdparty.common.service;
import com.ruoyi.common.core.domain.R;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ApiCommonService {
R<Object> writerPayImage(HttpServletResponse response, String contents) throws IOException;
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.thirdparty.common.service;
import com.ruoyi.domain.other.TtNotice;
public interface ApiNoticeService {
int addNotice(TtNotice ttNotice);
// 发送通知
void sendNotice(Long userId, String title, String content);
}

View File

@@ -0,0 +1,8 @@
package com.ruoyi.thirdparty.common.service;
import com.ruoyi.domain.entity.sys.TtUser;
public interface ApiRechargeService {
String cardPay(String password, TtUser ttUser);
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.thirdparty.common.service;
import com.ruoyi.thirdparty.common.model.ApiSmsBody;
public interface ApiSmsService {
String getVerifyCode(ApiSmsBody smsBody);
String validateCaptcha(String code, String verifyKey);
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.thirdparty.common.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.dto.deliver.TradeBuyParam;
import com.ruoyi.domain.dto.deliver.TradeManualConfirmParam;
import com.ruoyi.domain.vo.AvailableMarketOrnamentVO;
import com.ruoyi.thirdparty.common.controller.DeliverGoodsController.GetAvailableMarketListParam;
import com.ruoyi.thirdparty.zbt.param.ProductListParams;
import java.util.List;
public interface DeliverGoodsService {
R getAvailableMarketList(Integer ornamentsId, Integer partyType);
R tradeBuy(TradeBuyParam param);
String synchronousStatus(String outTradeNo);
int syncAllStatus();
void autoDelivery(Integer userId);
List<AvailableMarketOrnamentVO> getAvailableMarketListByHashName(String marketHashName, ProductListParams productListParams);
R getAvailableMarketList(GetAvailableMarketListParam param);
R tradeConfirm(TradeManualConfirmParam param);
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.common.service;
import java.math.BigDecimal;
/**
* 充值成功通知
*/
public interface RechargeSuccessfulNoticeService {
/**
* 发送充值成功通知
*/
void sendRechargeSuccessNotice(String userId, BigDecimal amount);
}

View File

@@ -0,0 +1,42 @@
package com.ruoyi.thirdparty.common.service.impl;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.thirdparty.common.service.ApiCommonService;
import org.springframework.stereotype.Service;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class ApiCommonServiceImpl implements ApiCommonService {
@Override
public R<Object> writerPayImage(HttpServletResponse response, String contents) throws IOException {
ServletOutputStream out = response.getOutputStream();
try {
Map<EncodeHintType,Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
hints.put(EncodeHintType.MARGIN, 0);
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,300,300,hints);
MatrixToImageWriter.writeToStream(bitMatrix,"jpg",out);
return R.ok("success");
}catch (Exception e){
return R.fail("数据异常");
}finally {
if(out != null){
out.flush();
out.close();
}
}
}
}

View File

@@ -0,0 +1,38 @@
package com.ruoyi.thirdparty.common.service.impl;
import com.ruoyi.domain.other.TtNotice;
import com.ruoyi.thirdparty.common.mapper.ApiNoticeMapper;
import com.ruoyi.thirdparty.common.service.ApiNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.Date;
@Slf4j
@Service("thirdpartyApiNoticeServiceImpl")
public class ApiNoticeServiceImpl implements ApiNoticeService {
@Autowired
@Qualifier("thirdpartyApiNoticeMapper")
private ApiNoticeMapper apiNoticeMapper;
@Override
public int addNotice(TtNotice ttNotice) {
ttNotice.setCreateTime(new Date());
return apiNoticeMapper.addNotice(ttNotice);
}
//用 AsyncManager 线程池异步写入并且发送通知
@Override
public void sendNotice(Long userId, String title, String content) {
TtNotice notice = new TtNotice();
notice.setUserId(userId);
notice.setTitle(title);
notice.setContent(content);
notice.setRead("0");
addNotice(notice);
}
}

View File

@@ -0,0 +1,142 @@
package com.ruoyi.thirdparty.common.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ruoyi.admin.config.RedisConstants;
import com.ruoyi.admin.mapper.TtOrderMapper;
import com.ruoyi.admin.mapper.TtRechargeCardMapper;
import com.ruoyi.admin.mapper.TtRechargeRecordMapper;
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
import com.ruoyi.admin.service.TtBonusService;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.redis.config.RedisLock;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.common.constant.PayType;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.TtRechargeCard;
import com.ruoyi.domain.other.TtRechargeRecord;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.thirdparty.common.service.ApiRechargeService;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class ApiRechargeServiceImpl implements ApiRechargeService {
private final RedisLock redisLock;
private final TtBonusService bonusService;
private final TtUserService userService;
private final TtRechargeCardMapper rechargeCardMapper;
private final TtRechargeRecordMapper rechargeRecordMapper;
public ApiRechargeServiceImpl(RedisLock redisLock,
TtBonusService bonusService,
TtUserService userService,
TtRechargeCardMapper rechargeCardMapper,
TtRechargeRecordMapper rechargeRecordMapper) {
this.redisLock = redisLock;
this.bonusService = bonusService;
this.userService = userService;
this.rechargeCardMapper = rechargeCardMapper;
this.rechargeRecordMapper = rechargeRecordMapper;
}
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private TtOrderMapper orderMapper;
@Override
@Transactional
public String cardPay(String password, TtUser ttUser) {
while (true) {
Boolean cardPayLock = redisLock.tryLock(RedisConstants.CARD_PAY_LOCK + ttUser.getParentId(), 3L, 60L, TimeUnit.SECONDS);
if (cardPayLock) {
try {
TtRechargeCard ttRechargeCard = new LambdaQueryChainWrapper<>(rechargeCardMapper).eq(TtRechargeCard::getPassword, password)
.eq(TtRechargeCard::getStatus, "0").eq(TtRechargeCard::getDelFlag, "0").one();
if (StringUtils.isNull(ttRechargeCard)) return "卡密已被使用!";
ttRechargeCard.setStatus("1");
ttRechargeCard.setUseUserId(ttUser.getUserId());
ttRechargeCard.setUseTime(DateUtils.getNowDate());
ttRechargeCard.setUpdateBy(ttUser.getUserName());
ttRechargeCard.setUpdateTime(DateUtils.getNowDate());
if (rechargeCardMapper.updateById(ttRechargeCard) > 0) {
BigDecimal totalAmount = ttRechargeCard.getPrice();
userService.updateOnlyUserAccount(ttUser.getUserId(), totalAmount, TtAccountRecordSource.RECHARGE);
// 更新VIP等级目前需要在记录日志之后要不然总充值不是最新
userService.updateUserVIPLevel(ttUser.getUserId());
TtRechargeRecord ttRechargeRecord = TtRechargeRecord.builder().build();
ttRechargeRecord.setUserId(ttUser.getUserId());
ttRechargeRecord.setParentId(ttUser.getParentId());
ttRechargeRecord.setArrivalAmount(totalAmount);
ttRechargeRecord.setAmountActuallyPaid(totalAmount);
ttRechargeRecord.setFinallyPrice(ttUser.getAccountAmount());
ttRechargeRecord.setOrderId(password);
ttRechargeRecord.setStatus("0");
ttRechargeRecord.setChannelType("0");
ttRechargeRecord.setCreateTime(DateUtils.getNowDate());
// 实时计算返佣
if (rechargeRecordMapper.insert(ttRechargeRecord) > 0) {
// AsyncManager.me().run(() -> {
// bonusService.bonus(ttUser.getUserId(), ttRechargeRecord.getId());
// });
}
// 设置商户订单号
String orderId = String.valueOf(IdUtil.getSnowflake(1, 1).nextId());
// 创建订单
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setOutTradeNo(password);
order.setUserId(ttUser.getUserId());
order.setType(PayType.MK_CARD.getCode());
order.setGoodsId(ttRechargeCard.getId());
order.setGoodsPrice(ttRechargeCard.getPrice());
order.setGoodsNum(1);
order.setTotalAmount(totalAmount);
order.setStatus(PayOrderStatus.PAY_COMPLE.getCode());
order.setCreateTime(new Date());
orderMapper.insert(order);
// 发送充值成功通知
// rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(ttUser.getUserId().toString(), totalAmount);
return "";
}
} catch (Exception e) {
log.error("happen exception is {}", e.getMessage(), e);
return "数据异常!";
} finally {
redisLock.unlock(RedisConstants.CARD_PAY_LOCK + ttUser.getParentId());
}
} else {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (Exception e) {
log.error("happen exception is {}", e.getMessage(), e);
return "数据异常!";
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
package com.ruoyi.thirdparty.common.service.impl;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.thirdparty.common.model.ApiSmsBody;
import com.ruoyi.thirdparty.common.service.ApiSmsService;
import com.ruoyi.thirdparty.note.service.YunXinNoteService;
import com.ruoyi.thirdparty.smsbao.service.SmsbaoService;
import com.ruoyi.thirdparty.winic.service.WinicService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class ApiSmsServiceImpl implements ApiSmsService {
private final RedisCache redisCache;
private final TtUserService userService;
private final YunXinNoteService yunXinNoteService;
public ApiSmsServiceImpl(RedisCache redisCache,
TtUserService userService,
YunXinNoteService yunXinNoteService) {
this.redisCache = redisCache;
this.userService = userService;
this.yunXinNoteService = yunXinNoteService;
}
@Autowired
private SmsbaoService smsbaoService;
@Autowired
private WinicService winicService;
@Value("${ruoyi.name}")
private String systemName;
@Override
public String getVerifyCode(ApiSmsBody smsBody) {
String phoneNumber = smsBody.getPhoneNumber();
String type = smsBody.getType();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY;
if (StringUtils.isEmpty(type)) {
return "类型不能为空";
}
if (StringUtils.isEmpty(phoneNumber)) {
return "手机号不能为空";
}
if (!Validator.isMobile(phoneNumber)) {
return "手机号格式错误,请检查手机号是否输入正确!";
}
if ("1".equals(type) && !userService.checkPhoneUnique(TtUser.builder().phoneNumber(phoneNumber).build())) {
return "手机号'" + phoneNumber + "'已被注册!";
}
if ("2".equals(type) || "3".equals(type) || "4".equals(type)) {
TtUser ttUser = new LambdaQueryChainWrapper<>(userService.getBaseMapper()).eq(TtUser::getPhoneNumber, phoneNumber).one();
if (StringUtils.isNull(ttUser)) return "该手机号未在本站注册!";
}
if ("1".equals(type)) {
verifyKey = verifyKey + "ApiRegister_" + phoneNumber;
} else if ("2".equals(type)) {
verifyKey = verifyKey + "ApiLogin_" + phoneNumber;
} else if ("3".equals(type)) {
verifyKey = verifyKey + "UpdatePhoneNumber_" + phoneNumber;
} else if ("4".equals(type)) {
verifyKey = verifyKey + "ApiForgetPassword_" + phoneNumber;
}
String cacheCode = redisCache.getCacheObject(verifyKey);
if (StringUtils.isNotEmpty(cacheCode)) {
return "验证码已发送,请注意手机短信信息!";
}
String randomCode = RandomUtil.randomNumbers(4);
// 网易云信
// String code = yunXinNoteService.sendNote(phoneNumber, randomCode);
// if (StringUtils.isEmpty(code)) {
// return "发送短信验证码时出现异常!";
// }
// 切换为短信宝
String content = "" + systemName +"】您的验证码为" + randomCode + "该验证码5分钟内有效请勿泄露于他人。";
String code = smsbaoService.sendSms(phoneNumber, content);
if (!"0".equals(code)) {
return "发送短信验证码时出现异常!";
}
// 切换为中正云通信
// String content = "【OG】您的验证码为" + randomCode + "该验证码5分钟内有效请勿泄露于他人。";
// String code = winicService.sendSms(phoneNumber, content);
// if (!"000".equals(code)) {
// return "发送短信验证码时出现异常!";
// }
redisCache.setCacheObject(verifyKey, randomCode, 5, TimeUnit.MINUTES);
return "";
}
@Override
public String validateCaptcha(String code, String verifyKey) {
verifyKey = CacheConstants.CAPTCHA_CODE_KEY + verifyKey;
String captcha = redisCache.getCacheObject(verifyKey);
if (!code.equalsIgnoreCase(captcha)) return "验证码错误";
redisCache.deleteObject(verifyKey);
return "success";
}
}

View File

@@ -0,0 +1,701 @@
package com.ruoyi.thirdparty.common.service.impl;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.admin.mapper.*;
import com.ruoyi.admin.service.TtOrnamentService;
import com.ruoyi.admin.service.TtOrnamentYYService;
import com.ruoyi.admin.service.TtYyOrnamentsService;
import com.ruoyi.common.constant.ThirdParty.Platform;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.domain.common.constant.DeliveryOrderStatus;
import com.ruoyi.domain.common.constant.PartyType;
import com.ruoyi.domain.common.constant.TtboxRecordStatus;
import com.ruoyi.domain.dto.deliver.TradeBuyParam;
import com.ruoyi.domain.dto.deliver.TradeManualConfirmParam;
import com.ruoyi.domain.entity.TtBoxRecords;
import com.ruoyi.domain.entity.TtOrnament;
import com.ruoyi.domain.entity.delivery.TtDeliveryRecord;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.TtYYOrnaments;
import com.ruoyi.domain.vo.AvailableMarketOrnamentVO;
import com.ruoyi.domain.vo.OnSaleOrnamentVO;
import com.ruoyi.domain.vo.queryTemplateSaleByCategoryDataVO;
import com.ruoyi.domain.vo.queryTemplateSaleByCategoryVO;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.common.controller.DeliverGoodsController.GetAvailableMarketListParam;
import com.ruoyi.thirdparty.common.service.DeliverGoodsService;
import com.ruoyi.thirdparty.cs340.domain.body.ByTemplateCreateOrder;
import com.ruoyi.thirdparty.cs340.domain.result.ByTemplateCreateOrderResult;
import com.ruoyi.thirdparty.cs340.domain.result.Cs340OrderStatusResult;
import com.ruoyi.thirdparty.cs340.domain.result.Cs340Result;
import com.ruoyi.thirdparty.cs340.service.Cs340Service;
import com.ruoyi.thirdparty.v5item.domain.body.CreateOrderBody;
import com.ruoyi.thirdparty.v5item.domain.result.CreateOrderResult;
import com.ruoyi.thirdparty.v5item.domain.result.QueryOrderStatusResult;
import com.ruoyi.thirdparty.v5item.domain.result.V5ItemResult;
import com.ruoyi.thirdparty.v5item.service.V5ItemService;
import com.ruoyi.thirdparty.yyyouping.service.yyyoupingService;
import com.ruoyi.thirdparty.yyyouping.utils.YYClient;
import com.ruoyi.thirdparty.yyyouping.utils.common.YYResult;
import com.ruoyi.thirdparty.zbt.param.*;
import com.ruoyi.thirdparty.zbt.result.ResultZbt;
import com.ruoyi.thirdparty.zbt.result.buy.OpenBuyResultDTO;
import com.ruoyi.thirdparty.zbt.result.order.OrderBuyDetailDTO;
import com.ruoyi.thirdparty.zbt.result.product.AvailableMarket;
import com.ruoyi.thirdparty.zbt.result.product.AvailableMarketList;
import com.ruoyi.thirdparty.zbt.service.ZBTService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import static com.ruoyi.domain.common.constant.DeliveryOrderStatus.DELIVERY_BEFORE;
import static com.ruoyi.domain.common.constant.DeliveryOrderStatus.ORDER_CANCEL;
@Service
@Slf4j
public class DeliverGoodsServiceImpl implements DeliverGoodsService {
private final ISysConfigService configService;
private final ZBTService zbtService;
private final TtUserMapper userMapper;
private final TtOrnamentMapper ornamentsMapper;
private final TtBoxRecordsMapper boxRecordsMapper;
private final TtDeliveryRecordMapper deliveryRecordMapper;
//@Autowired
private YYClient yyClient;
@Autowired
private TtYyOrnamentsService ttYyOrnamentsService;
@Autowired
private TtOrnamentService ttOrnamentService;
@Autowired
private TtOrnamentYYService ttOrnamentYYService;
@Autowired
private TtOrnamentsYYMapper ttOrnamentsYYMapper;
@Autowired
private yyyoupingService yyyoupingService;
@Autowired
private V5ItemService v5ItemService;
@Autowired
private Cs340Service cs340Service;
public DeliverGoodsServiceImpl(ISysConfigService configService,
ZBTService zbtService,
TtUserMapper userMapper,
TtOrnamentMapper ornamentsMapper,
TtBoxRecordsMapper boxRecordsMapper,
TtDeliveryRecordMapper deliveryRecordMapper) {
this.configService = configService;
this.zbtService = zbtService;
this.userMapper = userMapper;
this.ornamentsMapper = ornamentsMapper;
this.boxRecordsMapper = boxRecordsMapper;
this.deliveryRecordMapper = deliveryRecordMapper;
}
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public R getAvailableMarketList(Integer ornamentsId,Integer partyType) {
// 从平台获取的数据集
List<AvailableMarket> resList = null;
if (partyType.equals(PartyType.ZBT.getCode())){
// 获取饰品信息
TtOrnament ornament = new LambdaQueryChainWrapper<>(ornamentsMapper)
.eq(TtOrnament::getId, ornamentsId)
.one();
// 构建查询参数
ProductListParams apiParm = ProductListParams.builder()
.itemId(String.valueOf(ornament.getId()))
.build();
// 查询接口
ResultZbt<AvailableMarketList> productList = zbtService.productList(apiParm);
if (!productList.getSuccess()) {
log.error("调用ZBT获取某一个饰品的所有在售接口异常请检查代码是否正确");
return null;
}
// 过滤可用数据
AvailableMarketList availableMarketList = productList.getData();
resList = availableMarketList.getList();
}else if (partyType.equals(PartyType.YY_YOU_PING.getCode())){
// 获取饰品信息
TtOrnament ornament = new LambdaQueryChainWrapper<>(ornamentsMapper)
.eq(TtOrnament::getId, ornamentsId)
.one();
// 构建查询参数
ProductListParams apiParm = ProductListParams.builder()
.itemId(String.valueOf(ornament.getId()))
.build();
// 查询接口
ResultZbt<AvailableMarketList> productList = zbtService.productList(apiParm);
if (!productList.getSuccess()) {
log.error("调用ZBT获取某一个饰品的所有在售接口异常请检查代码是否正确");
return null;
}
// 过滤可用数据
AvailableMarketList availableMarketList = productList.getData();
resList = availableMarketList.getList();
}
if (ObjectUtil.isEmpty(resList)) return R.fail("平台没有该物品在售信息。");
// 构建返回结果
List<OnSaleOrnamentVO> resultList = new ArrayList<>();
// 提取可用数据
for (AvailableMarket availableMarket : resList) {
OnSaleOrnamentVO data = OnSaleOrnamentVO.builder().build();
data.setId(availableMarket.getId());
data.setDelivery(availableMarket.getDelivery());
data.setItemName(availableMarket.getItemName());
data.setImageUrl(availableMarket.getImageUrl());
data.setCnyPrice(availableMarket.getCnyPrice());
data.setPrice(availableMarket.getPrice());
resultList.add(data);
}
return R.ok(resultList);
}
@Override
public R tradeBuy(TradeBuyParam param) {
// 获取提货订单数据
TtDeliveryRecord ttDeliveryRecord = deliveryRecordMapper.selectById(param.getDeliveryRecordId());
// 获取用户数据
TtUser ttUser = userMapper.selectById(ttDeliveryRecord.getUserId());
// 初始化数据
String transactionLink = ttUser.getTransactionLink();
if (StringUtils.isEmpty(transactionLink)) {
return R.fail("该用户未绑定Steam交易链接");
}
String outTradeNo = ttDeliveryRecord.getOutTradeNo();
if (param.getPartyType().equals(PartyType.ZBT.getCode())){
// 构建购买参数
NormalBuyParamV2DTO normalBuyParamV2DTO = new NormalBuyParamV2DTO();
normalBuyParamV2DTO.setOutTradeNo(outTradeNo);
normalBuyParamV2DTO.setProductId(param.getProductId().toString());
normalBuyParamV2DTO.setTradeUrl(transactionLink);
// 购买
ResultZbt<OpenBuyResultDTO> tradeBuy = zbtService.tradeBuy(normalBuyParamV2DTO);
if (!tradeBuy.getSuccess()) {
return R.ok(tradeBuy.getErrorMsg());
}
// 获取结果数据
OpenBuyResultDTO data = tradeBuy.getData();
System.err.println("结果数据:" + data);
// 更新提货订单数据
ttDeliveryRecord.setBuyPrice(data.getBuyPrice());
ttDeliveryRecord.setThirdpartyDelivery(data.getDelivery());
ttDeliveryRecord.setOrderId(data.getOrderId());
ttDeliveryRecord.setStatus(DELIVERY_BEFORE.getCode()); // 发货订单状态 1待发货
ttDeliveryRecord.setMessage(DELIVERY_BEFORE.getMsg());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return R.ok();
}
return R.fail("更新提货订单数据异常!");
}else if (param.getPartyType().equals(PartyType.V5ITEM.getCode())){
LambdaQueryWrapper<TtOrnament> ttOrnamentsQuery = new LambdaQueryWrapper<>();
ttOrnamentsQuery.eq(TtOrnament::getId, param.getProductId());
TtOrnament ttOrnament = ttOrnamentService.getOne(ttOrnamentsQuery);
CreateOrderBody createOrderBody = new CreateOrderBody();
createOrderBody.setMarketHashName(ttOrnament.getMarketHashName());
BigDecimal purchasePrice = param.getPrice(); //.multiply(new BigDecimal("2"));
createOrderBody.setPurchasePrice(purchasePrice);
createOrderBody.setTradeUrl(transactionLink);
createOrderBody.setMerchantOrderNo(outTradeNo);
V5ItemResult<CreateOrderResult> createOrderResult = v5ItemService.createOrder(createOrderBody);
if (createOrderResult.getCode() != 0) {
return R.fail(createOrderResult.getMsg());
}
// 更新提货订单数据
ttDeliveryRecord.setBuyPrice(createOrderResult.getData().getPayAmount());
ttDeliveryRecord.setThirdpartyDelivery(1);
ttDeliveryRecord.setOrderId(createOrderResult.getData().getOrderNo());
ttDeliveryRecord.setStatus(DELIVERY_BEFORE.getCode()); // 发货订单状态 1待发货
ttDeliveryRecord.setMessage(DELIVERY_BEFORE.getMsg());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return R.ok();
}
return R.ok("正在开发");
} else if (Objects.equals(param.getPartyType(), PartyType.CS340.getCode())) {
LambdaQueryWrapper<TtOrnament> ttOrnamentsQuery = new LambdaQueryWrapper<>();
ttOrnamentsQuery.eq(TtOrnament::getId, param.getProductId());
TtOrnament ttOrnament = ttOrnamentService.getOne(ttOrnamentsQuery);
BigDecimal purchasePrice = param.getPrice();// .multiply(new BigDecimal("2"));
ByTemplateCreateOrder createOrderBody = new ByTemplateCreateOrder();
createOrderBody.setPurchasePrice(purchasePrice.toString());
createOrderBody.setTradeLinks(transactionLink);
createOrderBody.setMerchantOrderNo(outTradeNo);
createOrderBody.setCommodityHashName(ttOrnament.getMarketHashName());
Cs340Result<ByTemplateCreateOrderResult> createOrderResult = cs340Service.createOrder(createOrderBody);
if (createOrderResult.getCode() != 0) {
return R.fail(createOrderResult.getMsg());
}
// 更新提货订单数据
ttDeliveryRecord.setBuyPrice(createOrderResult.getData().getPayAmount());
ttDeliveryRecord.setThirdpartyDelivery(1);
ttDeliveryRecord.setOrderId(createOrderResult.getData().getOrderNo());
ttDeliveryRecord.setStatus(DELIVERY_BEFORE.getCode()); // 发货订单状态 1待发货
ttDeliveryRecord.setMessage(DELIVERY_BEFORE.getMsg());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return R.ok();
}
return R.ok("正在开发");
} else if (param.getPartyType().equals(PartyType.YY_YOU_PING.getCode())){
return R.ok("暂未开放。");
} else {
return R.fail("非法的第三方平台代码");
}
}
@Override
@Transactional
public String synchronousStatus(String outTradeNo) {
// 获取提货数据
TtDeliveryRecord ttDeliveryRecord = new LambdaQueryChainWrapper<>(deliveryRecordMapper)
.eq(TtDeliveryRecord::getOutTradeNo, outTradeNo).one();
if (10 == ttDeliveryRecord.getStatus() || 11 == ttDeliveryRecord.getStatus()) {
return "";
}
// 构建查询参数
Cs340Result<Cs340OrderStatusResult> orderStatusResult = cs340Service.queryOrderStatus(ttDeliveryRecord.getOrderId());
if (orderStatusResult == null|| orderStatusResult.getData() == null || orderStatusResult.getCode() != 0 ) {
return "查询购买订单详情失败";
}
// 获取结果数据
Integer status = orderStatusResult.getData().getBigStatus();
if (Objects.equals(status, 2) || Objects.equals(status, 280)){
ttDeliveryRecord.setStatus(status);
ttDeliveryRecord.setMessage(orderStatusResult.getData().getStatusMsg());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
// 将用户提货饰品返回用户背包
TtBoxRecords ttBoxRecords = new LambdaQueryChainWrapper<>(boxRecordsMapper)
.eq(TtBoxRecords::getId, ttDeliveryRecord.getBoxRecordsId())
.eq(TtBoxRecords::getHolderUserId, ttDeliveryRecord.getUserId())
.eq(TtBoxRecords::getOrnamentId, ttDeliveryRecord.getOrnamentId())
.eq(TtBoxRecords::getStatus, "2") // 状态 2已提取
.one();
if (StringUtils.isNull(ttBoxRecords)) {
return "未查询到该用户背包的饰品信息!";
}
ttBoxRecords.setStatus(TtboxRecordStatus.IN_PACKSACK_ON.getCode()); // 状态 0背包
ttBoxRecords.setUpdateTime(DateUtils.getNowDate());
boxRecordsMapper.updateById(ttBoxRecords);
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return "";
}
}
ttDeliveryRecord.setMessage(orderStatusResult.getData().getStatusMsg());
if (Objects.equals(status, 340)) {
ttDeliveryRecord.setStatus(DeliveryOrderStatus.ORDER_COMPLETE.getCode());
} else {
ttDeliveryRecord.setStatus(DeliveryOrderStatus.DELIVERY_AFTER.getCode());
}
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) return "";
return "同步提货订单数据异常!";
}
/**
* 保留V5历史同步订单状态的代码逻辑后面再对接V5的话及时打开
*/
private String synchronousStatusForV5(String outTradeNo) {
// 获取提货数据
TtDeliveryRecord ttDeliveryRecord = new LambdaQueryChainWrapper<>(deliveryRecordMapper)
.eq(TtDeliveryRecord::getOutTradeNo, outTradeNo).one();
if (10 == ttDeliveryRecord.getStatus() || 11 == ttDeliveryRecord.getStatus()) {
return "";
}
// 构建查询参数
OrderBuyDetailParams orderBuyDetailParams = new OrderBuyDetailParams();
orderBuyDetailParams.setOutTradeNo(ttDeliveryRecord.getOutTradeNo());
QueryOrderStatusResult orderStatusResult = v5ItemService.queryOrderStatus(ttDeliveryRecord.getOutTradeNo(), ttDeliveryRecord.getOrderId());
if (orderStatusResult == null) {
return "查询购买订单详情V2失败";
}
// 获取结果数据
Integer status = orderStatusResult.getStatus();
if (Objects.equals(status, 4)){
ttDeliveryRecord.setStatus(status);
ttDeliveryRecord.setMessage(orderStatusResult.getStatusMsg());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
// 将用户提货饰品返回用户背包
TtBoxRecords ttBoxRecords = new LambdaQueryChainWrapper<>(boxRecordsMapper)
.eq(TtBoxRecords::getId, ttDeliveryRecord.getBoxRecordsId())
.eq(TtBoxRecords::getHolderUserId, ttDeliveryRecord.getUserId())
.eq(TtBoxRecords::getOrnamentId, ttDeliveryRecord.getOrnamentId())
.eq(TtBoxRecords::getStatus, "2") // 状态 2已提取
.one();
if (StringUtils.isNull(ttBoxRecords)) {
return "未查询到该用户背包的饰品信息!";
}
ttBoxRecords.setStatus(TtboxRecordStatus.IN_PACKSACK_ON.getCode()); // 状态 0背包
ttBoxRecords.setUpdateTime(DateUtils.getNowDate());
boxRecordsMapper.updateById(ttBoxRecords);
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return "";
}
} else {
ttDeliveryRecord.setMessage(orderStatusResult.getStatusMsg());
// 发送发货成功通知
Map<String, String> noticeMap = new HashMap<>();
noticeMap.put("userId", ttDeliveryRecord.getUserId().toString());
noticeMap.put("title", "发货成功通知");
noticeMap.put("content", "发货成功,请注意查收。");
noticeMap.put("createTime", DateUtils.getTime());
rabbitTemplate.convertAndSend("notice_queue", noticeMap);
}
ttDeliveryRecord.setStatus(DeliveryOrderStatus.DELIVERY_AFTER.getCode());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) return "";
return "同步提货订单数据异常!";
}
@Override
public int syncAllStatus() {
List<TtDeliveryRecord> records = new LambdaQueryChainWrapper<>(deliveryRecordMapper)
.eq(TtDeliveryRecord::getStatus, 1)
.list();
for (TtDeliveryRecord record : records) {
String msg = synchronousStatus(record.getOutTradeNo());
}
return records.size();
}
@Override
public void autoDelivery(Integer userId) {
// 获取需要自动发货的数据
List<TtDeliveryRecord> deliveryRecordList = new LambdaQueryChainWrapper<>(deliveryRecordMapper)
.eq(TtDeliveryRecord::getUserId, userId)
.eq(TtDeliveryRecord::getDelivery, "2") // 网站发货模式1人工发货 2自动发货 3主播号提取
.eq(TtDeliveryRecord::getStatus, "0") // 发货订单状态0发起提货 1待发货 3待收货 10订单完成 11订单取消
.list();
if (StringUtils.isNull(deliveryRecordList) || deliveryRecordList.isEmpty()) {
log.info("{}号用户发起提货,无自动发货饰品,请在后台手动发货!", userId);
return;
}
// 饰品购买价格溢价率
String buyPricePremiumRateStr = configService.selectConfigByKey("buyPricePremiumRate");
BigDecimal buyPricePremiumRate = new BigDecimal(buyPricePremiumRateStr);
// ZBT平台币种汇率
String ZBTParitiesStr = configService.selectConfigByKey("ZBTParities");
BigDecimal ZBTParities = new BigDecimal(ZBTParitiesStr);
// 快速购买
for (TtDeliveryRecord ttDeliveryRecord : deliveryRecordList) {
// 获取用户信息
TtUser ttUser = userMapper.selectById(ttDeliveryRecord.getUserId());
// 获取饰品信息
TtOrnament ttOrnament = ornamentsMapper.selectById(ttDeliveryRecord.getOrnamentId());
// 计算可接受最高价
BigDecimal ornamentsPrice = ttDeliveryRecord.getOrnamentsPrice(); // 饰品价格
BigDecimal price = ornamentsPrice.divide(ZBTParities, 2, RoundingMode.HALF_UP).multiply(buyPricePremiumRate);
// 构建购买参数
QuickBuyParamV2DTO quickBuyParamV2DTO = new QuickBuyParamV2DTO();
// TODO: 2024/3/30 ttOrnament.getId()
quickBuyParamV2DTO.setItemId(String.valueOf(ttOrnament.getId()));
quickBuyParamV2DTO.setMaxPrice(price.toString()); // 购买a可以接受的最高价格(T币)
quickBuyParamV2DTO.setOutTradeNo(ttDeliveryRecord.getOutTradeNo());
quickBuyParamV2DTO.setTradeUrl(ttUser.getTransactionLink());
ResultZbt<OpenBuyResultDTO> tradeQuickBuy = zbtService.tradeQuickBuy(quickBuyParamV2DTO);
if (!tradeQuickBuy.getSuccess()) {
log.error("快速购买接口v2异常订单号{}", ttDeliveryRecord.getOutTradeNo());
continue;
}
// 获取结果数据
OpenBuyResultDTO data = tradeQuickBuy.getData();
// 更新提货订单数据
ttDeliveryRecord.setBuyPrice(data.getBuyPrice());
ttDeliveryRecord.setThirdpartyDelivery(data.getDelivery());
ttDeliveryRecord.setOrderId(data.getOrderId());
ttDeliveryRecord.setStatus(DELIVERY_BEFORE.getCode()); // 发货订单状态 1待发货
ttDeliveryRecord.setMessage("等待发货");
ttDeliveryRecord.setUpdateBy("自动发货");
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
// 更新状态
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
log.info("订单'{}'自动发货成功,等待卖家发货中......", ttDeliveryRecord.getOutTradeNo());
}
}
}
public List<AvailableMarketOrnamentVO> ZBTGetAvailable(String marketHashName, ProductListParams productListParams){
LambdaQueryWrapper<TtOrnament> ttOrnamentsQuery = new LambdaQueryWrapper<>();
ttOrnamentsQuery.eq(TtOrnament::getMarketHashName, marketHashName);
TtOrnament ornament = ttOrnamentService.getOne(ttOrnamentsQuery);
// zbt所有数据
productListParams.setItemId(String.valueOf(ornament.getId()));
ResultZbt<AvailableMarketList> availableZbt = zbtService.productList(productListParams);
if (!availableZbt.getSuccess()){
log.warn(availableZbt.getErrorMsg());
return null;
}
AvailableMarketList data = availableZbt.getData();
return data.getList().stream().map(item->{
return AvailableMarketOrnamentVO.builder()
.platform(Platform.ZBT.getCode())
.itemId(item.getItemId())
.delivery(item.getDelivery())
.itemName(item.getItemName())
.imageUrl(item.getImageUrl())
.cnyPrice(item.getCnyPrice())
.price(item.getPrice())
.build();
}).collect(Collectors.toList());
}
public List<AvailableMarketOrnamentVO> YYGetAvailable(String marketHashName, ProductListParams productListParams){
LambdaQueryWrapper<TtYYOrnaments> ttYYOrnamentsQuery = new LambdaQueryWrapper<>();
ttYYOrnamentsQuery.eq(TtYYOrnaments::getHashName,marketHashName);
TtYYOrnaments yyOrnament = ttYyOrnamentsService.getOne(ttYYOrnamentsQuery);
HashMap<String, Object> map = new HashMap<>();
JSONObject o = new JSONObject();
o.put("templateId",yyOrnament.getId());
log.info("obj="+o.toString());
map.put("requestList", ListUtil.of(o));
YYResult yyResult = yyyoupingService.yyApiBatchGetOnSaleCommodityInfo(map);
Object dataJson = yyResult.getData();
ObjectMapper objectMapper = new ObjectMapper();
queryTemplateSaleByCategoryDataVO dataVO;
try {
dataVO = objectMapper.readValue(JSON.toJSONString(dataJson), queryTemplateSaleByCategoryDataVO.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
List<queryTemplateSaleByCategoryVO> voList = dataVO.getSaleTemplateByCategoryResponseList();
List<AvailableMarketOrnamentVO> collect = voList.stream().map(item -> {
return AvailableMarketOrnamentVO.builder()
.platform(Platform.YY.getCode())
.templateId(item.getTemplateId())
.imageUrl(item.getIconUrl())
.build();
}).collect(Collectors.toList());
return null;
}
@Override
public List<AvailableMarketOrnamentVO> getAvailableMarketListByHashName(String marketHashName, ProductListParams productListParams) {
// zbt所有数据
List<AvailableMarketOrnamentVO> ZBTGetAvailable = ZBTGetAvailable(marketHashName, productListParams);
// yy所有数据
List<AvailableMarketOrnamentVO> YYGetAvailable = YYGetAvailable(marketHashName, productListParams);
// 合并所有数据
return null;
// // 获取饰品itemId
// TtOrnaments ornaments = new LambdaQueryChainWrapper<>(ornamentsMapper).eq(TtOrnaments::getId, ornamentsId).one();
// // 构建查询参数
// productListParams.setItemId(ornaments.getItemId());
// ResultZbt<AvailableMarketList> productList = zbtService.productList(productListParams); // 查询接口
// if (!productList.getSuccess()) {
// log.error("调用ZBT获取某一个饰品的所有在售接口异常请检查代码是否正确");
// return null;
// }
// AvailableMarketList availableMarketList = productList.getData();
// // 查询到的数据
// List<AvailableMarket> list = availableMarketList.getList();
// // 构建返回结果
// List<AvailableMarketOrnamentsListDataVO> resultList = new ArrayList<>();
// // 提取可用数据
// for (AvailableMarket availableMarket : list) {
// AvailableMarketOrnamentsListDataVO data = AvailableMarketOrnamentsListDataVO.builder().build();
// data.setId(availableMarket.getId());
// data.setDelivery(availableMarket.getDelivery());
// data.setItemName(availableMarket.getItemName());
// data.setImageUrl(availableMarket.getImageUrl());
// data.setCnyPrice(availableMarket.getCnyPrice());
// data.setPrice(availableMarket.getPrice());
// resultList.add(data);
// }
// return resultList;
}
@Override
public R getAvailableMarketList(GetAvailableMarketListParam param) {
// 返回结果
List<OnSaleOrnamentVO> resultList = new ArrayList<>();
// 获取饰品信息(这里暂时不支持批量查询)
List<TtOrnament> list = new LambdaQueryChainWrapper<>(ornamentsMapper)
.in(TtOrnament::getId, param.getOrnamentsId().get(0))
.list();
if (param.getPartyType().equals(PartyType.ZBT.getCode())){
for (TtOrnament item: list){
if (ObjectUtil.isEmpty(item.getZbtId())) continue;
// 构建查询参数
ProductListParams apiParm = ProductListParams.builder()
.itemId(String.valueOf(item.getZbtId()))
.build();
// 查询接口
ResultZbt<AvailableMarketList> productList = zbtService.productList(apiParm);
if (!productList.getSuccess()) {
log.error("调用ZBT获取某一个饰品的所有在售接口异常请检查代码是否正确");
return R.fail("调用ZBT获取某一个饰品的所有在售接口异常");
}
// 过滤可用数据
AvailableMarketList availableMarketList = productList.getData();
List<AvailableMarket> list1 = availableMarketList.getList();
if (ObjectUtil.isEmpty(list1)) {
return R.fail("平台没有该物品在售信息。");
}
// 提取可用数据
for (AvailableMarket availableMarket : list1) {
OnSaleOrnamentVO data = OnSaleOrnamentVO.builder().build();
data.setPartyType(PartyType.ZBT.getCode());
data.setId(availableMarket.getId());
data.setDelivery(availableMarket.getDelivery());
data.setItemName(availableMarket.getItemName());
data.setImageUrl(availableMarket.getImageUrl());
data.setCnyPrice(availableMarket.getCnyPrice());
data.setPrice(availableMarket.getPrice());
resultList.add(data);
}
}
} else if (param.getPartyType().equals(PartyType.YY_YOU_PING.getCode())){
// 构建查询参数
HashMap<String, Object> apiParam = new HashMap<>();
ArrayList<Map<String,String>> ids = new ArrayList<>();
for (TtOrnament item : list){
Map<String, String> i = MapUtil.builder("templateId", String.valueOf(item.getYyyoupingId())).build();
ids.add(i);
}
apiParam.put("requestList",ids);
// 查询接口
YYResult yyResult = yyyoupingService.yyApiBatchGetOnSaleCommodityInfo(apiParam);
if (!yyResult.isSuccess()) {
log.error("调用ZBT获取某一个饰品的所有在售接口异常请检查代码是否正确");
return R.fail("调用YY获取某一个饰品的所有在售接口异常");
}
// 过滤可用数据
List<String> data = JSONUtil.toList(JSON.toJSONString(yyResult.getData()), String.class);
resultList = data.stream().map(item->{
cn.hutool.json.JSONObject obj = JSONUtil.parseObj(item);
cn.hutool.json.JSONObject saleTemplateResponse = obj.get("saleTemplateResponse", cn.hutool.json.JSONObject.class);
cn.hutool.json.JSONObject saleCommodityResponse = obj.get("saleCommodityResponse", cn.hutool.json.JSONObject.class);
return OnSaleOrnamentVO.builder()
.partyType(PartyType.YY_YOU_PING.getCode())
.id(saleTemplateResponse.get("templateId",String.class))
.itemName(saleCommodityResponse.get("name",String.class))
.imageUrl(list.get(0).getImageUrl())
.price(saleCommodityResponse.get("minSellPrice",BigDecimal.class))
.build();
}).collect(Collectors.toList());
}
return R.ok(resultList);
}
@Override
public R tradeConfirm(TradeManualConfirmParam param) {
// 获取提货订单数据
TtDeliveryRecord ttDeliveryRecord = deliveryRecordMapper.selectById(param.getDeliveryRecordId());
// 更新提货订单数据
ttDeliveryRecord.setBuyPrice(param.getPrice());
ttDeliveryRecord.setThirdpartyDelivery(1);
ttDeliveryRecord.setOrderId(param.getOrderNo());
ttDeliveryRecord.setStatus(param.getStatus()); // 发货订单状态 1待发货
ttDeliveryRecord.setMessage(param.getMessage());
ttDeliveryRecord.setUpdateBy(SecurityUtils.getUsername());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
if (deliveryRecordMapper.updateById(ttDeliveryRecord) > 0) {
return R.ok();
}
return R.fail("更新提货订单数据异常!");
}
}

View File

@@ -0,0 +1,55 @@
package com.ruoyi.thirdparty.common.service.impl;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.thirdparty.common.service.ApiNoticeService;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class RechargeSuccessfulNoticeServiceImpl implements RechargeSuccessfulNoticeService {
/*@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void sendRechargeSuccessNotice(String userId, BigDecimal amount) {
try {
Map<String, String> noticeMap = new HashMap<>();
noticeMap.put("userId", userId);
noticeMap.put("title", "充值到账通知");
noticeMap.put("content", "您充值" + amount + "已到账,请注意查收。");
noticeMap.put("createTime", DateUtils.getTime());
rabbitTemplate.convertAndSend("notice_queue", noticeMap);
} catch (Exception e) {
log.error("happen exception is {}", e.getMessage(), e);
}
}*/
@Autowired
@Qualifier("thirdpartyApiNoticeServiceImpl")
private ApiNoticeService apiNoticeService;
@Override
public void sendRechargeSuccessNotice(String userId, BigDecimal amount) {
AsyncManager.me().run(() -> {
try {
String title = "充值到账通知";
String content = "您充值" + amount + "已到账,请注意查收。";
apiNoticeService.sendNotice(Long.valueOf(userId), title, content);
} catch (Exception e) {
log.error("发送充值通知失败userId={}, error={}", userId, e.getMessage(), e);
}
});
}
}

View File

@@ -0,0 +1,606 @@
package com.ruoyi.thirdparty.common.task;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.math.MathUtil;
import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.admin.service.ConfigService;
import com.ruoyi.admin.service.TtOrnamentService;
import com.ruoyi.admin.service.TtOrnamentYYService;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.domain.common.constant.PartyType;
import com.ruoyi.domain.dto.yyyouping.OrnamentYY;
import com.ruoyi.domain.dto.zbt.OrnamentZBT;
import com.ruoyi.domain.entity.TtOrnament;
import com.ruoyi.system.mapper.SysDictDataMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.common.NetworkDataLoader.NetworkDataLoader;
import com.ruoyi.thirdparty.yyyouping.service.yyyoupingService;
import com.ruoyi.thirdparty.yyyouping.utils.common.YYResult;
import com.ruoyi.thirdparty.zbt.param.SearchParams;
import com.ruoyi.thirdparty.zbt.result.ResultZbt;
import com.ruoyi.thirdparty.zbt.result.product.SearchData;
import com.ruoyi.thirdparty.zbt.service.ZBTService;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.SQLIntegrityConstraintViolationException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import static com.ruoyi.domain.common.constant.PartyType.YY_YOU_PING;
import static com.ruoyi.domain.common.constant.PartyType.ZBT;
@Slf4j
// 项目启动加载所有饰品信息
//@Component
public class LoadAllOrnaments implements ApplicationRunner {
// @Value("${yy-youping.ornamentsFilePath}")
// private String YYOrnamentsFilePath;
//
// @Autowired
// private yyyoupingService yyyoupingService;
@Autowired
private TtOrnamentService ttOrnamentService;
@Value("${mkcsgo.startLoadOrnaments}")
private Boolean startLoadOrnaments;
// 资源加载器集合
@Autowired
private List<NetworkDataLoader> networkDataLoaderList;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("是否加载网络ornaments资源{}", startLoadOrnaments);
if (!startLoadOrnaments) return;
// 1 加载各个平台数据
Set<TtOrnament> ttOrnaments = new HashSet<>();
for (NetworkDataLoader loader : networkDataLoaderList) {
Set<TtOrnament> load = loader.load();
ttOrnaments.addAll(load);
}
if (ObjectUtil.isEmpty(ttOrnaments) || ttOrnaments.isEmpty()) {
log.warn("加载饰品网络资源失败。");
return;
}
// 2 写db
log.info("db allData ing~~~");
for (TtOrnament item : ttOrnaments) {
try {
ttOrnamentService.saveOrUpdate(item);
} catch (DuplicateKeyException e) {
// log.info("过滤重复数据 【id】{}【hash】{}save。",item.getId(),item.getMarketHashName());
} catch (Exception e) {
e.printStackTrace();
log.info("保存数据异常。");
break;
}
}
log.info("加载饰品网络资源成功。");
}
// 从平台获取数据
// private Set<TtOrnament> loadOrnamentFromNet(PartyType[] partyTypes) {
//
// // 加载必要的枚举数据
// List<String> dictTypes = Arrays.asList(
// "ornaments_type", "ornaments_type_name",
// "ornaments_exterior", "ornaments_exterior_name",
// "ornaments_quality", "ornaments_quality_name",
// "ornaments_rarity", "ornaments_rarity_name");
// List<SysDictData> enumList = dictDataMapper.selectDictDataByTypes(dictTypes);
//
// Set<TtOrnament> res = null;
// for (NetworkDataLoader loader:networkDataLoaderList){
// Set<TtOrnament> load = loader.load();
// res.addAll(load);
// }
//
// return res;
//
// //1 加载ZBT平台数据
//
// //... 加载...平台数据
//
// //2 合并所有数据
//
// // //1 从一个平台获取第一组基础数据
// // Set<TtOrnament> baseData = loadBaseData(partyTypes[1],enumList);
// // if (ObjectUtil.isEmpty(baseData) || baseData.isEmpty()) return baseData;
// //
// // //2 整合其他平台
// // return mergeOtherParty(baseData, partyTypes, enumList);
//
// }
//
// public Set<TtOrnament> mergeOtherParty(Set<TtOrnament> baseData,PartyType[] partyTypes ,List<SysDictData> enumList){
//
// // Integer f = 0;
// for (int i = 1; i < partyTypes.length; i++) {
// if (partyTypes[i].equals(YY_YOU_PING)) {
//
// YYResult yyResult = yyyoupingService.yyApiTemplateQuery(new HashMap<String, Object>());
// String fileUrl = (String) yyResult.getData();
// log.info(fileUrl);
//
// // 下载文件
// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
// String format = dateFormat.format(System.currentTimeMillis());
// String fileName = YYOrnamentsFilePath + "yy_orn_" + format + ".txt";
//
// long size = HttpUtil.downloadFile(fileUrl, FileUtil.file(fileName));
// log.info("yy饰品信息下载成功文件大小: " + size);
//
// // 解析json
// File file = FileUtil.file(fileName);
// JSONArray jsonArray = JSONUtil.readJSONArray(file, StandardCharsets.UTF_8);
// List<OrnamentYY> yyList = jsonArray.toList(OrnamentYY.class);
//
// // 转换为TtOrnament
// Set<TtOrnament> collect = yyList.stream().map(item -> {
//
// return TtOrnament.builder()
// .id(Long.valueOf(item.getHashName().hashCode()))
// .yyyoupingId(item.getId())
// .marketHashName(item.getHashName())
// .name(item.getName())
// .price(BigDecimal.TEN) //// TODO: 2024/4/1 补充价格信息
// .usePrice(BigDecimal.TEN)
// .updateTime(item.getUpdateTime())
// .build();
//
// }).collect(Collectors.toSet());
//
// baseData.addAll(collect);
//
// } else if (partyTypes[i].equals(ZBT)) {
//
// // 饰品类型枚举
// //List<SysDictData> typeList = dictDataMapper.selectDictDataByType("ornaments_type");
//
// for (SysDictData type : enumList) {
//
// if (!type.getDictType().equals("ornaments_type")) continue;
//
// Set<TtOrnament> ttOrnaments = ZBTLoadOrnamentByType(type.getDictLabel(),type,enumList);
//
// baseData.addAll(ttOrnaments);
//
// log.info("合并其他平台{}条数据。",ttOrnaments.size());
//
// }
//
// } else {
// log.warn("非法的平台类型代码,获取饰品数据失败。");
// }
// }
//
// // 过滤zbt没有的数据
// log.info("过滤数据");
// Set<TtOrnament> resData = baseData.stream().filter(item -> {
// return ObjectUtil.isNotEmpty(item.getZbtId());
// }).collect(Collectors.toSet());
//
// return resData;
//
// }
//
// // zbt 根据饰品类型获取数据
// private Set<TtOrnament> ZBTLoadOrnamentByType(String typeHash,SysDictData type,List<SysDictData> allEnum){
//
// HashSet<TtOrnament> result = new HashSet<>();
//
// // 分批请求api
// int page = 1;
// boolean stopFlag = false;
// while (true) {
//
// // 结束标记
// if (stopFlag) break;
//
// // 请求平台数据
// SearchParams param = SearchParams.builder()
// .page(String.valueOf(page))
// .category(typeHash)
// .limit("200")
// .build();
// ResultZbt<SearchData> response = zbtService.search(param);
// SearchData data = response.getData();
// if (!response.getSuccess() || ObjectUtil.isEmpty(data)) break;
//
// List<OrnamentZBT> list = JSONUtil.toList(JSONUtil.toJsonStr(data.getList()), OrnamentZBT.class);
//
// if (list.size() < 200) stopFlag = !stopFlag;
//
// // 转换为通用类型
// Set<TtOrnament> collect = list.stream().map(item -> {
//
// Long id = Long.valueOf(HashUtil.rsHash(item.getMarketHashName()));
// if (id < 0) id = Math.abs(id);
//
// TtOrnament ornament = TtOrnament.builder()
// .id(id)
// .zbtId(item.getItemId())
// .marketHashName(item.getMarketHashName())
// .name(item.getItemName())
// .shortName(item.getShortName())
//
// .price(ObjectUtil.isNotEmpty(item.getPriceInfo()) ? item.getPriceInfo().getPrice() : null)
// .usePrice(ObjectUtil.isNotEmpty(item.getPriceInfo()) ? item.getPriceInfo().getPrice() : null)
// .quantity(ObjectUtil.isNotEmpty(item.getPriceInfo()) ? item.getPriceInfo().getQuantity() : null)
//
// .imageUrl(item.getImageUrl())
//
// .type(type.getDictValue())
// .typeName(item.getTypeName())
//
// .quality(getCodeByNameOrHash(allEnum,item.getQuality()))
// .qualityName(item.getQualityName())
// .qualityColor(item.getQualityColor())
//
// .rarity(getCodeByNameOrHash(allEnum,item.getRarity()))
// .rarityName(item.getRarityName())
// .rarityColor(item.getRarityColor())
//
// .exterior(getCodeByNameOrHash(allEnum,item.getExterior()))
// .exteriorHashName(item.getExterior())
// .exteriorName(item.getExteriorName())
//
// .updateTime(item.getUpdateTime())
// .build();
//
// ornament.setUsePrice(ornament.getPrice());
//
// return ornament;
// }).collect(Collectors.toSet());
//
// result.addAll(collect);
//
// // log.info("加载【扎比特】{}条类型为{}的饰品数据。",page*collect.size(),typeHash);
// System.out.print("\r加载【扎比特】"+page*collect.size()+"条类型为"+typeHash+"的饰品数据。");
//
// page++;
// }
//
// return result;
//
// }
//
// // 从yy平台获取第一组基础数据
// private Set<TtOrnament> loadBaseData(PartyType partyType,List<SysDictData> allEnum) {
//
// Set<TtOrnament> baseData = null;
//
// if (partyType.equals(YY_YOU_PING)) {
// baseData = getBaseDataFromYY(allEnum);
// } else if (partyType.equals(ZBT)) {
//
// baseData = getBaseDataFromZBT(allEnum);
//
// } else {
// log.warn("非法的平台类型代码,获取饰品数据失败。");
// return baseData;
// }
//
// return baseData;
// }
//
// // 从ZBT获取基础数据
// public Set<TtOrnament> getBaseDataFromZBT(List<SysDictData> allEnum){
//
// Set<TtOrnament> baseData = null;
//
// YYResult yyResult = yyyoupingService.yyApiTemplateQuery(new HashMap<String, Object>());
// if (!yyResult.isSuccess()) {
// log.warn(yyResult.getMsg());
// return baseData;
// }
// String fileUrl = (String) yyResult.getData();
// log.info("yyFile=" + fileUrl);
//
// // 下载文件
// String fileName = YYOrnamentsFilePath + "yyOrn" + System.currentTimeMillis() + ".txt";
// System.out.println(fileName);
//
// long size = HttpUtil.downloadFile(fileUrl, new File(fileName));
// log.info("yy饰品信息下载成功文件大小: " + size);
//
// // 解析json
// File file = FileUtil.file(fileName);
// JSONArray jsonArray = JSONUtil.readJSONArray(file, StandardCharsets.UTF_8);
// List<OrnamentYY> yyList = jsonArray.toList(OrnamentYY.class);
//
// log.info("补充详细信息~~~");
// // 转换为TtOrnament
// baseData = yyList.stream().map(item -> {
//
// Long id = Long.valueOf(HashUtil.rsHash(item.getHashName()));
// if (id < 0) {
// id = Math.abs(id);
// }
//
// TtOrnament ornament = TtOrnament.builder()
// .id(id)
// .yyyoupingId(item.getId())
// .marketHashName(item.getHashName())
//
// .type(getCodeByNameOrHash(allEnum,item.getTypeHashName()))
// .typeName(item.getTypeName())
// .typeHashName(item.getTypeHashName())
//
// .name(item.getName())
// .shortName(item.getName())
//
// .isPutaway("1")
// .isProprietaryProperty("1")
// .updateTime(item.getUpdateTime())
// .build();
//
// return ornament;
// }).collect(Collectors.toSet());
//
// // 分批补充 补充其他信息
// List<Map<String, String>> requestList = baseData.stream().map(item -> {
// return MapUtil.builder("templateId", String.valueOf(item.getYyyoupingId())).build();
// }).collect(Collectors.toList());
//
// int page = 0;
// int pageSize = 200;
// Boolean stopFlag = false;
// ArrayList<TtOrnament> ornamentsList = new ArrayList<>(baseData);
// while (true) {
//
// if (stopFlag) {
// System.out.println("-100% 补充完成✔");
// break;
// }
//
// BigDecimal p = new BigDecimal(page * pageSize)
// .divide(new BigDecimal(baseData.size()),2,BigDecimal.ROUND_DOWN);
// System.out.print("\r补充"+page * pageSize+"条基础记录"+p.multiply(new BigDecimal(100)).setScale(0)+"%");
//
//
// List<Map<String, String>> reqList = null;
// try {
// reqList = requestList.subList(page * pageSize, page * pageSize + pageSize); // 尽量大
// } catch (Exception e) {
// int lenth = requestList.size();
// reqList = requestList.subList(page * pageSize, lenth - 1); // 尽量大
// stopFlag = !stopFlag;
// }
//
// HashMap<String, Object> map = new HashMap<>();
// map.put("requestList", reqList);
//
// YYResult yyResult1 = yyyoupingService.yyApiBatchGetOnSaleCommodityInfo(map);
// List<JSONObject> list = JSONUtil.toList(JSONUtil.toJsonStr(yyResult1.getData()), JSONObject.class);
//
// for (JSONObject obj : list) {
//
// JSONObject saleTemplateResponse = JSONUtil.parseObj(obj.get("saleTemplateResponse"));
// JSONObject saleCommodityResponse = JSONUtil.parseObj(obj.get("saleCommodityResponse"));
//
// String templateId = String.valueOf((Integer) saleTemplateResponse.get("templateId"));
//
// for (int i = 0; i < ornamentsList.size(); i++) {
//
// TtOrnament ornament = ornamentsList.get(i);
// if (templateId.equals(String.valueOf(ornament.getYyyoupingId()))) {
//
// ornament.setPrice(new BigDecimal(saleCommodityResponse.get("minSellPrice").toString()));
// ornament.setUsePrice(new BigDecimal(saleCommodityResponse.get("minSellPrice").toString()));
//
// ornament.setImageUrl(saleTemplateResponse.get("iconUrl").toString());
//
// Object exteriorName = saleTemplateResponse.get("exteriorName");
// if (ObjectUtil.isNotNull(exteriorName)){
// ornament.setExterior(getCodeByNameOrHash(allEnum,exteriorName.toString()));
// ornament.setExteriorName(exteriorName.toString());
// }
//
// ornament.setQuality(getCodeByNameOrHash(allEnum,saleTemplateResponse.get("qualityName").toString()));
// ornament.setQualityName(saleTemplateResponse.get("qualityName").toString());
// // if (ObjectUtil.isEmpty(ornament.getQuality()) && ObjectUtil.isNotEmpty(ornament.getQualityName())){
// // System.out.println(ornament.getQuality());
// // System.out.println(ornament.getQualityName());
// // }
//
// ornament.setRarity(getCodeByNameOrHash(allEnum,saleTemplateResponse.get("rarityName").toString()));
// ornament.setRarityName(saleTemplateResponse.get("rarityName").toString());
//
// ornamentsList.set(i,ornament);
//
// break;
// }
// }
// }
//
// page++;
//
// }
//
// baseData = new HashSet<>(ornamentsList);
//
// return baseData;
// }
//
// // 从yy获取基础数据
// public Set<TtOrnament> getBaseDataFromYY(List<SysDictData> allEnum){
//
// Set<TtOrnament> baseData = null;
//
// YYResult yyResult = yyyoupingService.yyApiTemplateQuery(new HashMap<String, Object>());
// if (!yyResult.isSuccess()) {
// log.warn(yyResult.getMsg());
// return baseData;
// }
// String fileUrl = (String) yyResult.getData();
// log.info("yyFile=" + fileUrl);
//
// // 下载文件
// String fileName = YYOrnamentsFilePath + "yyOrn" + System.currentTimeMillis() + ".txt";
// System.out.println(fileName);
//
// long size = HttpUtil.downloadFile(fileUrl, new File(fileName));
// log.info("yy饰品信息下载成功文件大小: " + size);
//
// // 解析json
// File file = FileUtil.file(fileName);
// JSONArray jsonArray = JSONUtil.readJSONArray(file, StandardCharsets.UTF_8);
// List<OrnamentYY> yyList = jsonArray.toList(OrnamentYY.class);
//
// log.info("补充详细信息~~~");
// // 转换为TtOrnament
// baseData = yyList.stream().map(item -> {
//
// Long id = Long.valueOf(HashUtil.rsHash(item.getHashName()));
// if (id < 0) {
// id = Math.abs(id);
// }
//
// TtOrnament ornament = TtOrnament.builder()
// .id(id)
// .yyyoupingId(item.getId())
// .marketHashName(item.getHashName())
//
// .type(getCodeByNameOrHash(allEnum,item.getTypeHashName()))
// .typeName(item.getTypeName())
// .typeHashName(item.getTypeHashName())
//
// .name(item.getName())
// .shortName(item.getName())
//
// .isPutaway("1")
// .isProprietaryProperty("1")
// .updateTime(item.getUpdateTime())
// .build();
//
// return ornament;
// }).collect(Collectors.toSet());
//
// // 分批补充 补充其他信息
// List<Map<String, String>> requestList = baseData.stream().map(item -> {
// return MapUtil.builder("templateId", String.valueOf(item.getYyyoupingId())).build();
// }).collect(Collectors.toList());
//
// int page = 0;
// int pageSize = 200;
// Boolean stopFlag = false;
// ArrayList<TtOrnament> ornamentsList = new ArrayList<>(baseData);
// while (true) {
//
// if (stopFlag) {
// System.out.println("-100% 补充完成✔");
// break;
// }
//
// BigDecimal p = new BigDecimal(page * pageSize)
// .divide(new BigDecimal(baseData.size()),2,BigDecimal.ROUND_DOWN);
// System.out.print("\r补充"+page * pageSize+"条基础记录"+p.multiply(new BigDecimal(100)).setScale(0)+"%");
//
//
// List<Map<String, String>> reqList = null;
// try {
// reqList = requestList.subList(page * pageSize, page * pageSize + pageSize); // 尽量大
// } catch (Exception e) {
// int lenth = requestList.size();
// reqList = requestList.subList(page * pageSize, lenth - 1); // 尽量大
// stopFlag = !stopFlag;
// }
//
// HashMap<String, Object> map = new HashMap<>();
// map.put("requestList", reqList);
//
// YYResult yyResult1 = yyyoupingService.yyApiBatchGetOnSaleCommodityInfo(map);
// List<JSONObject> list = JSONUtil.toList(JSONUtil.toJsonStr(yyResult1.getData()), JSONObject.class);
//
// for (JSONObject obj : list) {
//
// JSONObject saleTemplateResponse = JSONUtil.parseObj(obj.get("saleTemplateResponse"));
// JSONObject saleCommodityResponse = JSONUtil.parseObj(obj.get("saleCommodityResponse"));
//
// String templateId = String.valueOf((Integer) saleTemplateResponse.get("templateId"));
//
// for (int i = 0; i < ornamentsList.size(); i++) {
//
// TtOrnament ornament = ornamentsList.get(i);
// if (templateId.equals(String.valueOf(ornament.getYyyoupingId()))) {
//
// ornament.setPrice(new BigDecimal(saleCommodityResponse.get("minSellPrice").toString()));
// ornament.setUsePrice(new BigDecimal(saleCommodityResponse.get("minSellPrice").toString()));
//
// ornament.setImageUrl(saleTemplateResponse.get("iconUrl").toString());
//
// Object exteriorName = saleTemplateResponse.get("exteriorName");
// if (ObjectUtil.isNotNull(exteriorName)){
// ornament.setExterior(getCodeByNameOrHash(allEnum,exteriorName.toString()));
// ornament.setExteriorName(exteriorName.toString());
// }
//
// ornament.setQuality(getCodeByNameOrHash(allEnum,saleTemplateResponse.get("qualityName").toString()));
// ornament.setQualityName(saleTemplateResponse.get("qualityName").toString());
// // if (ObjectUtil.isEmpty(ornament.getQuality()) && ObjectUtil.isNotEmpty(ornament.getQualityName())){
// // System.out.println(ornament.getQuality());
// // System.out.println(ornament.getQualityName());
// // }
//
// ornament.setRarity(getCodeByNameOrHash(allEnum,saleTemplateResponse.get("rarityName").toString()));
// ornament.setRarityName(saleTemplateResponse.get("rarityName").toString());
//
// ornamentsList.set(i,ornament);
//
// break;
// }
// }
// }
//
// page++;
//
// }
//
// baseData = new HashSet<>(ornamentsList);
//
// return baseData;
// }
//
// public static void main(String[] args) {
// System.out.println("StatTrak™".equals(""));
// }
//
// // 根据类型和标签获取code
// private String getCodeByNameOrHash(List<SysDictData> typeList,String nameOrHash){
//
// for (SysDictData type : typeList){
// if (type.getDictLabel().equals(nameOrHash)) return type.getDictValue();
// if (type.getDictType().equals(nameOrHash)) return type.getDictValue();
// }
// return null;
// }
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.thirdparty.cs340.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "cs340")
public class Cs340Config {
private String baseUrl;
private String appKey;
private String privateKey;
private String publicKey;
private String callbackPublicKey;
}

View File

@@ -0,0 +1,48 @@
package com.ruoyi.thirdparty.cs340.controller;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.thirdparty.cs340.domain.body.ByTemplateCreateOrder;
import com.ruoyi.thirdparty.cs340.domain.body.Cs340CheckTradeUrlBody;
import com.ruoyi.thirdparty.cs340.domain.body.Cs340OnSaleInfoBody;
import com.ruoyi.thirdparty.cs340.domain.result.Cs340CallBackNotifyInfo;
import com.ruoyi.thirdparty.cs340.service.Cs340Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/cs340item")
public class Cs340ItemController extends BaseController {
@Autowired
private Cs340Service cs340Service;
@PostMapping("/checkTradeUrl")
public AjaxResult checkTradeUrl(@RequestBody Cs340CheckTradeUrlBody body) {
return success(cs340Service.checkTradeUrl(body.getTradeUrl()));
}
@GetMapping("/checkMerchantBalance")
public AjaxResult checkMerchantBalance() {
return success(cs340Service.checkMerchantBalance());
}
/**
* 查询库存
*/
@PostMapping("/queryOnSaleInfo")
public AjaxResult queryOnSaleInfo(@Validated @RequestBody Cs340OnSaleInfoBody onSaleInfoBody) {
return success(cs340Service.queryOnSaleInfo(onSaleInfoBody));
}
@PostMapping("/createOrder")
public AjaxResult createOrder(@RequestBody ByTemplateCreateOrder createOrderBody) {
return success(cs340Service.createOrder(createOrderBody));
}
@PostMapping("/notify")
public AjaxResult notify(@RequestBody Cs340CallBackNotifyInfo notify) {
return cs340Service.notify(notify);
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.thirdparty.cs340.domain.body;
import lombok.Data;
/**
* merchantOrderNo String 是 商户订单号 小于等于59位的字符串
* tradeLinks String 是 收货方的Steam交易链接
* commodityTemplateId String 是 商品模版ID
* 数据来源查询模板ID templateId和hashName二选一必传递同时传入以templateId为准如传入不可为0
* commodityHashName String 是 模板hashname
* 数据来源查询模板ID templateId和hashName二选一必传递同时传入以templateId为准如传入不可为空
* purchasePrice String 是 购买最高价 单位:元
* 不可为0
* fastShipping Integer 否 极速发货购买模式 0优先购买极速发货
* 1只购买极速发货该模式purchasePrice参数请使用fastShippingMinSellPrice作为价格极速发货价格可通过批量查询在售商品价格或者批量查询在售商品详情接口获取极速发货价格存在并且大于0可使用极速发货。
*/
@Data
public class ByTemplateCreateOrder {
private String merchantOrderNo;
private String tradeLinks;
private String commodityHashName;
private String purchasePrice;
}

View File

@@ -0,0 +1,9 @@
package com.ruoyi.thirdparty.cs340.domain.body;
import lombok.Data;
@Data
public class Cs340CheckTradeUrlBody {
private String tradeUrl;
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.thirdparty.cs340.domain.body;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
@Data
public class Cs340OnSaleInfoBody {
@NotNull(message = "marketHashName不能为空")
private String marketHashName;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
import java.util.List;
@Data
public class BatchGetOnSaleCommodityInfoDto {
private String sign;
private String timestamp;
private String appKey;
List<CommodityInfoRequestListDto> requestList;
}

View File

@@ -0,0 +1,38 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
/**
* merchantOrderNo String 是 商户订单号 小于等于59位的字符串
* tradeLinks String 是 收货方的Steam交易链接
* commodityHashName String 是 模板hashname&数据来源查询模板ID templateId和hashName二选一必传递同时传入以templateId为准如传入不可为空
* purchasePrice String 是 购买最高价 单位:元 不可为0
* 请求示例
* {
* "sign":"4LL1O++Btn5JgZ6kHuJAN7dEG4cgLd0goo+Cb4wld0ygpU8lU+73mLxXYtN81ApAhWuw9zn+ENrtc2KcrL6XgHMSG8JC/5NjHCGO/3jDUjAac3YMh5vdQjsqhK1IoxdrOXM9QUyUj2kU9CA4eRey5wBx+BjbzHkSXXt6tOk9pDkeg56wuKR9L3g1BldcwBEacW2fU4pfEW9vpJC9QTCU/FAT0JXMTpYOaeIn+uf9eQRlRNguI1eUoWJAiGQgVQ/TlFlkdVqeE0pHp3Ntod2r+pkb4vCKrMDI6HW28XsM0uMupOfBUL8zRNFEOvLtswJduVo5r5Fd1ysHFcFywHOT0A==",
* "timestamp":"2023-01-17 17:59:00",
* "appKey":"123456",
* "merchantOrderNo": "20230112000",
* "tradeLinks": "https://steamcommunity.com/tradeoffer/new/?partner=123456&token=vP12QeQA",
* "commodityTemplateId": "100696",
* "commodityHashName": "",
* "purchasePrice": "0.08"
* }
*/
@Data
public class ByTemplateCreateOrderDto {
private String sign;
private String timestamp;
private String appKey;
private String merchantOrderNo;
private String tradeLinks;
private String commodityHashName;
private String purchasePrice;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
@Data
public class CheckMerchantBalanceDto {
private String sign;
private String timestamp;
private String appKey;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
@Data
public class CheckTradeUrlDto {
private String sign;
private String timestamp;
private String appKey;
private String tradeLinks;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
/**
* requestList Object[] 是 批量请求参数列表
* 数组数量1-200
* templateId Integer 是 模板id
* 来源查询模板ID templateId和hashName二选一必传递如传入不可为0
* templateHashName String 是 模板hashName
* 来源查询模板ID templateId和hashName二选一必传递如传入不可为空
*/
@Data
public class CommodityInfoRequestListDto {
private Long templateId;
private String templateHashName;
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
/**
* orderNo String 是 订单号 小于等于40位的数字字符串
* merchantNo String 是 商户订单号 数据来源:
* 指定模板购买
* 指定商品购买
* 注orderNo和merchantNo二选一传递同时传入以orderNo为准
* 小于等于59位的字符串
*/
@Data
public class Cs340OrderStatusDto {
private String sign;
private String timestamp;
private String appKey;
private String orderNo;
}

View File

@@ -0,0 +1,29 @@
package com.ruoyi.thirdparty.cs340.domain.dto;
import lombok.Data;
/**
* typeId Integer 是 武器大类ID
* typeHashName String 是 武器大类hashName 如传入不可为空
* typeId和typeHashName同时传递时以typeId为准
* weaponId String 是 武器类型ID
* weaponHashName String 是 武器类型HashName 如传入不可为空
* weaponId和weaponHashName同时传递时以weaponId为准
* 武器大类和武器类型同时传递,以武器类型为准。
* priceRangeMinmun String 否 商品价格区间最小值 单位:元
* priceRangeMaxmun String 否 商品价格区间最大值 单位:元
* page String 否 页码 最大50
* pageSize String 否 每页显示数量 最大值200若传入参数200则按200执行
*/
@Data
public class QueryTemplateSaleByCategoryDto {
private String sign;
private String timestamp;
private String appKey;
private Integer typeId;
private String typeHashName;
private String weaponId;
private String weaponHashName;
private String page;
private String pageSize;
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
* data Object[] 返回消息体。
* saleTemplateResponse Object
* templateId String 模板id。
* templateHashName String 模板hashName。
* iconUrl String 模板图片链接。
* exteriorName String 外观名称。
* rarityName String 品质。
* qualityName String 类别。
* saleCommodityResponse Object
* minSellPrice String 在售最低价(单位:元)。
* fastShippingMinSellPrice String 极速发货在售最低价(单位:元)。
* referencePrice String 模板参考价(单位:元)。
* sellNum String 在售数量。
*/
@Data
public class BatchGetOnSaleCommodityInfoResult {
private SaleTemplateResponse saleTemplateResponse;
private SaleCommodityResponse saleCommodityResponse;
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
import java.math.BigDecimal;
/**
* 参数 类型 说明
* data Object 返回消息体。
* merchantOrderNo String 商户订单号。
* orderNo String 订单单号。
* payAmount number 实际支付金额。
* orderStatus Integer 交易状态1,成功0,失败。
* 示例:
* {
* "code": 0,
* "msg": "成功",
* "timestamp": 1673943192928,
* "data": {
* "merchantOrderNo": "20230112102",
* "orderNo": "2023011710002994746",
* "payAmount": 0.1,
* "orderStatus": 0
* }
* }
*/
@Data
public class ByTemplateCreateOrderResult {
private String merchantOrderNo;
private String orderNo;
private BigDecimal payAmount;
private Integer orderStatus;
}

View File

@@ -0,0 +1,45 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
* orderNo String 订单号。
* orderType Integer 预留字段
* orderSubType Integer 预留字段
* shippingMode Integer 发货模式0,卖家直发1,极速发货
* tradeOfferId long 报价ID
* tradeOfferLinks String 报价链接
* buyerUserId Integer 购买用户编号。
* orderStatus Integer 订单大状态。
* orderSubStatus Integer 订单小状态。
* failCode Integer 订单失败原因编号。
* failReason String 订单失败原因提示信息。
* merchantOrderNo String 第三方商户单号。
* notifyType Integer 通知类型(0:创建订单1:等待发货2:等待收货3:购买成功4:订单取消)。
* notifyDesc String 通知类型描述(创建订单,等待发货,等待收货,购买成功,订单取消)。
*
* 通知类型 orderStatus orderSubStatus
* 创建订单 100-创建订单
* 等待发货 140-交货中 1101 待发送报价
* 等待收货 1103 待确认报价
* 购买成功 340-已完成
* 订单取消 280-已取消
* 订单购买失败 2-购买失败
*/
@Data
public class Cs340CallBackInfo {
private String orderNo;
private Integer orderType;
private Integer orderSubType;
private Integer shippingMode;
private Long tradeOfferId;
private String tradeOfferLinks;
private Integer buyerUserId;
private Integer orderStatus;
private Integer orderSubStatus;
private Integer failCode;
private String failReason;
private String merchantOrderNo;
private Integer notifyType;
private String notifyDesc;
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
@Data
public class Cs340CallBackNotifyInfo {
private String messageNo;
private String callBackInfo;
private String sign;
}

View File

@@ -0,0 +1,9 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
@Data
public class Cs340CallBackResult {
private String messageNo;
private boolean flag;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
@Data
public class Cs340CheckTradeUrlResult {
private String steamId;
private Integer status;
private String msg;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Cs340MerchantBalanceResult {
private BigDecimal amount;
private Integer userId;
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
* data object 返回消息体。
* orderNumber String 订单编号。
* shippingMode Integer 发货模式0,卖家直发1,极速发货
* tradeOfferId Long 报价ID
* tradeOfferLinks String 报价链接(不是每笔订单都存在)
* statusMsg String 订单状态。
* bigStatus Integer 订单大状态。
* bigStatusMsg String 订单大状态提示信息。
* smallStatus Integer 订单小状态。
* smallStatusMsg String 订单小状态提示信息。
* failCode Integer 订单失败原因编号仅记录失败原因具体以bigStatus状态为准
* failReason String 订单失败原因提示信息。
*
* 大状态bigStatus说明
* 数值 说明
* 2 失败
* 100 创建中。
* 120 支付中。
* 130 异常挂起。
* 140 交货中。
* 360 结算中。
* 340 已完成。
* 280 已取消。
*/
@Data
public class Cs340OrderStatusResult {
private String orderNumber;
private Integer shippingMode;
private Long tradeOfferId;
private String tradeOfferLinks;
private String statusMsg;
private Integer bigStatus;
private String bigStatusMsg;
private Integer smallStatus;
private String smallStatusMsg;
private Integer failCode;
private String failReason;
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
@Data
public class Cs340Result<T> {
private Integer code;
private String msg;
private Long timestamp;
private T data;
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
*saleCommodityResponse Object
* minSellPrice String 在售最低价(单位:元)。
* fastShippingMinSellPrice String 极速发货在售最低价(单位:元)。
* referencePrice String 模板参考价(单位:元)。
* sellNum String 在售数量。
*/
@Data
public class SaleCommodityResponse {
private String minSellPrice;
private String fastShippingMinSellPrice;
private String referencePrice;
private String sellNum;
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
* templateId integer 模板id
* templateHashName string 模板hash name
* templateName string 模版名称
* iconUrl string 模板图片
* exteriorName string 外观名称
* rarityName string 品质
* typeId integer 武器大类
* typeHashName string 武器大类hashName
* weaponId integer 武器类型ID
* weaponHashName string 武器类型标签hashName
* minSellPrice string 在售最低价(单位元)
* fastShippingMinSellPrice String 极速发货在售最低价(单位:元)
* referencePrice String 模板参考价(单位:元)。
* sellNum integer 在售数量
*/
@Data
public class SaleTemplateByCategoryResponse {
private Integer templateId;
private String templateHashName;
private String templateName;
private String iconUrl;
private String exteriorName;
private String rarityName;
private Integer typeId;
private String typeHashName;
private Integer weaponId;
private String weaponHashName;
private String minSellPrice;
private String fastShippingMinSellPrice;
private String referencePrice;
private Integer sellNum;
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
import java.util.List;
/**
* currentPage
* newPageIsHaveContent
* saleTemplateByCategoryResponseList
* templateId
*/
@Data
public class SaleTemplateByCategoryResult {
private Integer currentPage;
private Boolean newPageIsHaveContent;
private List<SaleTemplateByCategoryResponse> saleTemplateByCategoryResponseList;
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.thirdparty.cs340.domain.result;
import lombok.Data;
/**
* * saleTemplateResponse Object
* * templateId String 模板id。
* * templateHashName String 模板hashName。
* * iconUrl String 模板图片链接。
* * exteriorName String 外观名称。
* * rarityName String 品质。
* * qualityName String 类别。
*/
@Data
public class SaleTemplateResponse {
private String templateId;
private String templateHashName;
private String iconUrl;
private String exteriorName;
private String rarityName;
private String qualityName;
}

View File

@@ -0,0 +1,44 @@
package com.ruoyi.thirdparty.cs340.service;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.thirdparty.cs340.domain.body.ByTemplateCreateOrder;
import com.ruoyi.thirdparty.cs340.domain.body.Cs340OnSaleInfoBody;
import com.ruoyi.thirdparty.cs340.domain.result.*;
import java.util.List;
public interface Cs340Service {
/**
* 验证交易链接
*/
Cs340CheckTradeUrlResult checkTradeUrl(String tradeUrl);
/**
* 余额查询
*/
Cs340MerchantBalanceResult checkMerchantBalance();
/**
* 创建订单
*/
Cs340Result<ByTemplateCreateOrderResult> createOrder(ByTemplateCreateOrder order);
/**
* 查询订单状态
* @param orderNo 商家订单号
*/
Cs340Result<Cs340OrderStatusResult> queryOrderStatus(String orderNo);
/**
* 查询在线订单
*/
BatchGetOnSaleCommodityInfoResult queryOnSaleInfo(Cs340OnSaleInfoBody onSaleInfoBody);
/**
* 回调通知信息
*/
AjaxResult notify(Cs340CallBackNotifyInfo notify);
/**
* 批量获取价格
*/
List<BatchGetOnSaleCommodityInfoResult> batchGetOnSaleCommodityInfoResult(List<String> templateHashNames);
}

View File

@@ -0,0 +1,285 @@
package com.ruoyi.thirdparty.cs340.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.admin.mapper.TtDeliveryRecordMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.domain.common.constant.DeliveryOrderStatus;
import com.ruoyi.domain.entity.delivery.TtDeliveryRecord;
import com.ruoyi.thirdparty.cs340.config.Cs340Config;
import com.ruoyi.thirdparty.cs340.domain.body.ByTemplateCreateOrder;
import com.ruoyi.thirdparty.cs340.domain.body.Cs340OnSaleInfoBody;
import com.ruoyi.thirdparty.cs340.domain.dto.*;
import com.ruoyi.thirdparty.cs340.domain.result.*;
import com.ruoyi.thirdparty.cs340.service.Cs340Service;
import com.ruoyi.thirdparty.cs340.utils.RSAUtils;
import com.ruoyi.thirdparty.yyyouping.utils.common.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* https://www.yuque.com/laobi-hrwiq/gzgf3p/qlcuu7b8goar5dg7
*/
@Slf4j
@Service
public class Cs340ServiceImpl implements Cs340Service {
// 设置日期转换为字符串格式
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired
private Cs340Config cs340Config;
@Autowired
private TtDeliveryRecordMapper deliveryRecordMapper;
@Override
public Cs340CheckTradeUrlResult checkTradeUrl(String tradeUrl) {
String url = cs340Config.getBaseUrl() + "/v1/api/checkTradeUrl";
// 构建请求头
Map<String, String> headers = new HashMap<>();
CheckTradeUrlDto dto = new CheckTradeUrlDto();
dto.setTimestamp(sdf.format(new Date()));
dto.setAppKey(cs340Config.getAppKey());
dto.setTradeLinks(tradeUrl);
dto.setSign(generateSign(JSONObject.toJSONString(dto), cs340Config.getPrivateKey()));
String body = JSON.toJSONString(dto);
String result = HttpUtils.sendPostJSONString(url, body, headers);
log.info("body is {}, result is {}", body, result);
// 将JSON转换为对象
// 定义V5ItemResult的泛型类型
TypeReference<Cs340Result<Cs340CheckTradeUrlResult>> typeRef =
new TypeReference<Cs340Result<Cs340CheckTradeUrlResult>>() {};
// 使用FastJson将JSON字符串转换为对象
Cs340Result<Cs340CheckTradeUrlResult> result1 = JSONObject.parseObject(result, typeRef);
if (result1.getCode() != 0) {
return null;
}
return result1.getData();
}
@Override
public Cs340MerchantBalanceResult checkMerchantBalance() {
String url = cs340Config.getBaseUrl() + "/v1/api/getAssetsInfo";
// 构建请求头
Map<String, String> headers = new HashMap<>();
CheckMerchantBalanceDto dto = new CheckMerchantBalanceDto();
dto.setTimestamp(sdf.format(new Date()));
dto.setAppKey(cs340Config.getAppKey());
dto.setSign(generateSign(JSONObject.toJSONString(dto), cs340Config.getPrivateKey()));
String body = JSON.toJSONString(dto);
String result = HttpUtils.sendPostJSONString(url, body, headers);
log.info("body is {}, result is {}", body, result);
// 将JSON转换为对象
// 定义V5ItemResult的泛型类型
TypeReference<Cs340Result<Cs340MerchantBalanceResult>> typeRef =
new TypeReference<Cs340Result<Cs340MerchantBalanceResult>>() {};
// 使用FastJson将JSON字符串转换为对象
Cs340Result<Cs340MerchantBalanceResult> result1 = JSONObject.parseObject(result, typeRef);
if (result1.getCode() != 0) {
return null;
}
return result1.getData();
}
@Override
public Cs340Result<ByTemplateCreateOrderResult> createOrder(ByTemplateCreateOrder order) {
String url = cs340Config.getBaseUrl() + "/v1/api/byTemplateCreateOrder";
// 构建请求头
Map<String, String> headers = new HashMap<>();
ByTemplateCreateOrderDto dto = new ByTemplateCreateOrderDto();
BeanUtils.copyProperties(order, dto);
dto.setTimestamp(sdf.format(new Date()));
dto.setAppKey(cs340Config.getAppKey());
dto.setSign(generateSign(JSONObject.toJSONString(dto), cs340Config.getPrivateKey()));
String body = JSON.toJSONString(dto);
String result = HttpUtils.sendPostJSONString(url, body, headers);
log.info("body is {}, result is {}", body, result);
// 将JSON转换为对象
// 定义V5ItemResult的泛型类型
TypeReference<Cs340Result<ByTemplateCreateOrderResult>> typeRef =
new TypeReference<Cs340Result<ByTemplateCreateOrderResult>>() {};
// 使用FastJson将JSON字符串转换为对象
return JSONObject.parseObject(result, typeRef);
}
@Override
public Cs340Result<Cs340OrderStatusResult> queryOrderStatus(String orderNo) {
String url = cs340Config.getBaseUrl() + "/v1/api/orderStatus";
// 构建请求头
Map<String, String> headers = new HashMap<>();
Cs340OrderStatusDto dto = new Cs340OrderStatusDto();
dto.setOrderNo(orderNo);
dto.setTimestamp(sdf.format(new Date()));
dto.setAppKey(cs340Config.getAppKey());
dto.setSign(generateSign(JSONObject.toJSONString(dto), cs340Config.getPrivateKey()));
String body = JSON.toJSONString(dto);
String result = HttpUtils.sendPostJSONString(url, body, headers);
log.info("body is {}, result is {}", body, result);
// 将JSON转换为对象
// 定义V5ItemResult的泛型类型
TypeReference<Cs340Result<Cs340OrderStatusResult>> typeRef =
new TypeReference<Cs340Result<Cs340OrderStatusResult>>() {};
// 使用FastJson将JSON字符串转换为对象
return JSONObject.parseObject(result, typeRef);
}
@Override
public BatchGetOnSaleCommodityInfoResult queryOnSaleInfo(Cs340OnSaleInfoBody onSaleInfoBody) {
List<BatchGetOnSaleCommodityInfoResult> results = batchGetOnSaleCommodityInfoResult(Collections.singletonList(onSaleInfoBody.getMarketHashName()));
if (CollectionUtils.isEmpty(results)) {
return null;
}
return results.get(0);
}
@Override
public AjaxResult notify(Cs340CallBackNotifyInfo notify) {
log.info("收到订单更改回调通知。入参:{}", notify.toString());
String callBackInfo = notify.getCallBackInfo();
String messageNo = notify.getMessageNo();
String sign = notify.getSign();
Map<String, Object> params = new HashMap<>();
params.put("messageNo", messageNo);
params.put("callBackInfo", callBackInfo);
// 第一步:检查参数是否已经排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有参数名和参数值串在一起
StringBuilder stringBuilder = new StringBuilder();
for (String key : keys) {
Object value = params.get(key);
if (value != null) {
stringBuilder.append(key).append(value);
}
}
log.info("stringBuilder:{}", stringBuilder);
boolean b = false;
try {
b = RSAUtils.verifyByPublicKey(stringBuilder.toString().getBytes(), cs340Config.getCallbackPublicKey(), sign);
} catch (Exception e) {
log.error("notify happen excepiton is {}", e.getMessage(), e);
}
Cs340CallBackResult result = new Cs340CallBackResult();
result.setMessageNo(messageNo);
if (!b) {
log.info("b is :{}, cs340Config.getPublicKey() is {}", b, cs340Config.getCallbackPublicKey());
return AjaxResult.success("verifyByPublicKey fail", result);
}
ObjectMapper objectMapper = new ObjectMapper();
Cs340CallBackInfo info = objectMapper.convertValue(callBackInfo, Cs340CallBackInfo.class);
// 获取提货订单数据
LambdaQueryWrapper<TtDeliveryRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TtDeliveryRecord::getOrderId, info.getOrderNo());
TtDeliveryRecord ttDeliveryRecord = deliveryRecordMapper.selectOne(queryWrapper);
if (Objects.equals(info.getOrderStatus(), 340)) {
ttDeliveryRecord.setStatus(DeliveryOrderStatus.ORDER_COMPLETE.getCode());
ttDeliveryRecord.setMessage(DeliveryOrderStatus.ORDER_COMPLETE.getMsg());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
deliveryRecordMapper.updateById(ttDeliveryRecord);
} else if (Objects.equals(info.getOrderStatus(), 280)) {
ttDeliveryRecord.setStatus(DeliveryOrderStatus.ORDER_CANCEL.getCode());
ttDeliveryRecord.setMessage(DeliveryOrderStatus.ORDER_CANCEL.getMsg());
ttDeliveryRecord.setUpdateTime(DateUtils.getNowDate());
deliveryRecordMapper.updateById(ttDeliveryRecord);
}
log.info("验签结果:{}", b);
result.setFlag(true);
return AjaxResult.success(result);
}
@Override
public List<BatchGetOnSaleCommodityInfoResult> batchGetOnSaleCommodityInfoResult(List<String> templateHashNames) {
if (CollectionUtils.isEmpty(templateHashNames)) {
return Collections.emptyList();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("sleep happen exception");
}
String url = cs340Config.getBaseUrl() + "/v1/api/batchGetOnSaleCommodityInfo";
// 构建请求头
Map<String, String> headers = new HashMap<>();
List<CommodityInfoRequestListDto> requestList = new ArrayList<>();
for (String templateHashName : templateHashNames) {
CommodityInfoRequestListDto dto = new CommodityInfoRequestListDto();
dto.setTemplateHashName(templateHashName);
requestList.add(dto);
}
BatchGetOnSaleCommodityInfoDto dto = new BatchGetOnSaleCommodityInfoDto();
dto.setTimestamp(sdf.format(new Date()));
dto.setAppKey(cs340Config.getAppKey());
dto.setRequestList(requestList);
dto.setSign(generateSign(JSONObject.toJSONString(dto), cs340Config.getPrivateKey()));
String body = JSON.toJSONString(dto);
String result = HttpUtils.sendPostJSONString(url, body, headers);
// log.info("body is {}, result is {}", body, result);
// 将JSON转换为对象
// 定义V5ItemResult的泛型类型
TypeReference<Cs340Result<List<BatchGetOnSaleCommodityInfoResult>>> typeRef =
new TypeReference<Cs340Result<List<BatchGetOnSaleCommodityInfoResult>>>() {};
// 使用FastJson将JSON字符串转换为对象
Cs340Result<List<BatchGetOnSaleCommodityInfoResult>> result1 = JSONObject.parseObject(result, typeRef);
if (result1.getCode() != 0) {
return Collections.emptyList();
}
if (result1.getData() == null || CollectionUtils.isEmpty(result1.getData())) {
return Collections.emptyList();
}
return result1.getData();
}
/**
* 生成认证sign信息
*/
private String generateSign(String body, String privateKey) {
Map<String, Object> map = JSONObject.parseObject(body, Map.class);
// 第一步:检查参数是否已经排序
String[] keys = map.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有参数名和参数值串在一起
StringBuilder stringBuilder = new StringBuilder();
for (String key : keys) {
if (Objects.equals(key, "sign")) {
continue;
}
Object value = map.get(key);
if (value != null) {
stringBuilder.append(key).append(JacksonUtils.writeValueAsString(value));
}
}
try {
return RSAUtils.signByPrivateKey(stringBuilder.toString().getBytes(), privateKey);
} catch (Exception e) {
log.error("签名失败 is {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String body = "{\"sign\":\"4LL1O++Btn5JgZ6kHuJAN7dEG4cgLd0goo+Cb4wld0ygpU8lU+73mLxXYtN81ApAhWuw9zn+ENrtc2KcrL6XgHMSG8JC/5NjHCGO/3jDUjAac3YMh5vdQjsqhK1IoxdrOXM9QUyUj2kU9CA4eRey5wBx+BjbzHkSXXt6tOk9pDkeg56wuKR9L3g1BldcwBEacW2fU4pfEW9vpJC9QTCU/FAT0JXMTpYOaeIn+uf9eQRlRNguI1eUoWJAiGQgVQ/TlFlkdVqeE0pHp3Ntod2r+pkb4vCKrMDI6HW28XsM0uMupOfBUL8zRNFEOvLtswJduVo5r5Fd1ysHFcFywHOT0A==\",\"timestamp\":\"2023-01-17 16:15:00\",\"appKey\":\"123456\",\"merchantOrderNo\":\"20230112061\",\"tradeLinks\":\"https://steamcommunity.com/tradeoffer/new/?partner=323393236&token=vP12QeQA\",\"commodityId\":\"427620\",\"purchasePrice\":\"0.07\"}";
ByTemplateCreateOrderDto dtp = JSON.parseObject(body, ByTemplateCreateOrderDto.class);
Cs340ServiceImpl cs340Service = new Cs340ServiceImpl();
Cs340OnSaleInfoBody cs340OnSaleInfoBody = new Cs340OnSaleInfoBody();
cs340OnSaleInfoBody.setMarketHashName("Sticker | tabseN | Antwerp 2022");
BatchGetOnSaleCommodityInfoResult response = cs340Service.queryOnSaleInfo(cs340OnSaleInfoBody);
System.out.println(response != null ? response.toString() : "null");
}
}

View File

@@ -0,0 +1,104 @@
package com.ruoyi.thirdparty.cs340.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.service.TtOrnamentService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.domain.entity.TtOrnament;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.cs340.domain.result.BatchGetOnSaleCommodityInfoResult;
import com.ruoyi.thirdparty.cs340.service.Cs340Service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.*;
@Slf4j
@Component("Cs340ItemPullTask")
public class Cs340ItemPullTask {
@Autowired
private TtOrnamentService ornamentsService;
@Autowired
private Cs340Service cs340Service;
@Autowired
private ISysConfigService configService;
public void getCs340OrnamentsData() {
log.info("cs2pifa拉取最新饰品数据任务执行开始...");
String usePricePremiumRateStr = configService.selectConfigByKey("usePricePremiumRate");
BigDecimal usePricePremiumRate = new BigDecimal(usePricePremiumRateStr);
String ZBTParitiesStr = configService.selectConfigByKey("ZBTParities");
BigDecimal parities = new BigDecimal(ZBTParitiesStr);
// 查询数据库
LambdaQueryWrapper<TtOrnament> wrapper = Wrappers.lambdaQuery();
List<TtOrnament> ttOrnamentList = ornamentsService.list(wrapper);
if (CollectionUtils.isEmpty(ttOrnamentList)) {
log.info("ttOrnamentList is empty");
return;
}
List<List<TtOrnament>> splitOrnamentList = ListUtils.partition(ttOrnamentList, 200);
for (List<TtOrnament> list : splitOrnamentList) {
List<String> templateHashNames = new ArrayList<>();
Map<String, TtOrnament> templateHashMap = new HashMap<>();
for (TtOrnament ornament : list) {
if (ornament == null || StringUtils.isBlank(ornament.getMarketHashName())) {
continue;
}
templateHashNames.add(ornament.getMarketHashName());
templateHashMap.put(ornament.getMarketHashName(), ornament);
}
List<BatchGetOnSaleCommodityInfoResult> infos = cs340Service.batchGetOnSaleCommodityInfoResult(templateHashNames);
if (CollectionUtils.isEmpty(infos)) {
continue;
}
Set<String> templateHashNameSet = new HashSet<>();
List<TtOrnament> updateList = new ArrayList<>();
for (BatchGetOnSaleCommodityInfoResult info : infos) {
if (info == null || info.getSaleCommodityResponse() == null || info.getSaleTemplateResponse() == null) {
continue;
}
if (templateHashNameSet.contains(info.getSaleTemplateResponse().getTemplateHashName())) {
log.info("templateHashName same is {}", info.getSaleTemplateResponse().getTemplateHashName());
}
templateHashNameSet.add(info.getSaleTemplateResponse().getTemplateHashName());
TtOrnament ttOrnament = templateHashMap.get(info.getSaleTemplateResponse().getTemplateHashName());
if (ttOrnament == null) {
continue;
}
if (ttOrnament.getPrice() == null || !Objects.equals(ttOrnament.getPrice().toString(), info.getSaleCommodityResponse().getMinSellPrice()) ||
ttOrnament.getQuantity() == null || !Objects.equals(ttOrnament.getQuantity().toString(), info.getSaleCommodityResponse().getSellNum())) {
// 加入到更新队列
BigDecimal price = new BigDecimal(info.getSaleCommodityResponse().getMinSellPrice());
BigDecimal userPrice = price.multiply(parities).multiply(usePricePremiumRate);
if (userPrice.compareTo(new BigDecimal("99999999.99")) > 0)
continue;
ttOrnament.setPrice(price);
ttOrnament.setQuantity(info.getSaleCommodityResponse().getSellNum() != null ? Integer.parseInt(info.getSaleCommodityResponse().getSellNum()) : 0);
ttOrnament.setUsePrice(userPrice);
ttOrnament.setUpdateTime(DateUtils.getNowDate());
updateList.add(ttOrnament);
}
}
if (CollectionUtils.isNotEmpty(updateList)) {
batchUpdateOrnaments(updateList);
}
}
log.info("更新数据完成");
}
private void batchUpdateOrnaments(List<TtOrnament> updateList) {
ornamentsService.updateBatchById(updateList, updateList.size());
log.info("更新{}条", updateList.size());
}
}

View File

@@ -0,0 +1,501 @@
package com.ruoyi.thirdparty.cs340.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* SHA256withRSA
*
* @author zhumiao
*
*/
public class RSAUtils {
private static final Logger logger = LoggerFactory.getLogger(RSAUtils.class);
// MAX_DECRYPT_BLOCK应等于密钥长度/81byte=8bit所以当密钥位数为2048时最大解密长度应为256.
// 128 对应 1024256对应2048
private static final int KEYSIZE = 2048;
// RSA最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117;
// RSA最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 256;
// 不仅可以使用DSA算法同样也可以使用RSA算法做数字签名
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
// 默认种子 请根据自己的需要定义
public static final String DEFAULT_SEED = "xxxxxxxxxxxxxxxxxx";
/**
* 目前固定公钥、私钥,有需求再改动
*/
public static String PUBLIC_KEY_LZ = "***************";
public static String PRIVATE_KEY_LZ = "*******************************************=";
public static final String PUBLIC_KEY = "PublicKey";
public static final String PRIVATE_KEY = "PrivateKey";
/**
*
* 生成密钥
*
* @param seed 种子
*
* @return 密钥对象
* @throws Exception
*
*/
public static Map<String, Key> initKey(String seed) throws Exception {
logger.info("生成密钥");
KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = new SecureRandom();
// 如果指定seed那么secureRandom结果是一样的所以生成的公私钥也永远不会变
secureRandom.setSeed(seed.getBytes());
// Modulus size must range from 512 to 1024 and be a multiple of 64
keygen.initialize(KEYSIZE, secureRandom);
KeyPair keys = keygen.genKeyPair();
PrivateKey privateKey = keys.getPrivate();
PublicKey publicKey = keys.getPublic();
Map<String, Key> map = new HashMap<>(2);
map.put(PUBLIC_KEY, publicKey);
map.put(PRIVATE_KEY, privateKey);
return map;
}
/**
*
* 生成默认密钥
*
*
* @return 密钥对象
* @throws Exception
*
*/
public static Map<String, Key> initKey() throws Exception {
return initKey(DEFAULT_SEED);
}
/**
*
* 取得私钥
*
* @param keyMap
*
* @return
* @throws Exception
*
*/
public static String getPrivateKey(Map<String, Key> keyMap) throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
// return encryptBASE64(key.getEncoded()); // base64加密私钥
return base64ToStr(key.getEncoded()); // base64加密私钥
}
private static String base64ToStr(byte[] encoded) {
return java.util.Base64.getEncoder().encodeToString(encoded);
}
/**
*
* 取得公钥
*
* @param keyMap
*
* @return
* @throws Exception
*
*/
public static String getPublicKey(Map<String, Key> keyMap) throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
// return encryptBASE64(key.getEncoded()); // base64加密公钥
return base64ToStr(key.getEncoded()); // base64加密公钥
}
/**
*
* 用私钥对信息进行数字签名
*
* @param data 加密数据
*
* @param privateKey 私钥-base64加密的
*
* @return
*
* @throws Exception
*
*/
public static String signByPrivateKey(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = decryptBASE64(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey priKey = factory.generatePrivate(keySpec);// 生成私钥
// 用私钥对信息进行数字签名
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(priKey);
signature.update(data);
return encryptBASE64(signature.sign());
}
/**
*
* BASE64Encoder 加密
*
* @param data 要加密的数据
*
* @return 加密后的字符串
*
*/
private static String encryptBASE64(byte[] data) {
// BASE64Encoder encoder = new BASE64Encoder();
// String encode = encoder.encode(data);
// return encode;
return new String(Base64.encodeBase64(data));
}
private static byte[] decryptBASE64(String data) {
// BASE64Decoder 每76个字符换行
// BASE64Decoder decoder = new BASE64Decoder();
// byte[] buffer = decoder.decodeBuffer(data);
// return buffer;
// codec 的 Base64 不换行
return Base64.decodeBase64(data);
}
// TODO rsa 2 对比是否更优
public static boolean verifyByPublicKey(byte[] data, String publicKey, String sign) throws Exception {
byte[] keyBytes = decryptBASE64(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(decryptBASE64(sign)); // 验证签名
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws UnsupportedEncodingException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws Exception 加密过程中的异常信息
*/
public static String encryptByPublicKey(String str, String publicKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
// base64编码的公钥
byte[] keyBytes = decryptBASE64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM)
.generatePublic(new X509EncodedKeySpec(keyBytes));
// RSA加密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
logger.info(publicKey);
logger.info("provider: {}", cipher.getProvider().getClass().getName());
byte[] data = str.getBytes("UTF-8");
// 加密时超过117字节就报错。为此采用分段加密的办法来加密
byte[] enBytes = null;
for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
// 注意要使用2的倍数否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
enBytes = ArrayUtils.addAll(enBytes, doFinal);
}
logger.info(enBytes.length + "");
String outStr = encryptBASE64(enBytes);
return outStr;
}
/**
* RSA私钥加密
*
* @param str 加密字符串
* @param privateKey 公钥
* @return 密文
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws UnsupportedEncodingException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws Exception 加密过程中的异常信息
*/
public static String encryptByPrivateKey(String str, String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
// base64编码的公钥
byte[] keyBytes = decryptBASE64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
// RSA加密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, priKey);
byte[] data = str.getBytes("UTF-8");
// 加密时超过117字节就报错。为此采用分段加密的办法来加密
byte[] enBytes = null;
for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
// 注意要使用2的倍数否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
enBytes = ArrayUtils.addAll(enBytes, doFinal);
}
String outStr = encryptBASE64(enBytes);
return outStr;
}
/**
* 读取公钥
*
* @param publicKeyPath
* @return
*/
public static PublicKey readPublic(String publicKeyPath) {
if (publicKeyPath != null) {
try (FileInputStream bais = new FileInputStream(publicKeyPath)) {
CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificatefactory.generateCertificate(bais);
return cert.getPublicKey();
} catch (CertificateException e) {
logger.error(e.getMessage(), e);
} catch (FileNotFoundException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
return null;
}
/**
* 读取私钥
*
*
* @return
*/
public static PrivateKey readPrivate(String privateKeyPath, String privateKeyPwd) {
if (privateKeyPath == null || privateKeyPwd == null) {
return null;
}
try (InputStream stream = new FileInputStream(new File(privateKeyPath));) {
// 获取JKS 服务器私有证书的私钥取得标准的JKS的 KeyStore实例
KeyStore store = KeyStore.getInstance("JKS");// JKS二进制格式同时包含证书和私钥一般有密码保护PKCS12二进制格式同时包含证书和私钥一般有密码保护。
// jks文件密码根据实际情况修改
store.load(stream, privateKeyPwd.toCharArray());
// 获取jks证书别名
Enumeration<String> en = store.aliases();
String pName = null;
while (en.hasMoreElements()) {
String n = (String) en.nextElement();
if (store.isKeyEntry(n)) {
pName = n;
}
}
// 获取证书的私钥
PrivateKey key = (PrivateKey) store.getKey(pName, privateKeyPwd.toCharArray());
return key;
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
/**
* RSA私钥解密
*
* @param encryStr 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
* @throws Exception 解密过程中的异常信息
*/
public static String decryptByPrivateKey(String encryStr, String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, UnsupportedEncodingException {
// base64编码的私钥
byte[] decoded = decryptBASE64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
.generatePrivate(new PKCS8EncodedKeySpec(decoded));
// RSA解密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, priKey);
logger.info(privateKey);
logger.info("provider: {}" , cipher.getProvider().getClass().getName());
// 64位解码加密后的字符串
byte[] data = decryptBASE64(encryStr);
logger.info(data.length + "");
// 解密时超过128字节报错。为此采用分段解密的办法来解密
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
sb.append(new String(doFinal));
}
return sb.toString();
}
/**
* RSA公钥解密
*
* @param encryStr 加密字符串
*
* @return 铭文
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
* @throws Exception 解密过程中的异常信息
*/
public static String decryptByPublicKey(String encryStr, String publicKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException, InvalidKeyException {
// base64编码的私钥
byte[] decoded = decryptBASE64(publicKey);
RSAPublicKey priKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM)
.generatePublic(new X509EncodedKeySpec(decoded));
// RSA解密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, priKey);
// 64位解码加密后的字符串
byte[] data = decryptBASE64(encryStr);
// 解密时超过128字节报错。为此采用分段解密的办法来解密
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
sb.append(new String(doFinal));
}
return sb.toString();
}
/**
* 加密
* @param key
* @param data
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws InvalidKeyException
* @throws IOException
*/
public static String testEncrypt(String key,String data) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException{
byte[] decode = Base64.decodeBase64(key);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode);
KeyFactory kf = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey generatePrivate = kf.generatePrivate(pkcs8EncodedKeySpec);
Cipher ci = Cipher.getInstance(KEY_ALGORITHM);
ci.init(Cipher.ENCRYPT_MODE, generatePrivate);
byte[] bytes = data.getBytes();
int inputLen = bytes.length;
int offLen = 0;//偏移量
int i = 0;
ByteArrayOutputStream bops = new ByteArrayOutputStream();
while(inputLen - offLen > 0){
byte [] cache;
if(inputLen - offLen > 117){
cache = ci.doFinal(bytes, offLen,117);
}else{
cache = ci.doFinal(bytes, offLen,inputLen - offLen);
}
bops.write(cache);
i++;
offLen = 117 * i;
}
bops.close();
byte[] encryptedData = bops.toByteArray();
String encodeToString = Base64.encodeBase64String(encryptedData);
return encodeToString;
}
/**
* 解密
* @param key
* @param data
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws NoSuchPaddingException
* @throws InvalidKeySpecException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws IOException
*/
public static String testDecrypt(String key,String data) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, IOException{
byte[] decode = Base64.decodeBase64(key);
// PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode); //java底层 RSA公钥只支持X509EncodedKeySpec这种格式
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decode);
KeyFactory kf = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey generatePublic = kf.generatePublic(x509EncodedKeySpec);
Cipher ci = Cipher.getInstance(KEY_ALGORITHM);
ci.init(Cipher.DECRYPT_MODE,generatePublic);
int inputLen = data.getBytes().length;
byte[] bytes = data.getBytes();
int offLen = 0;
int i = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while(inputLen - offLen > 0){
byte[] cache;
if(inputLen - offLen > 128){
cache = ci.doFinal(bytes,offLen,128);
}else{
cache = ci.doFinal(bytes,offLen,inputLen - offLen);
}
byteArrayOutputStream.write(cache);
i++;
offLen = 128 * i;
}
byteArrayOutputStream.close();
byte[] byteArray = byteArrayOutputStream.toByteArray();
return new String(byteArray);
}
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.thirdparty.gfht.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "gfht")
public class GfhtConfig {
/**
* 服务器URL
*/
private String serverUrl;
/**
* 商户ID
*/
private String pid;
/**
* 签名
*/
private String sign;
/**
* 通知URL
*/
private String notifyUrl;
/**
* 返回URL
*/
private String returnUrl;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.thirdparty.gfht.controller;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UserPermission;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.thirdparty.gfht.service.GfhtService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Api(tags = "国富汇通")
@Slf4j
@RestController
@RequestMapping("api/gfht")
public class GfhtController extends BaseController {
@Autowired
private TtUserService ttUserService;
@Autowired
private GfhtService gfhtService;
@ApiOperation("支付")
@UserPermission
@PostMapping(value = "/pay")
public R pay(@RequestBody @Validated CreateOrderParam param) {
Long userId = SecurityUtils.getUserId();
TtUser ttUser = ttUserService.getById(userId);
// 是否实名认证0未认证 1已认证
/*if ("0".equals(ttUser.getIsRealCheck())) {
return R.fail("未实名认证");
}*/
String ip = IpUtils.getIpAddr();
return gfhtService.pay(param, ttUser, ip);
}
@GetMapping("/notify")
public String notify(@RequestParam Map<String, String> params) {
return gfhtService.notify(params);
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.thirdparty.gfht.domain;
import lombok.Data;
@Data
public class GfhtResponse {
private String code;
private String msg;
private String tradeNo;
private String payurl;
private String qrcode;
private String urlscheme;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.thirdparty.gfht.service;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import java.util.Map;
public interface GfhtService {
R pay(CreateOrderParam param, TtUser ttUser, String ip);
String notify(Map<String, String> params);
}

View File

@@ -0,0 +1,465 @@
package com.ruoyi.thirdparty.gfht.service.impl;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.admin.mapper.*;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.domain.common.constant.*;
import com.ruoyi.domain.entity.*;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.domain.other.TtRechargeRecord;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.baidu.service.BaiduService;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import com.ruoyi.thirdparty.gfht.config.GfhtConfig;
import com.ruoyi.thirdparty.gfht.domain.GfhtResponse;
import com.ruoyi.thirdparty.gfht.service.GfhtService;
import com.ruoyi.thirdparty.gfht.util.GfhtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class GfhtServiceImpl implements GfhtService {
@Autowired
private TtRechargeProdMapper rechargeProdMapper;
@Autowired
private GfhtConfig gfhtConfig;
@Autowired
private TtOrderMapper orderMapper;
@Autowired
private TtUserMapper userMapper;
@Autowired
private TtRechargeProdMapper rechargeListMapper;
@Autowired
private TtUserService userService;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private TtRechargeRecordMapper ttRechargeRecordMapper;
@Autowired
private ISysConfigService configService;
@Autowired
private TtPromotionLevelMapper ttPromotionLevelMapper;
@Autowired
private TtFirstRechargeMapper ttFirstRechargeMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private TtRechargeConfigMapper ttRechargeConfigMapper;
@Autowired
private BaiduService baiduService;
@Override
public R pay(CreateOrderParam param, TtUser ttUser, String ip) {
TtRechargeProd goods;
if (param.getPayWay() != null && Objects.equals(2, param.getPayWay())) {
// 1.查询商品信息
TtRechargeConfig config = new LambdaQueryChainWrapper<>(ttRechargeConfigMapper)
.eq(TtRechargeConfig::getId, param.getGoodsId())
.eq(TtRechargeConfig::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(config)) {
return R.fail("不存在的价格配置");
}
if (config.getPrice().compareTo(param.getGoodsPrice()) != 0) {
return R.fail("配置的价格不一致");
}
goods = new TtRechargeProd();
BeanUtils.copyProperties(config, goods);
} else {
// 1.查询商品信息
goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, param.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) {
return R.fail("不存在的商品");
}
if (goods.getPrice().compareTo(param.getGoodsPrice()) != 0) {
return R.fail("商品价格不一致");
}
}
List<TtOrder> orders = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getUserId, ttUser.getUserId())
.eq(TtOrder::getType, PayType.GFHT_PAY.getCode())
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.gt(TtOrder::getCreateTime, new Date(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10)))
.list();
if (CollectionUtils.isNotEmpty(orders) && orders.size() > 2) {
return R.fail("操作频繁!");
}
// 计算总价值
BigDecimal totalAmount = param.getGoodsPrice().multiply(new BigDecimal(param.getGoodsNum()));
// 2.构建支付参数
Map<String, String> map = new HashMap<>();
map.put("pid", gfhtConfig.getPid());
map.put("type", param.getPayType());
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
String orderId = String.valueOf(snowflake.nextId());
map.put("out_trade_no", orderId);
map.put("notify_url", gfhtConfig.getNotifyUrl());
map.put("return_url", gfhtConfig.getReturnUrl());
map.put("name", goods.getName());
map.put("money", String.valueOf(totalAmount));
map.put("clientip", ip);
String key = gfhtConfig.getSign();
GfhtUtil signUtil = new GfhtUtil(key);
String sign = signUtil.getSign(map);
map.put("sign", sign);
map.put("sign_type", "MD5");
// 3.发送请求
// HttpRequest post = HttpUtil.createPost(gfhtConfig.getServerUrl());
// post.header("Content-Type","application/x-www-form-urlencoded");
// post.formStr(map);
// HttpResponse res = post.execute();
// 切换请求
// 获取服务器 URL
String url = gfhtConfig.getServerUrl();
// 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 构建表单数据
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
formParams.add(entry.getKey(), entry.getValue());
}
// 构建请求实体
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formParams, headers);
// 发送 POST 请求
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
// 获取响应
String result = response.getBody();
log.info("result is {}", result);
// 4.解析响应
GfhtResponse resBody = JSONUtil.toBean(result, GfhtResponse.class);
if (!resBody.getCode().equals("1")) {
return R.fail(resBody.getMsg());
}
// 5.创建订单
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setThirdParty(PartyType.INNER_PICTURE_PAY.getCode() + "");
order.setOutTradeNo(resBody.getTradeNo());
order.setUserId(ttUser.getUserId());
order.setType(PayType.GFHT_PAY.getCode());
order.setGoodsId(param.getGoodsId());
order.setGoodsPrice(param.getGoodsPrice());
order.setGoodsNum(param.getGoodsNum());
order.setTotalAmount(totalAmount);
order.setSign(sign);
order.setStatus(PayOrderStatus.NO_PAY.getCode());
order.setCreateTime(new Date());
order.setBdVid(param.getBdVid());
orderMapper.insert(order);
return R.ok(resBody);
}
@Override
public String notify(Map<String, String> params) {
log.info("支付成功通知");
String result = "failure";
// 校验
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus)) {
log.error("交易失败");
return result;
}
// 查询订单信息
String outTradeNo = params.get("trade_no");
TtOrder order = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getOutTradeNo, outTradeNo)
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.one();
// 防止重复通知
if (!PayOrderStatus.NO_PAY.getCode().equals(order.getStatus())) {
return result;
}
// 查询用户信息
TtUser user = new LambdaQueryChainWrapper<>(userMapper)
.eq(TtUser::getUserId, order.getUserId())
.eq(TtUser::getDelFlag, 0)
.one();
// 3.查询商品信息
TtRechargeProd goods;
boolean innerPicturePay = Objects.equals(PartyType.INNER_PICTURE_PAY.getCode() + "", order.getThirdParty());
if (innerPicturePay) {
goods = new TtRechargeProd();
TtRechargeConfig config = new LambdaQueryChainWrapper<>(ttRechargeConfigMapper)
.eq(TtRechargeConfig::getId, order.getGoodsId())
.eq(TtRechargeConfig::getStatus, 0)
.one();
BeanUtils.copyProperties(config, goods);
} else {
goods = new LambdaQueryChainWrapper<>(rechargeListMapper)
.eq(TtRechargeProd::getId, order.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
}
// 4.账户结算
if (ObjectUtil.isNull(goods.getProductA())) goods.setProductA(BigDecimal.ZERO);
if (ObjectUtil.isNull(goods.getProductC())) goods.setProductC(BigDecimal.ZERO);
payNotifyAccounting(order, user, goods, order.getGoodsNum());
try {
// 首充赠送
firstChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广等级充值赠送
promotionLevelChargeGiftAmount(user, goods, order.getGoodsNum());
// 推广充值返佣
promotionChargeCommission(user, goods, order.getGoodsNum());
// 回传百度推广信息
baiduService.upload(user.getUserId(), 10, order.getBdVid());
} catch (Exception e) {
log.error("happen exception is {}", e.getMessage(), e);
}
// 5.更新订单
boolean update = new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
if (update) {
result = "success";
}
// 6.发送充值通知
rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(order.getUserId().toString(), order.getGoodsPrice());
return result;
}
// 账户结算
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber) {
// 加钱
// BigDecimal totalAmount = order.getTotalAmount();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal totalCredits = goods.getProductC().multiply(new BigDecimal(goodsNumber));
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, user.getUserId())
.setSql("account_amount = account_amount + " + totalAmount.toString()
+ ",account_credits = account_credits + " + totalCredits.toString());
userService.update(userUpdate);
user = userService.getById(user.getUserId());
// 综合消费日志
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(user.getUserId())
.amount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? totalAmount : null)
.finalAmount(totalAmount.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountAmount().add(totalAmount) : null)
.credits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? totalCredits : null)
.finalCredits(totalCredits.compareTo(BigDecimal.ZERO) > 0 ? user.getAccountCredits().add(totalCredits) : null)
.total(totalAmount.add(totalCredits)) // 收支合计
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.RECHARGE.getCode())
.remark(TtAccountRecordSource.RECHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.updateTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
// 充值记录
TtRechargeRecord ttRechargeRecord = TtRechargeRecord.builder().build();
ttRechargeRecord.setUserId(order.getUserId());
ttRechargeRecord.setParentId(user.getParentId());
ttRechargeRecord.setArrivalAmount(totalAmount);
ttRechargeRecord.setAmountActuallyPaid(totalAmount);
ttRechargeRecord.setFinallyPrice(user.getAccountAmount());
ttRechargeRecord.setOrderId(order.getOrderId());
ttRechargeRecord.setOutTradeNo(order.getOutTradeNo());
ttRechargeRecord.setStatus("0");
ttRechargeRecord.setChannelType("1");
ttRechargeRecord.setCreateTime(DateUtils.getNowDate());
ttRechargeRecordMapper.insert(ttRechargeRecord);
// 更新VIP等级
userService.updateUserVIPLevel(user.getUserId());
}
/**
* 首充赠送
*/
private void firstChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.判断是否为首充
LambdaQueryWrapper<TtUserBlendErcash> wrapper = new LambdaQueryWrapper<>();
wrapper
.eq(TtUserBlendErcash::getSource, 1)
.eq(TtUserBlendErcash::getUserId, ttUser.getUserId());
List<TtUserBlendErcash> ttUserBlendErcashes = userBlendErcashMapper.selectList(wrapper);
if (ttUserBlendErcashes.size() > 0) {
return;
}
// 2.加钱
// 充值金额
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
// 查询赠送比例
BigDecimal firstChargeAmountRatio = ttFirstRechargeMapper.selectRatioByMinAmount(totalAmount);
if (Objects.isNull(firstChargeAmountRatio)) {
return;
}
// 计算赠送金额
BigDecimal giftAmount = totalAmount.multiply(firstChargeAmountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.FIRST_CHARGE.getCode())
.remark(TtAccountRecordSource.FIRST_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广等级充值赠送
*/
@Async
public void promotionLevelChargeGiftAmount(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
// 1.查询用户推广等级对应的赠送比例
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
if (Objects.isNull(ttPromotionLevel))
return;
// 2.加钱
BigDecimal amountRatio = ttPromotionLevel.getRechargeGiftRatio();
if (Objects.isNull(amountRatio))
return;
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getUserId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 3.记录
ttUser = userService.getById(ttUser.getUserId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getCode())
.remark(TtAccountRecordSource.PROMOTION_LEVEL_CHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
/**
* 推广充值返佣
*/
private void promotionChargeCommission(TtUser ttUser, TtRechargeProd goods, Integer goodsNumber) {
if (Objects.isNull(ttUser.getParentId())) {
return;
}
// 查询用户推广等级信息
TtPromotionLevel ttPromotionLevel = ttPromotionLevelMapper.selectById(ttUser.getPromotionLevel());
// 给上级加钱
BigDecimal amountRatio = ttPromotionLevel.getCommissions();
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal giftAmount = totalAmount.multiply(amountRatio);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, ttUser.getParentId())
.setSql("account_amount = account_amount + " + giftAmount.toString());
userService.update(userUpdate);
// 记录
ttUser = userService.getById(ttUser.getParentId());
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(ttUser.getUserId())
.amount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? giftAmount : null)
.finalAmount(giftAmount.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount().add(giftAmount) : null)
.total(giftAmount)
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getCode())
.remark(TtAccountRecordSource.PROMOTION_CHARGE_COMMISSION.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
}
}

View File

@@ -0,0 +1,76 @@
package com.ruoyi.thirdparty.gfht.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class GfhtUtil {
private String key;
public GfhtUtil(String key) {
this.key = key;
}
/**
* 生成MD5签名
*
* @param params 参数列表
* @return 生成的签名
*/
public String getSign(Map<String, String> params) {
// 排序参数
Map<String, String> sortedParams = new TreeMap<>(params);
sortedParams.remove("sign");
sortedParams.remove("sign_type");
// 构建签名字符串
StringBuilder signStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
signStr.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
if (signStr.length() > 0) {
signStr.setLength(signStr.length() - 1); // 移除最后一个&
}
// 添加商户密钥
signStr.append(this.key);
// 计算MD5签名
return md5(signStr.toString()).toLowerCase();
}
/**
* 计算MD5哈希值
*
* @param input 输入字符串
* @return MD5哈希值
*/
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
return bytesToHex(messageDigest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not found", e);
}
}
/**
* 将字节数组转换为十六进制字符串
*
* @param bytes 字节数组
* @return 十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,52 @@
package com.ruoyi.thirdparty.hipay.controller;
import cn.hutool.json.JSONUtil;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.UserPermission;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.CreateOrderParam;
import com.ruoyi.thirdparty.hipay.service.HipayPaymentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Api(tags = "")
@Slf4j
@RestController
@RequestMapping("/api/hipay")
public class HiPayController {
@Autowired
private TtUserService ttUserService;
@Autowired
private HipayPaymentService hipayPaymentService;
@ApiOperation("支付")
@UserPermission
@PostMapping(value = "/pay")
public R pay(@RequestBody @Validated PayRequest param) {
Long userId = SecurityUtils.getUserId();
TtUser ttUser = ttUserService.getById(userId);
// 是否实名认证0未认证 1已认证
if ("0".equals(ttUser.getIsRealCheck())) {
return R.fail("未实名认证");
}
String ip = IpUtils.getIpAddr();
return hipayPaymentService.pay(userId, param);
}
@PostMapping("/notify")
public R notify(@RequestBody Map<String, String> params) {
log.info("HiPay notify params: {}", JSONUtil.toJsonStr(params));
return hipayPaymentService.notifyCallback(params);
}
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.thirdparty.hipay.controller;
import io.swagger.annotations.ApiModelProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class PayRequest {
@NotNull(message = "商品id不能为空。")
private Integer goodsId;
@NotNull(message = "商品数量不能为空。")
@Min(value = 1, message = "最小值1")
private Integer goodsNum;
@ApiModelProperty("1:支付宝1 2:支付宝2")
private Integer payType;
@ApiModelProperty("1原有支付2图片支付")
private Integer payWay = 1;
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.thirdparty.hipay.controller;
import lombok.Data;
@Data
public class PayResponse {
private String qrUrl;
private String payUrl;
private String orderId;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.thirdparty.hipay.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum StateEnum {
CREATE(2, "已创建"),
EXCHANGE(5, "已转币"),
CANCEL(9, "已取消"),
ERROR(99, "错误"),
;
private final int code;
private final String desc;
}

View File

@@ -0,0 +1,26 @@
package com.ruoyi.thirdparty.hipay.model;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class NotifyData {
private BigDecimal amount;
private long createtime;
private String id;
private String note;
private String notifyurl;
private String orderid;
private int ordertype;
private BigDecimal recvcharge;
private String recvid;
private String recvnickname;
private String resign;
private String returnurl;
private String sign;
private int state;
private long transtime;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.thirdparty.hipay.model;
import lombok.Data;
@Data
public class PaymentResponse {
private int code;
private String msg;
private String payUrl;
private String qrcode;
private String tradeNo;
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.thirdparty.hipay.model;
import lombok.Data;
@Data
public class PaymentResult {
private int code;
private String msg;
private PaymentData data;
@Data
public static class PaymentData {
private String qrurl;
private String payurl;
private String orderid;
}
}

View File

@@ -0,0 +1,310 @@
package com.ruoyi.thirdparty.hipay.service;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ruoyi.admin.mapper.TtOrderMapper;
import com.ruoyi.admin.mapper.TtRechargeProdMapper;
import com.ruoyi.admin.mapper.TtRechargeRecordMapper;
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
import com.ruoyi.admin.mapper.TtUserMapper;
import com.ruoyi.admin.service.TtBonusService;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MoneyUtil;
import com.ruoyi.domain.common.constant.PayOrderStatus;
import com.ruoyi.domain.common.constant.PayType;
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
import com.ruoyi.domain.common.constant.TtAccountRecordType;
import com.ruoyi.domain.entity.TtOrder;
import com.ruoyi.domain.entity.TtRechargeProd;
import com.ruoyi.domain.entity.TtUserBlendErcash;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.domain.other.TtRechargeRecord;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.thirdparty.common.service.RechargeSuccessfulNoticeService;
import com.ruoyi.thirdparty.hipay.controller.PayRequest;
import com.ruoyi.thirdparty.hipay.controller.PayResponse;
import com.ruoyi.thirdparty.hipay.enums.StateEnum;
import com.ruoyi.thirdparty.hipay.model.NotifyData;
import com.ruoyi.thirdparty.hipay.model.PaymentResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class HipayPaymentService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private TtOrderMapper orderMapper;
private static final String PAY_URL = "https://xahnob7yqke3.hipay.one/pay/pay/createpay";
private static final String RECV_ID = "e42835c685f0712b19f27d693ee11f42";
private static final String API_KEY = "aa1818c04f964043900d21bf38a83768";
private static final String PAYCHANNELID1 = "610"; // 支付宝1 小额超级支付宝
private static final String PAYCHANNELID2 = "616"; // 支付宝2 大额超级支付宝
@Autowired
private TtRechargeProdMapper rechargeProdMapper;
@Autowired
private TtUserMapper userMapper;
@Autowired
private RechargeSuccessfulNoticeService rechargeSuccessfulNoticeService;
@Autowired
private TtRechargeRecordMapper ttRechargeRecordMapper;
@Autowired
private TtUserService userService;
@Autowired
private TtUserBlendErcashMapper userBlendErcashMapper;
@Autowired
private TtBonusService bonusService;
public R<PayResponse> pay(Long uid, PayRequest request) {
// 1.查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, request.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
if (ObjectUtil.isEmpty(goods)) {
return R.fail("不存在的商品");
}
// 计算总价值
BigDecimal totalAmount = goods.getPrice().multiply(new BigDecimal(request.getGoodsNum()));
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
String orderId = String.valueOf(snowflake.nextId());
String amount = MoneyUtil.toStr(totalAmount);
// MD5签名
String signText = "pay" + RECV_ID + orderId + amount + API_KEY;
String sign = DigestUtils.md5DigestAsHex(signText.getBytes(StandardCharsets.UTF_8));
String paychannelid;
if (request.getPayType() == 2) {
paychannelid = PAYCHANNELID2;
} else {
paychannelid = PAYCHANNELID1;
}
Map<String, Object> data = new HashMap<>();
data.put("recvid", RECV_ID);
data.put("orderid", orderId);
data.put("amount", amount);
data.put("sign", sign);
data.put("paychannelid", paychannelid);
data.put("memuid", DigestUtils.md5DigestAsHex((uid + "").getBytes(StandardCharsets.UTF_8)));
data.put("notifyurl", "http://154.12.94.98:8081/api/hipay/notify");
data.put("returnurl", "");
data.put("note", "充值" + amount);
log.info("Hipay request data: {}", JSONUtil.toJsonStr(data));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(data, headers);
ResponseEntity<String> response = restTemplate.exchange(
PAY_URL,
HttpMethod.POST,
entity,
String.class
);
String result = response.getBody();
log.info("Hipay response data: {}", result);
PaymentResult resBody = JSONUtil.toBean(result, PaymentResult.class);
if (resBody.getCode() != 1) {
return R.fail(resBody.getMsg());
}
TtOrder order = new TtOrder();
order.setOrderId(orderId);
order.setOutTradeNo(resBody.getData().getOrderid());
order.setUserId(Math.toIntExact(uid));
order.setType(PayType.HI_PAY.getCode());
order.setGoodsId(request.getGoodsId());
order.setGoodsPrice(goods.getPrice());
order.setGoodsNum(request.getGoodsNum());
order.setTotalAmount(totalAmount);
order.setSign(sign);
order.setStatus(PayOrderStatus.NO_PAY.getCode());
order.setCreateTime(new Date());
orderMapper.insert(order);
PayResponse resp = new PayResponse();
resp.setQrUrl(resBody.getData().getQrurl());
resp.setPayUrl(resBody.getData().getPayurl());
resp.setOrderId(orderId);
return R.ok(resp);
}
public R<Void> notifyCallback(Map<String, String> params) {
log.info("支付回调通知");
String code = params.get("code");
if (!"1".equals(code)) {
log.error("支付失败code={}, msg={}", code, params.get("msg"));
return R.fail("支付失败");
}
NotifyData resBody = JSONUtil.toBean(params.get("data"), NotifyData.class);
// 校验
int tradeStatus = resBody.getState();
if (StateEnum.EXCHANGE.getCode() != tradeStatus) {
log.error("交易失败");
return R.fail("交易失败");
}
String sign = resBody.getSign();
String resign = resBody.getResign();
String d = DigestUtils.md5DigestAsHex((sign + API_KEY).getBytes(StandardCharsets.UTF_8));
if (!d.equals(resign)) {
return R.fail("签名验证失败");
}
// 查询订单信息
String outTradeNo = resBody.getOrderid();
TtOrder order = new LambdaQueryChainWrapper<>(orderMapper)
.eq(TtOrder::getOutTradeNo, outTradeNo)
.eq(TtOrder::getStatus, PayOrderStatus.NO_PAY.getCode())
.one();
if (order == null) {
return R.fail("订单不存在");
}
// 防止重复通知
if (!PayOrderStatus.NO_PAY.getCode().equals(order.getStatus())) {
return R.fail("重复通知");
}
// 查询用户信息
TtUser user = new LambdaQueryChainWrapper<>(userMapper)
.eq(TtUser::getUserId, order.getUserId())
.eq(TtUser::getDelFlag, 0)
.one();
// 3.查询商品信息
TtRechargeProd goods = new LambdaQueryChainWrapper<>(rechargeProdMapper)
.eq(TtRechargeProd::getId, order.getGoodsId())
.eq(TtRechargeProd::getStatus, 0)
.one();
// 4.账户结算
if (ObjectUtil.isNull(goods.getProductA())) goods.setProductA(BigDecimal.ZERO);
if (ObjectUtil.isNull(goods.getProductC())) goods.setProductC(BigDecimal.ZERO);
payNotifyAccounting(order, user, goods, order.getGoodsNum());
// try {
// // 首充赠送
// firstChargeGiftAmount(user, goods, order.getGoodsNum());
//
// // 推广等级充值赠送
// promotionLevelChargeGiftAmount(user, goods, order.getGoodsNum());
//
// // 推广充值返佣
// promotionChargeCommission(user, goods, order.getGoodsNum());
// } catch (Exception e) {
// e.printStackTrace();
// }
// 5.更新订单
new LambdaUpdateChainWrapper<>(orderMapper)
.eq(TtOrder::getId, order.getId())
.set(TtOrder::getStatus, PayOrderStatus.PAY_COMPLE.getCode())
.set(TtOrder::getUpdateTime, new Date())
.update();
// 6.发送充值通知
rechargeSuccessfulNoticeService.sendRechargeSuccessNotice(order.getUserId().toString(), order.getGoodsPrice());
return R.ok();
}
// 账户结算
public void payNotifyAccounting(TtOrder order, TtUser user, TtRechargeProd goods, Integer goodsNumber) {
// 加钱
BigDecimal totalAmount = goods.getProductA().multiply(new BigDecimal(goodsNumber));
BigDecimal oldAmount = user.getAccountAmount();
if (oldAmount == null) {
oldAmount = BigDecimal.ZERO;
}
BigDecimal newAmount = oldAmount.add(totalAmount);
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
userUpdate
.eq(TtUser::getUserId, user.getUserId())
.setSql("account_amount = account_amount + " + totalAmount);
userService.update(userUpdate);
// 综合消费日志
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
.userId(user.getUserId())
.amount(totalAmount)
.finalAmount(newAmount)
.total(totalAmount) // 收支合计
.type(TtAccountRecordType.INPUT.getCode())
.source(TtAccountRecordSource.RECHARGE.getCode())
.remark(TtAccountRecordSource.RECHARGE.getMsg())
.createTime(new Timestamp(System.currentTimeMillis()))
.updateTime(new Timestamp(System.currentTimeMillis()))
.build();
userBlendErcashMapper.insert(blendErcash);
// 充值记录
TtRechargeRecord ttRechargeRecord = TtRechargeRecord.builder().build();
ttRechargeRecord.setUserId(order.getUserId());
ttRechargeRecord.setParentId(user.getParentId());
ttRechargeRecord.setArrivalAmount(totalAmount);
ttRechargeRecord.setAmountActuallyPaid(totalAmount);
ttRechargeRecord.setFinallyPrice(newAmount);
ttRechargeRecord.setOrderId(order.getOrderId());
ttRechargeRecord.setOutTradeNo(order.getOutTradeNo());
ttRechargeRecord.setStatus("0");
ttRechargeRecord.setChannelType("1");
ttRechargeRecord.setCreateTime(DateUtils.getNowDate());
ttRechargeRecordMapper.insert(ttRechargeRecord);
// 更新VIP等级
userService.updateUserVIPLevel(user.getUserId());
// AsyncManager.me().run(() -> {
// bonusService.bonus(user.getUserId(), ttRechargeRecord.getId());
// });
}
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.thirdparty.jiujia.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "jiu-jia-pay")
public class JiuJiaProperties {
private String aliPayUrl;
private String memberId;
private String appKey;
private String apiSecret;
private String apiDomain;
private String callbackUrl;
}

View File

@@ -0,0 +1,56 @@
package com.ruoyi.thirdparty.jiujia.controller;
import com.ruoyi.domain.entity.sys.TtUser;
import com.ruoyi.admin.service.TtUserService;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.thirdparty.jiujia.domain.CallbackBody;
import com.ruoyi.thirdparty.jiujia.service.JiuJiaPayService;
import com.ruoyi.domain.other.CreateOrderParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "九嘉支付")
@RestController
@RequestMapping("/api/jiuJiaPay")
public class JiuJiaPayController extends BaseController {
private final ISysConfigService sysConfigService;
private final TtUserService userService;
private final JiuJiaPayService jiuJiaPayService;
public JiuJiaPayController(ISysConfigService sysConfigService,
TtUserService userService,
JiuJiaPayService jiuJiaPayService) {
this.sysConfigService = sysConfigService;
this.userService = userService;
this.jiuJiaPayService = jiuJiaPayService;
}
// TODO: 2024/4/11 要修改!!!
@ApiOperation("回滚")
@Anonymous
@PostMapping("/callback")
public String callback(@RequestBody CallbackBody callbackBody) {
return jiuJiaPayService.callback(callbackBody);
}
@ApiOperation("九嘉预下单")
@PostMapping("/createPay")
public R<Object> createPay(@RequestBody CreateOrderParam createOrderBody) {
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
if ("1".equals(websiteMaintenance)) {
return R.fail("网站维护中......");
}
TtUser ttUser = userService.getById(getUserId());
String msg = jiuJiaPayService.createPay(createOrderBody, ttUser);
return msg.startsWith("https") ? R.ok(msg) : R.fail(msg);
}
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.thirdparty.jiujia.domain;
import lombok.Data;
@Data
public class CallbackBody {
private String member_id;
private String total_fee;
private String result_code;
private String trade_no;
private String out_trade_no;
private String time_end;
private String pay_type;
private String ext_sign;
private String sign;
}

View File

@@ -0,0 +1,21 @@
package com.ruoyi.thirdparty.jiujia.domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@Builder
public class CheckOrderRequestParam {
@JsonProperty("app_key")
private String appKey;
@JsonProperty("member_id")
private String memberId;
@JsonProperty("out_trade_no")
private String outTradeNo;
}

Some files were not shown because too many files have changed in this diff Show More