From e16fbdf2d6157a458c05c46eccfabed121d6cefd Mon Sep 17 00:00:00 2001 From: wangzhiwei Date: Thu, 26 Feb 2026 17:30:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E6=B7=BB=E5=8A=A0=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=92=8C=E6=94=AF=E4=BB=98=E5=AE=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 集成微信支付和支付宝支付SDK - 添加支付配置文件和配置类 - 实现支付控制器提供创建订单和处理回调接口 - 更新内容购买服务支持第三方支付方式 - 添加支付订单服务处理支付状态更新 - 修改CMS内容服务添加收藏和查看记录功能 - 更新应用配置文件适配开发环境和Redis连接 - 升级分页插件版本并添加统一SQL解析器依赖 --- pom.xml | 27 +- .../kexue/skills/common/util/HttpUtil.java | 27 ++ .../kexue/skills/config/PaymentConfig.java | 66 +++ .../controller/CmsContentController.java | 26 ++ .../skills/controller/PayController.java | 110 +++++ .../kexue/skills/mapper/CmsContentMapper.java | 18 + .../skills/service/CmsContentService.java | 27 +- .../com/kexue/skills/service/PayService.java | 45 ++ .../service/impl/CmsContentServiceImpl.java | 124 ++++-- .../impl/ContentPurchaseServiceImpl.java | 27 +- .../skills/service/impl/PayServiceImpl.java | 392 ++++++++++++++++++ .../service/impl/PaymentOrderServiceImpl.java | 18 +- src/main/resources/application-common.yml | 8 +- src/main/resources/application-dev.yml | 23 + src/main/resources/application.yml | 2 +- .../resources/mapper/CmsContentMapper.xml | 92 ++++ 16 files changed, 976 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/kexue/skills/config/PaymentConfig.java create mode 100644 src/main/java/com/kexue/skills/controller/PayController.java create mode 100644 src/main/java/com/kexue/skills/service/PayService.java create mode 100644 src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java diff --git a/pom.xml b/pom.xml index 70ffa0d..f2796c1 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,7 @@ com.github.pagehelper pagehelper-spring-boot-starter - 1.4.2 + 1.4.6 org.mybatis @@ -198,9 +198,20 @@ org.springframework.boot spring-boot-starter-logging + + com.github.jsqlparser + jsqlparser + + + + com.github.jsqlparser + jsqlparser + 4.4 + + cn.dev33 @@ -257,6 +268,20 @@ 1.2.83 + + + com.github.binarywang + weixin-java-pay + 4.4.0 + + + + + com.alipay.sdk + alipay-sdk-java + 4.38.0.ALL + + diff --git a/src/main/java/com/kexue/skills/common/util/HttpUtil.java b/src/main/java/com/kexue/skills/common/util/HttpUtil.java index dd1f246..2ee86b5 100644 --- a/src/main/java/com/kexue/skills/common/util/HttpUtil.java +++ b/src/main/java/com/kexue/skills/common/util/HttpUtil.java @@ -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 response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + return response.body(); + } } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/config/PaymentConfig.java b/src/main/java/com/kexue/skills/config/PaymentConfig.java new file mode 100644 index 0000000..a48ae31 --- /dev/null +++ b/src/main/java/com/kexue/skills/config/PaymentConfig.java @@ -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; + + } +} diff --git a/src/main/java/com/kexue/skills/controller/CmsContentController.java b/src/main/java/com/kexue/skills/controller/CmsContentController.java index 061a81c..3332795 100644 --- a/src/main/java/com/kexue/skills/controller/CmsContentController.java +++ b/src/main/java/com/kexue/skills/controller/CmsContentController.java @@ -170,4 +170,30 @@ public class CmsContentController { public CommonResult deleteById(@PathVariable("contentId") Long contentId) { return CommonResult.success(cmsContentService.deleteById(contentId) > 0); } + + /** + * 添加收藏 + * + * @param contentId 内容ID + * @return 操作结果 + */ + @PostMapping("/addFavorite") + @Operation(summary = "添加收藏", description = "添加内容收藏") + @RequireAuth + public CommonResult addFavorite(@RequestParam("contentId") Long contentId) { + return CommonResult.success(cmsContentService.addFavorite(contentId) > 0); + } + + /** + * 添加查看记录 + * + * @param contentId 内容ID + * @return 操作结果 + */ + @PostMapping("/addView") + @Operation(summary = "添加查看记录", description = "添加内容查看记录") + @RequireAuth + public CommonResult addView(@RequestParam("contentId") Long contentId) { + return CommonResult.success(cmsContentService.addView(contentId) > 0); + } } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/controller/PayController.java b/src/main/java/com/kexue/skills/controller/PayController.java new file mode 100644 index 0000000..c64ef28 --- /dev/null +++ b/src/main/java/com/kexue/skills/controller/PayController.java @@ -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> createWechatPay(@RequestBody PaymentOrder order) { + try { + // 创建支付订单 + PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order); + // 生成微信支付参数 + Map 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 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> handleAlipayReturn(HttpServletRequest request) { + Map result = payService.handleAlipayReturn(request); + if (result.get("success").equals(true)) { + return CommonResult.success(result); + } else { + return CommonResult.failed(result.get("message").toString()); + } + } +} diff --git a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java index 9afb8f2..b5b73d6 100644 --- a/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java +++ b/src/main/java/com/kexue/skills/mapper/CmsContentMapper.java @@ -23,6 +23,24 @@ public interface CmsContentMapper { */ List getPageList(CmsContentDto queryDto); + /** + * 带分页的查询 + * + * @param queryDto 筛选条件 + * @param offset 偏移量 + * @param limit 限制数量 + * @return 查询结果 + */ + List getPageListWithPagination(@Param("queryDto") CmsContentDto queryDto, @Param("offset") int offset, @Param("limit") int limit); + + /** + * 查询总记录数 + * + * @param queryDto 筛选条件 + * @return 总记录数 + */ + int getPageListCount(CmsContentDto queryDto); + /** * 查询列表 * diff --git a/src/main/java/com/kexue/skills/service/CmsContentService.java b/src/main/java/com/kexue/skills/service/CmsContentService.java index 4f07cee..20f81d0 100644 --- a/src/main/java/com/kexue/skills/service/CmsContentService.java +++ b/src/main/java/com/kexue/skills/service/CmsContentService.java @@ -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); } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/PayService.java b/src/main/java/com/kexue/skills/service/PayService.java new file mode 100644 index 0000000..93db4f1 --- /dev/null +++ b/src/main/java/com/kexue/skills/service/PayService.java @@ -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 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 handleAlipayReturn(HttpServletRequest request); +} diff --git a/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java index 071b4b6..af0f85d 100644 --- a/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/CmsContentServiceImpl.java @@ -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 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 list = this.cmsContentMapper.getPageList(queryDto); - return new PageInfo<>(list); + PageInfo 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; + } } } \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java index f9fc381..64ccf77 100644 --- a/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/ContentPurchaseServiceImpl.java @@ -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; } diff --git a/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java new file mode 100644 index 0000000..810927a --- /dev/null +++ b/src/main/java/com/kexue/skills/service/impl/PayServiceImpl.java @@ -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 params, String key) { + // 排序参数 + List 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 params) { + StringBuilder sb = new StringBuilder(); + sb.append(""); + for (Map.Entry entry : params.entrySet()) { + sb.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append(""); + } + sb.append(""); + return sb.toString(); + } + + /** + * 将XML转换为Map + * @param xml XML字符串 + * @return Map + */ + private Map xmlToMap(String xml) { + Map map = new HashMap<>(); + try { + // 简单的XML解析 + String content = xml.replaceAll("", "").replaceAll("", ""); + String[] elements = content.split(""); + 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 createWechatPay(PaymentOrder order) { + try { + // 构建微信支付参数 + Map 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 result = xmlToMap(xmlResult); + + // 处理响应 + if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) { + Map 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 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 ""; + } + + // 处理支付结果 + 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 ""; + } else { + logger.error("微信支付回调失败: {}", params.get("return_msg")); + return ""; + } + } catch (Exception e) { + logger.error("处理微信支付回调失败", e); + return ""; + } + } + + /** + * 创建支付宝支付订单 + * @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 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 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 handleAlipayReturn(HttpServletRequest request) { + try { + // 获取回调参数 + Map 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 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 result = new HashMap<>(); + result.put("success", false); + result.put("message", "处理失败"); + return result; + } + } + + +} diff --git a/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java index bae81c6..2a95bef 100644 --- a/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java +++ b/src/main/java/com/kexue/skills/service/impl/PaymentOrderServiceImpl.java @@ -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()); + } } } diff --git a/src/main/resources/application-common.yml b/src/main/resources/application-common.yml index 40c38ee..72c1596 100644 --- a/src/main/resources/application-common.yml +++ b/src/main/resources/application-common.yml @@ -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 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 63523b5..ed14db7 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -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 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c2b93a0..111f815 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: profiles: - active: prod + active: dev application: name: agentSkills version: 1.0.0 diff --git a/src/main/resources/mapper/CmsContentMapper.xml b/src/main/resources/mapper/CmsContentMapper.xml index 24d6f3d..23549cb 100644 --- a/src/main/resources/mapper/CmsContentMapper.xml +++ b/src/main/resources/mapper/CmsContentMapper.xml @@ -100,6 +100,98 @@ + + + + + +