feat(payment): 添加支付功能支持微信和支付宝

- 集成微信支付和支付宝支付SDK
- 添加支付配置文件和配置类
- 实现支付控制器提供创建订单和处理回调接口
- 更新内容购买服务支持第三方支付方式
- 添加支付订单服务处理支付状态更新
- 修改CMS内容服务添加收藏和查看记录功能
- 更新应用配置文件适配开发环境和Redis连接
- 升级分页插件版本并添加统一SQL解析器依赖
This commit is contained in:
wangzhiwei 2026-02-26 17:30:55 +08:00
parent d33d567b81
commit e16fbdf2d6
16 changed files with 976 additions and 56 deletions

27
pom.xml
View File

@ -164,7 +164,7 @@
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
<version>1.4.6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
@ -198,9 +198,20 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 统一的jsqlparser版本 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.4</version>
</dependency>
<!-- Sa-Token 核心依赖 -->
<dependency>
<groupId>cn.dev33</groupId>
@ -257,6 +268,20 @@
<version>1.2.83</version>
</dependency>
<!-- 微信支付SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.4.0</version>
</dependency>
<!-- 支付宝SDK -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.0.ALL</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>

View File

@ -55,4 +55,31 @@ public class HttpUtil {
return response.body();
}
/**
* 发送POST请求到指定URL
*
* @param url 请求URL
* @param requestBody 请求体字符串
* @return 响应结果
* @throws Exception 异常信息
*/
public static String post(String url, String requestBody) throws Exception {
// 创建HttpClient实例
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
// 构建HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/xml")
.POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
.build();
// 发送请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
}

View File

@ -0,0 +1,66 @@
package com.kexue.skills.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 支付配置类
*/
@Component
@ConfigurationProperties(prefix = "payment")
@Data
public class PaymentConfig {
// 微信支付配置
private WechatPayConfig wechat;
// 支付宝支付配置
private AlipayConfig alipay;
/**
* 微信支付配置
*/
@Data
public static class WechatPayConfig {
// 应用ID
private String appId;
// 商户号
private String mchId;
// 商户密钥
private String mchKey;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件路径
private String privateKeyPath;
// 微信服务器地址
private String domain;
// 支付回调地址
private String notifyUrl;
// 支付成功跳转地址
private String returnUrl;
}
/**
* 支付宝支付配置
*/
@Data
public static class AlipayConfig {
// 应用ID
private String appId;
// 商户私钥
private String privateKey;
// 支付宝公钥
private String publicKey;
// 支付回调地址
private String notifyUrl;
// 支付成功跳转地址
private String returnUrl;
// 签名类型
private String signType;
// 字符编码
private String charset;
// 支付宝网关
private String gatewayUrl;
}
}

View File

@ -170,4 +170,30 @@ public class CmsContentController {
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
return CommonResult.success(cmsContentService.deleteById(contentId) > 0);
}
/**
* 添加收藏
*
* @param contentId 内容ID
* @return 操作结果
*/
@PostMapping("/addFavorite")
@Operation(summary = "添加收藏", description = "添加内容收藏")
@RequireAuth
public CommonResult<Boolean> addFavorite(@RequestParam("contentId") Long contentId) {
return CommonResult.success(cmsContentService.addFavorite(contentId) > 0);
}
/**
* 添加查看记录
*
* @param contentId 内容ID
* @return 操作结果
*/
@PostMapping("/addView")
@Operation(summary = "添加查看记录", description = "添加内容查看记录")
@RequireAuth
public CommonResult<Boolean> addView(@RequestParam("contentId") Long contentId) {
return CommonResult.success(cmsContentService.addView(contentId) > 0);
}
}

View File

@ -0,0 +1,110 @@
package com.kexue.skills.controller;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.PaymentOrder;
import com.kexue.skills.service.PayService;
import com.kexue.skills.service.PaymentOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 支付控制器
*/
@Tag(name = "支付管理 Api")
@RestController
@RequestMapping("/api/pay")
public class PayController {
private static final Logger logger = LoggerFactory.getLogger(PayController.class);
@Resource
private PayService payService;
@Resource
private PaymentOrderService paymentOrderService;
/**
* 创建微信支付订单
* @param order 支付订单信息
* @return 微信支付参数
*/
@Operation(summary = "创建微信支付订单", description = "创建微信支付订单")
@PostMapping("/wx/create")
public CommonResult<Map<String, String>> createWechatPay(@RequestBody PaymentOrder order) {
try {
// 创建支付订单
PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order);
// 生成微信支付参数
Map<String, String> payParams = payService.createWechatPay(createdOrder);
return CommonResult.success(payParams);
} catch (Exception e) {
logger.error("创建微信支付订单失败", e);
return CommonResult.failed("创建微信支付订单失败: " + e.getMessage());
}
}
/**
* 处理微信支付回调
* @param request HTTP请求
* @return 回调响应
*/
@Operation(summary = "处理微信支付回调", description = "处理微信支付回调")
@PostMapping("/pay/wx/notify")
public String handleWechatPayNotify(HttpServletRequest request) {
return payService.handleWechatPayNotify(request);
}
/**
* 创建支付宝支付订单
* @param order 支付订单信息
* @return 支付宝支付表单
*/
@Operation(summary = "创建支付宝支付订单", description = "创建支付宝支付订单")
@PostMapping("/alipay/create")
public CommonResult<String> createAlipay(@RequestBody PaymentOrder order) {
try {
// 创建支付订单
PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order);
// 生成支付宝支付表单
String form = payService.createAlipay(createdOrder);
return CommonResult.success(form);
} catch (Exception e) {
logger.error("创建支付宝支付订单失败", e);
return CommonResult.failed("创建支付宝支付订单失败: " + e.getMessage());
}
}
/**
* 处理支付宝支付回调
* @param request HTTP请求
* @return 回调响应
*/
@Operation(summary = "处理支付宝支付回调", description = "处理支付宝支付回调")
@PostMapping("/ali-pay/trade/notify")
public String handleAlipayNotify(HttpServletRequest request) {
return payService.handleAlipayNotify(request);
}
/**
* 处理支付宝支付同步回调
* @param request HTTP请求
* @return 同步回调响应
*/
@Operation(summary = "处理支付宝支付同步回调", description = "处理支付宝支付同步回调")
@GetMapping("/ali-pay/trade/return")
public CommonResult<Map<String, Object>> handleAlipayReturn(HttpServletRequest request) {
Map<String, Object> result = payService.handleAlipayReturn(request);
if (result.get("success").equals(true)) {
return CommonResult.success(result);
} else {
return CommonResult.failed(result.get("message").toString());
}
}
}

View File

@ -23,6 +23,24 @@ public interface CmsContentMapper {
*/
List<CmsContent> getPageList(CmsContentDto queryDto);
/**
* 带分页的查询
*
* @param queryDto 筛选条件
* @param offset 偏移量
* @param limit 限制数量
* @return 查询结果
*/
List<CmsContent> getPageListWithPagination(@Param("queryDto") CmsContentDto queryDto, @Param("offset") int offset, @Param("limit") int limit);
/**
* 查询总记录数
*
* @param queryDto 筛选条件
* @return 总记录数
*/
int getPageListCount(CmsContentDto queryDto);
/**
* 查询列表
*

View File

@ -37,15 +37,6 @@ public interface CmsContentService extends BaseService {
*/
CmsContent queryById(Long contentId);
/**
* 通过主键查询单条数据带用户ID
*
* @param contentId 主键
* @param userId 用户ID
* @return 实例对象
*/
CmsContent queryById(Long contentId, Long userId);
/**
* 新增数据
*
@ -115,27 +106,31 @@ public interface CmsContentService extends BaseService {
* 添加收藏
*
* @param contentId 内容ID
* @param userId 用户ID
* @param userName 用户名
* @return 影响行数
*/
int addFavorite(Long contentId, Long userId, String userName);
int addFavorite(Long contentId);
/**
* 取消收藏
*
* @param contentId 内容ID
* @param userId 用户ID
* @return 影响行数
*/
int removeFavorite(Long contentId, Long userId);
int removeFavorite(Long contentId);
/**
* 检查用户是否已收藏该内容
*
* @param contentId 内容ID
* @param userId 用户ID
* @return 是否已收藏
*/
boolean isFavorited(Long contentId, Long userId);
boolean isFavorited(Long contentId);
/**
* 添加查看记录
*
* @param contentId 内容ID
* @return 影响行数
*/
int addView(Long contentId);
}

View File

@ -0,0 +1,45 @@
package com.kexue.skills.service;
import com.kexue.skills.entity.PaymentOrder;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 支付服务接口
*/
public interface PayService {
/**
* 创建微信支付订单
* @param order 支付订单信息
* @return 微信支付参数
*/
Map<String, String> createWechatPay(PaymentOrder order);
/**
* 处理微信支付回调
* @param request HTTP请求
* @return 回调响应
*/
String handleWechatPayNotify(HttpServletRequest request);
/**
* 创建支付宝支付订单
* @param order 支付订单信息
* @return 支付宝支付表单
*/
String createAlipay(PaymentOrder order);
/**
* 处理支付宝支付回调
* @param request HTTP请求
* @return 回调响应
*/
String handleAlipayNotify(HttpServletRequest request);
/**
* 处理支付宝支付同步回调
* @param request HTTP请求
* @return 同步回调响应
*/
Map<String, Object> handleAlipayReturn(HttpServletRequest request);
}

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.CmsContent;
import com.kexue.skills.entity.CmsContentView;
import com.kexue.skills.entity.CmsContentLike;
import com.kexue.skills.entity.base.BaseQueryDto;
import com.kexue.skills.entity.dto.CmsContentDto;
import com.kexue.skills.mapper.CmsContentMapper;
import com.kexue.skills.mapper.CmsContentViewMapper;
@ -17,6 +18,8 @@ import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import cn.dev33.satoken.stp.StpUtil;
/**
* (CmsContent)表服务实现类
*
@ -43,9 +46,26 @@ public class CmsContentServiceImpl implements CmsContentService {
*/
@Override
public PageInfo<CmsContent> getPageList(CmsContentDto queryDto) {
// 添加参数校验确保分页参数有效
if (queryDto.getPageNum() == null || queryDto.getPageNum() < 1) {
queryDto.setPageNum(BaseQueryDto.DEFAULT_CURRENT_PAGE);
}
if (queryDto.getPageSize() == null || queryDto.getPageSize() < 1) {
queryDto.setPageSize(BaseQueryDto.DEFAULT_PAGE_SIZE);
}
// 打印分页参数
System.out.println("PageNum: " + queryDto.getPageNum() + ", PageSize: " + queryDto.getPageSize());
// 使用PageHelper进行分页
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
List<CmsContent> list = this.cmsContentMapper.getPageList(queryDto);
return new PageInfo<>(list);
PageInfo<CmsContent> pageInfo = new PageInfo<>(list);
// 打印分页结果
System.out.println("Total: " + pageInfo.getTotal() + ", PageSize: " + pageInfo.getPageSize() + ", List Size: " + list.size());
return pageInfo;
}
/**
@ -67,35 +87,27 @@ public class CmsContentServiceImpl implements CmsContentService {
*/
@Override
public CmsContent queryById(Long contentId) {
return this.cmsContentMapper.queryById(contentId);
}
/**
* 通过主键查询单条数据带用户ID
*
* @param contentId 主键
* @param userId 用户ID
* @return 实例对象
*/
@Override
public CmsContent queryById(Long contentId, Long userId) {
CmsContent content = this.cmsContentMapper.queryById(contentId);
if (content == null) {
return null;
}
// 如果用户ID不为空添加用户查看记录
if (userId != null) {
try {
// 尝试获取当前登录用户ID
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
// 检查用户是否已经查看过该内容5分钟内
CmsContentView existingView = cmsContentViewMapper.queryByUserIdAndContentId(userId, contentId);
if (existingView == null) {
// 新增查看记录
CmsContentView viewRecord = new CmsContentView();
viewRecord.setUserId(userId);
viewRecord.setUserName(StpUtil.getLoginIdAsString());
viewRecord.setContentId(contentId);
viewRecord.setContentTitle(content.getTitle());
viewRecord.setViewTime(new Date());
viewRecord.setDeleteFlag(0);
viewRecord.setCreateBy(StpUtil.getLoginIdAsString());
viewRecord.setUpdateBy(StpUtil.getLoginIdAsString());
cmsContentViewMapper.insert(viewRecord);
} else {
// 检查是否超过5分钟
@ -103,9 +115,12 @@ public class CmsContentServiceImpl implements CmsContentService {
if (existingView.getViewTime().before(fiveMinutesAgo)) {
// 更新查看时间
existingView.setViewTime(new Date());
existingView.setUpdateBy(StpUtil.getLoginIdAsString());
cmsContentViewMapper.update(existingView);
}
}
} catch (Exception e) {
// 未登录用户不记录查看历史
}
return content;
@ -253,12 +268,15 @@ public class CmsContentServiceImpl implements CmsContentService {
* 添加收藏
*
* @param contentId 内容ID
* @param userId 用户ID
* @param userName 用户名
* @return 影响行数
*/
@Override
public int addFavorite(Long contentId, Long userId, String userName) {
public int addFavorite(Long contentId) {
// 获取当前登录用户ID
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
// 获取当前登录用户名
String userName = StpUtil.getLoginIdAsString();
// 检查内容是否存在
CmsContent content = this.cmsContentMapper.queryById(contentId);
if (content == null) {
@ -291,11 +309,13 @@ public class CmsContentServiceImpl implements CmsContentService {
* 取消收藏
*
* @param contentId 内容ID
* @param userId 用户ID
* @return 影响行数
*/
@Override
public int removeFavorite(Long contentId, Long userId) {
public int removeFavorite(Long contentId) {
// 获取当前登录用户ID
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
// 检查用户是否已经收藏过该内容
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
if (existingLike == null) {
@ -319,15 +339,69 @@ public class CmsContentServiceImpl implements CmsContentService {
* 检查用户是否已收藏该内容
*
* @param contentId 内容ID
* @param userId 用户ID
* @return 是否已收藏
*/
@Override
public boolean isFavorited(Long contentId, Long userId) {
if (userId == null) {
public boolean isFavorited(Long contentId) {
try {
// 获取当前登录用户ID
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
return existingLike != null;
} catch (Exception e) {
// 未登录用户返回false
return false;
}
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
return existingLike != null;
}
/**
* 添加查看记录
*
* @param contentId 内容ID
* @return 影响行数
*/
@Override
public int addView(Long contentId) {
try {
// 获取当前登录用户ID
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
// 获取当前登录用户名
String userName = StpUtil.getLoginIdAsString();
// 检查内容是否存在
CmsContent content = this.cmsContentMapper.queryById(contentId);
if (content == null) {
return 0;
}
// 检查用户是否已经查看过该内容5分钟内
CmsContentView existingView = cmsContentViewMapper.queryByUserIdAndContentId(userId, contentId);
if (existingView == null) {
// 新增查看记录
CmsContentView viewRecord = new CmsContentView();
viewRecord.setUserId(userId);
viewRecord.setUserName(userName);
viewRecord.setContentId(contentId);
viewRecord.setContentTitle(content.getTitle());
viewRecord.setViewTime(new Date());
viewRecord.setDeleteFlag(0);
viewRecord.setCreateBy(userName);
viewRecord.setUpdateBy(userName);
return cmsContentViewMapper.insert(viewRecord);
} else {
// 检查是否超过5分钟
Date fiveMinutesAgo = new Date(System.currentTimeMillis() - 5 * 60 * 1000);
if (existingView.getViewTime().before(fiveMinutesAgo)) {
// 更新查看时间
existingView.setViewTime(new Date());
existingView.setUpdateBy(userName);
return cmsContentViewMapper.update(existingView);
}
return 0; // 5分钟内已查看过不重复记录
}
} catch (Exception e) {
// 未登录用户不记录查看历史
return 0;
}
}
}

View File

@ -163,7 +163,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
*
* @param userId 用户ID
* @param contentId 内容ID
* @param payType 支付方式1.余额支付 2.积分支付
* @param payType 支付方式1.余额支付 2.积分支付 3.微信支付 4.支付宝支付
* @return 购买结果
*/
@Override
@ -203,6 +203,11 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
// 扣除用户余额
this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
// 6. 更新购买记录状态
purchase.setStatus(2); // 已支付
purchase.setPurchaseTime(new Date());
this.insert(purchase);
} else if (payType == 2) {
// 积分支付
Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付");
@ -213,15 +218,25 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
// 扣除用户积分
this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
// 6. 更新购买记录状态
purchase.setStatus(2); // 已支付
purchase.setPurchaseTime(new Date());
this.insert(purchase);
} else if (payType == 3 || payType == 4) {
// 微信支付或支付宝支付
// 这里只创建购买记录实际支付由前端调用支付接口完成
// 支付完成后通过回调更新购买记录状态
BigDecimal price = content.getPrice();
purchase.setAmount(price);
purchase.setPoints(0);
// 插入购买记录状态为待支付
this.insert(purchase);
} else {
Assert.isTrue(false, "不支持的支付方式");
}
// 6. 更新购买记录状态
purchase.setStatus(2); // 已支付
purchase.setPurchaseTime(new Date());
this.insert(purchase);
return purchase;
}

View File

@ -0,0 +1,392 @@
package com.kexue.skills.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.kexue.skills.common.util.HttpUtil;
import com.kexue.skills.config.PaymentConfig;
import com.kexue.skills.entity.PaymentOrder;
import com.kexue.skills.service.PayService;
import com.kexue.skills.service.PaymentOrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* 支付服务实现类
*/
@Service("payService")
public class PayServiceImpl implements PayService {
private static final Logger logger = LoggerFactory.getLogger(PayServiceImpl.class);
@Resource
private PaymentConfig paymentConfig;
@Resource
private PaymentOrderService paymentOrderService;
/**
* 生成随机字符串
* @return 随机字符串
*/
private String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成MD5签名
* @param params 参数
* @param key 密钥
* @return 签名
*/
private String generateSignature(Map<String, String> params, String key) {
// 排序参数
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 构建签名字符串
StringBuilder sb = new StringBuilder();
for (String k : keys) {
String v = params.get(k);
if (v != null && !v.isEmpty()) {
sb.append(k).append("=").append(v).append("&");
}
}
sb.append("key=").append(key);
// 生成MD5
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
result.append("0");
}
result.append(hex);
}
return result.toString().toUpperCase();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
logger.error("生成签名失败", e);
throw new RuntimeException("生成签名失败", e);
}
}
/**
* 将Map转换为XML
* @param params 参数
* @return XML字符串
*/
private String mapToXml(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* 将XML转换为Map
* @param xml XML字符串
* @return Map
*/
private Map<String, String> xmlToMap(String xml) {
Map<String, String> map = new HashMap<>();
try {
// 简单的XML解析
String content = xml.replaceAll("<xml>", "").replaceAll("</xml>", "");
String[] elements = content.split("</");
for (String element : elements) {
if (element.isEmpty()) continue;
int idx = element.indexOf(">");
if (idx == -1) continue;
String key = element.substring(1, idx);
String value = element.substring(idx + 1);
map.put(key, value);
}
} catch (Exception e) {
logger.error("XML解析失败", e);
throw new RuntimeException("XML解析失败", e);
}
return map;
}
/**
* 创建微信支付订单
* @param order 支付订单信息
* @return 微信支付参数
*/
@Override
public Map<String, String> createWechatPay(PaymentOrder order) {
try {
// 构建微信支付参数
Map<String, String> params = new HashMap<>();
params.put("appid", paymentConfig.getWechat().getAppId());
params.put("mch_id", paymentConfig.getWechat().getMchId());
params.put("nonce_str", generateNonceStr());
params.put("body", order.getProductName());
params.put("out_trade_no", order.getOrderNo());
params.put("total_fee", String.valueOf((int) (order.getAmount().doubleValue() * 100)));
params.put("spbill_create_ip", "127.0.0.1");
params.put("notify_url", paymentConfig.getWechat().getNotifyUrl());
params.put("trade_type", "NATIVE");
// 生成签名
String sign = generateSignature(params, paymentConfig.getWechat().getMchKey());
params.put("sign", sign);
// 构建请求XML
String xml = mapToXml(params);
// 发送请求到微信支付接口
String xmlResult = HttpUtil.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xml);
Map<String, String> result = xmlToMap(xmlResult);
// 处理响应
if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) {
Map<String, String> payParams = new HashMap<>();
payParams.put("code_url", result.get("code_url"));
payParams.put("order_no", order.getOrderNo());
return payParams;
} else {
logger.error("微信支付下单失败: {}", result.get("return_msg"));
throw new RuntimeException("微信支付下单失败: " + result.get("return_msg"));
}
} catch (Exception e) {
logger.error("创建微信支付订单失败", e);
throw new RuntimeException("创建微信支付订单失败", e);
}
}
/**
* 处理微信支付回调
* @param request HTTP请求
* @return 回调响应
*/
@Override
public String handleWechatPayNotify(HttpServletRequest request) {
try {
// 读取回调数据
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String xmlData = sb.toString();
// 解析XML
Map<String, String> params = xmlToMap(xmlData);
// 验证签名
String sign = params.get("sign");
params.remove("sign");
String expectedSign = generateSignature(params, paymentConfig.getWechat().getMchKey());
if (!sign.equals(expectedSign)) {
logger.error("微信支付回调签名验证失败");
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml>";
}
// 处理支付结果
if ("SUCCESS".equals(params.get("return_code")) && "SUCCESS".equals(params.get("result_code"))) {
String orderNo = params.get("out_trade_no");
String transactionId = params.get("transaction_id");
// 更新支付订单状态
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
if (order != null) {
paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId);
}
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
} else {
logger.error("微信支付回调失败: {}", params.get("return_msg"));
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付失败]]></return_msg></xml>";
}
} catch (Exception e) {
logger.error("处理微信支付回调失败", e);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>";
}
}
/**
* 创建支付宝支付订单
* @param order 支付订单信息
* @return 支付宝支付表单
*/
@Override
public String createAlipay(PaymentOrder order) {
try {
// 初始化支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
paymentConfig.getAlipay().getGatewayUrl(),
paymentConfig.getAlipay().getAppId(),
paymentConfig.getAlipay().getPrivateKey(),
"JSON",
paymentConfig.getAlipay().getCharset(),
paymentConfig.getAlipay().getPublicKey(),
paymentConfig.getAlipay().getSignType());
// 创建支付请求
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(paymentConfig.getAlipay().getNotifyUrl());
request.setReturnUrl(paymentConfig.getAlipay().getReturnUrl());
// 构建请求参数
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", order.getOrderNo());
bizContent.put("total_amount", order.getAmount().toString());
bizContent.put("subject", order.getProductName());
bizContent.put("body", order.getProductDesc());
bizContent.put("timeout_express", "30m");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(com.alibaba.fastjson.JSON.toJSONString(bizContent));
// 生成支付表单
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
return response.getBody();
} else {
logger.error("支付宝支付下单失败: {}", response.getMsg());
throw new RuntimeException("支付宝支付下单失败: " + response.getMsg());
}
} catch (AlipayApiException e) {
logger.error("创建支付宝支付订单失败", e);
throw new RuntimeException("创建支付宝支付订单失败", e);
}
}
/**
* 处理支付宝支付回调
* @param request HTTP请求
* @return 回调响应
*/
@Override
public String handleAlipayNotify(HttpServletRequest request) {
try {
// 获取回调参数
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, values[0]);
});
// 初始化支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
paymentConfig.getAlipay().getGatewayUrl(),
paymentConfig.getAlipay().getAppId(),
paymentConfig.getAlipay().getPrivateKey(),
"JSON",
paymentConfig.getAlipay().getCharset(),
paymentConfig.getAlipay().getPublicKey(),
paymentConfig.getAlipay().getSignType());
// 验证签名
boolean signVerified = com.alipay.api.internal.util.AlipaySignature.rsaCheckV1(
params, paymentConfig.getAlipay().getPublicKey(),
paymentConfig.getAlipay().getCharset(),
paymentConfig.getAlipay().getSignType());
if (!signVerified) {
logger.error("支付宝支付回调签名验证失败");
return "failure";
}
// 处理支付结果
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
String orderNo = params.get("out_trade_no");
String transactionId = params.get("trade_no");
// 更新支付订单状态
PaymentOrder order = paymentOrderService.queryByOrderNo(orderNo);
if (order != null) {
paymentOrderService.updateStatus(order.getOrderId(), 2, transactionId);
}
return "success";
} else {
logger.error("支付宝支付回调失败: {}", tradeStatus);
return "failure";
}
} catch (Exception e) {
logger.error("处理支付宝支付回调失败", e);
return "failure";
}
}
/**
* 处理支付宝支付同步回调
* @param request HTTP请求
* @return 同步回调响应
*/
@Override
public Map<String, Object> handleAlipayReturn(HttpServletRequest request) {
try {
// 获取回调参数
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, values[0]);
});
// 初始化支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
paymentConfig.getAlipay().getGatewayUrl(),
paymentConfig.getAlipay().getAppId(),
paymentConfig.getAlipay().getPrivateKey(),
"JSON",
paymentConfig.getAlipay().getCharset(),
paymentConfig.getAlipay().getPublicKey(),
paymentConfig.getAlipay().getSignType());
// 验证签名
boolean signVerified = com.alipay.api.internal.util.AlipaySignature.rsaCheckV1(
params, paymentConfig.getAlipay().getPublicKey(),
paymentConfig.getAlipay().getCharset(),
paymentConfig.getAlipay().getSignType());
Map<String, Object> result = new HashMap<>();
if (signVerified) {
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
result.put("success", true);
result.put("orderNo", params.get("out_trade_no"));
result.put("message", "支付成功");
} else {
result.put("success", false);
result.put("message", "支付失败");
}
} else {
result.put("success", false);
result.put("message", "签名验证失败");
}
return result;
} catch (Exception e) {
logger.error("处理支付宝支付同步回调失败", e);
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("message", "处理失败");
return result;
}
}
}

View File

@ -30,6 +30,9 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
@Resource
private AccountService accountService;
@Resource
private com.kexue.skills.service.ContentPurchaseService contentPurchaseService;
/**
* 分页查询
*
@ -189,7 +192,7 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
// 更新订单状态
this.updateStatus(order.getOrderId(), status, channelOrderNo);
// 如果支付成功更新账户余额
// 如果支付成功处理相关业务逻辑
if (status == 2) { // 2.已支付
// 根据业务类型处理不同的业务逻辑
if ("recharge".equals(order.getBusinessType())) {
@ -197,8 +200,17 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
this.accountService.addBalance(order.getUserId(), order.getAmount(), orderNo,
order.getBusinessId(), order.getBusinessType(), "充值成功");
} else if ("purchase_content".equals(order.getBusinessType())) {
// 购买内容业务这里不需要操作余额因为余额已经在购买时扣除
// 只需要更新订单状态即可
// 购买内容业务更新购买记录状态
try {
// 查找对应的购买记录并更新状态
com.kexue.skills.entity.ContentPurchase purchase = contentPurchaseService.queryByUserIdAndContentId(order.getUserId(), order.getBusinessId());
if (purchase != null && purchase.getStatus() == 1) { // 1.待支付
contentPurchaseService.updateStatus(purchase.getPurchaseId(), 2); // 2.已支付
}
} catch (Exception e) {
// 记录错误但不影响支付回调的处理
java.util.logging.Logger.getLogger(getClass().getName()).severe("更新购买记录状态失败: " + e.getMessage());
}
}
}

View File

@ -2,9 +2,9 @@
common:
# Redis配置
redis:
# host: 127.0.0.1
# port: 6379
host: 43.248.97.19
port: 16379
host: 127.0.0.1
port: 6379
# host: 43.248.97.19
# port: 16379
password: 654321
database: 1

View File

@ -104,3 +104,26 @@ jetcache:
web:
upload:
path: /kexue/agent-skills/upload/
# 支付配置
payment:
# 微信支付配置
wechat:
appId: wx7d13d99de5be3bfa
mchId: 1673321732
mchKey: UDuZXDcmy5Eb6o0nTNZhu6ek4DDh4K8B
mchSerialNo: 5EFC47D3AA59BFD1AAE548F96B5E19E1C60F067D
privateKeyPath: apiclient_key.pem
domain: https://api.mch.weixin.qq.com
notifyUrl: http://127.0.0.1:19001/pay/wx/notify
returnUrl: http://127.0.0.1:19001/pay/success
# 支付宝支付配置
alipay:
appId: 2021004138642603
privateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBzqh/sBfx8UsiKIzSaGm3QwMape+PEmHaFYsP0XxRLyRe5d3H1r4JBvE/GBCDarXkZMvJ3PKeUpO61i7LZtNBN7M6XbMvs/eiaipUmWCNF4IK+ilhOqr8GNryjb6tBnO0uzd1c7UmAvxF+MKkxti0qfscP+Tr6pkuF14DofOyITY56x0y36DmEc85rr213llQ/bSX5nVPHWfddtrqB+TNFvrnAN10NxnUurDK+wd7fKq3TgMjYNDINPGxFYbYezeIGvYj6E34mFS3b3wyxTcB5DeL4GV1FAWqJ0Yk+3hHT3OdmK/UnNEr55w+r3liQBCDRBoE4fldbG5CtW2/ER0lAgMBAAECggEAC2kgNMFFCZaddS49Ws2k5WA1qKUHjvsdsO8N32EZ3YUYXGM2gLem0uJSWKqD4RmDTcVyiJcsmLBHnjfvux+Z2HTOA4ZzFvFqBlPwzqkA7MYxP0fIVWyz1R9WN9Yv+cPEbhG7CU8XkHTYuknoylVUfWUn1s7jD736oyuYrxcQdgsHOHpLHngvsELLa1pv2EURohvr4p+zirMjFGuz3BVaGgVpWegn0nJ/8n1y0ZTM04Mvm/zXpGQQxfuUxrK2owQMFViY7BrRQXlPVeUM/IPAx7cvxLkR5hl9UgAl+nH6FCsm5osEvUln6VLhGTmNFBLCN9piX6sZaPEKZKRBCz0GQQKBgQDD4Dq+dGQAoCmnXdkzI6DHGXOePa+sGnj1Y55dRcrPWPKtCPeEnIPxhVCJ2+cqYK49K6youhLYzs3h8y/M4lh0JQLKP4zTSyZnARmdYHW/SyTu9BcHCdiPwZBrmn7bCWotGf3r5QRIJT6ilZEj8cLnF+9+gd0YyLyRE7Rmihq2UQKBgQCpps7qghRv3JIB0Hb4nBCbAeyPMjHj+7BSUsui2Dhdg3MeTk48a7RLl+r139pMzgTm5Pj0VG6qslUeqP2HlQ46o0Z2bPeohfXH4zMJi2amh4MvAFp4t8eNCc6faeqpJPTTQj3hS4drFnHEHBeFfgFCXZKhjYeP7SP2WVLOQvUAlQKBgFIq6fmjEaBBj7ep4sdVFsjuoFWtQthLcppd47z03hMFGSgFLu/uSFs0tYhfOyXH0M/QVmmhRO62Mh+qyE6GVNzD+dultQmd6Mok5/3gzQQmHaQvuMk3FCWZ6V96O+Temi+5S499TsKE/TVu0Kfnbv9KRykmiP0wmAmz3mV1YadBAoGABc5quIX5MxbmfF9pIvscamG3efMq1/WuRDMHOyyRSUoNb5UYgmLhSdEKPp4Jt6U5b7mYd6xIGVl/Jkx8WN6WHRWnfLggBcmH7u5sub/mpH5w0/P8JLONhds3Eieq210jb/ONcJ+II/chr6eSeoQkgOP498SDRj7Eg1LtTZfnEL0CgYBnMeXQWKUae+xES5NsEX7D0lwSCotb7attTHeE6vZOI77TzsURb4jOEAhYVsdJ8lm+J1Sv1Wjnr2yMxiRH2G+I1tUxGcI8OkRT26FxFdMl1RdbTf+gDM8IjMiu+Li+plIzb28VtF8q/Umgd+5LTlSBmM2yoiL8RKtmStjr5iIuhA=="
publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnakP04nUmsoFoveIvOhbLqkA1xQuYtvkrqq2AVvTsbtqpsEOTm9G095e2rBYLp89oDcf6L6BhtJPwdrhnA+qifUyVmACI9sprrsGeRYQgndK7y4c6spQcSnsnakSxlIp22j7pvBXNAZuqud2hQV+TOLKEUh1W3izTgMj/Ejoh3ZsCjgDRtTVgaytzSdHYrhNku+pIrl15/xVGJED99RYXkR8GHawxuK+vWVmxU0tiTCwTsqLz43v6TtCZ+/UfLL/luwp9B4ZvB+0qon82LILYr6oxs10kE2IAvryuDToAc1s/v/36jgt+7DXwqzfUDksHhVLHdJHChyc4ax5HmMsBwIDAQAB"
notifyUrl: http://127.0.0.1:19001/pay/ali-pay/trade/notify
returnUrl: https://shuziren.xueai.art/alipay-success
signType: RSA2
charset: UTF-8
gatewayUrl: https://openapi.alipay.com/gateway.do

View File

@ -1,6 +1,6 @@
spring:
profiles:
active: prod
active: dev
application:
name: agentSkills
version: 1.0.0

View File

@ -100,6 +100,98 @@
</if>
</select>
<!--带分页的查询-->
<select id="getPageListWithPagination" resultMap="CmsContentMap">
select
content_id, title, subtitle, content_type, summary, content, cover_image, author_id, author_name,
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, origin, tags, create_time, update_time, create_by, update_by, delete_flag
from cms_content
<where>
<if test="queryDto.contentId != null">
and content_id = #{queryDto.contentId}
</if>
<if test="queryDto.title != null and queryDto.title != ''">
and (title like concat('%', #{queryDto.title}, '%') or summary like concat('%', #{queryDto.title}, '%'))
</if>
<if test="queryDto.contentType != null">
and content_type = #{queryDto.contentType}
</if>
<if test="queryDto.authorId != null">
and author_id = #{queryDto.authorId}
</if>
<if test="queryDto.authorName != null and queryDto.authorName != ''">
and author_name like concat('%', #{queryDto.authorName}, '%')
</if>
<if test="queryDto.auditStatus != null">
and audit_status = #{queryDto.auditStatus}
</if>
<if test="queryDto.publishStatus != null">
and publish_status = #{queryDto.publishStatus}
</if>
<if test="queryDto.createBy != null and queryDto.createBy != ''">
and create_by like concat('%', #{queryDto.createBy}, '%')
</if>
<if test="queryDto.deleteFlag != null">
and delete_flag = #{queryDto.deleteFlag}
</if>
<if test="queryDto.isOfficial != null">
and is_official = #{queryDto.isOfficial}
</if>
<if test="queryDto.tagId != null">
and find_in_set(#{queryDto.tagId}, tags)
</if>
</where>
<if test="queryDto.sortBy != null and queryDto.sortBy != ''">
order by ${queryDto.sortBy} ${queryDto.sortDesc ? 'desc' : 'asc'}
</if>
<if test="queryDto.sortBy == null or queryDto.sortBy == ''">
order by sort asc, create_time desc
</if>
limit #{offset}, #{limit}
</select>
<!--查询总记录数-->
<select id="getPageListCount" resultType="int">
select count(*)
from cms_content
<where>
<if test="contentId != null">
and content_id = #{contentId}
</if>
<if test="title != null and title != ''">
and (title like concat('%', #{title}, '%') or summary like concat('%', #{title}, '%'))
</if>
<if test="contentType != null">
and content_type = #{contentType}
</if>
<if test="authorId != null">
and author_id = #{authorId}
</if>
<if test="authorName != null and authorName != ''">
and author_name like concat('%', #{authorName}, '%')
</if>
<if test="auditStatus != null">
and audit_status = #{auditStatus}
</if>
<if test="publishStatus != null">
and publish_status = #{publishStatus}
</if>
<if test="createBy != null and createBy != ''">
and create_by like concat('%', #{createBy}, '%')
</if>
<if test="deleteFlag != null">
and delete_flag = #{deleteFlag}
</if>
<if test="isOfficial != null">
and is_official = #{isOfficial}
</if>
<if test="tagId != null">
and find_in_set(#{tagId}, tags)
</if>
</where>
</select>
<!--查询列表-->
<select id="getList" resultMap="CmsContentMap">
select