feat(payment): 添加支付功能支持微信和支付宝
- 集成微信支付和支付宝支付SDK - 添加支付配置文件和配置类 - 实现支付控制器提供创建订单和处理回调接口 - 更新内容购买服务支持第三方支付方式 - 添加支付订单服务处理支付状态更新 - 修改CMS内容服务添加收藏和查看记录功能 - 更新应用配置文件适配开发环境和Redis连接 - 升级分页插件版本并添加统一SQL解析器依赖
This commit is contained in:
parent
d33d567b81
commit
e16fbdf2d6
27
pom.xml
27
pom.xml
|
|
@ -164,7 +164,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.pagehelper</groupId>
|
<groupId>com.github.pagehelper</groupId>
|
||||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.4.6</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
|
|
@ -198,9 +198,20 @@
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-logging</artifactId>
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
<artifactId>jsqlparser</artifactId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 统一的jsqlparser版本 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
<artifactId>jsqlparser</artifactId>
|
||||||
|
<version>4.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Sa-Token 核心依赖 -->
|
<!-- Sa-Token 核心依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.dev33</groupId>
|
<groupId>cn.dev33</groupId>
|
||||||
|
|
@ -257,6 +268,20 @@
|
||||||
<version>1.2.83</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,31 @@ public class HttpUtil {
|
||||||
|
|
||||||
return response.body();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -170,4 +170,30 @@ public class CmsContentController {
|
||||||
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
|
public CommonResult<Boolean> deleteById(@PathVariable("contentId") Long contentId) {
|
||||||
return CommonResult.success(cmsContentService.deleteById(contentId) > 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,24 @@ public interface CmsContentMapper {
|
||||||
*/
|
*/
|
||||||
List<CmsContent> getPageList(CmsContentDto queryDto);
|
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询列表
|
* 查询列表
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,6 @@ public interface CmsContentService extends BaseService {
|
||||||
*/
|
*/
|
||||||
CmsContent queryById(Long contentId);
|
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 contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @param userName 用户名
|
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int addFavorite(Long contentId, Long userId, String userName);
|
int addFavorite(Long contentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消收藏
|
* 取消收藏
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int removeFavorite(Long contentId, Long userId);
|
int removeFavorite(Long contentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查用户是否已收藏该内容
|
* 检查用户是否已收藏该内容
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @return 是否已收藏
|
* @return 是否已收藏
|
||||||
*/
|
*/
|
||||||
boolean isFavorited(Long contentId, Long userId);
|
boolean isFavorited(Long contentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加查看记录
|
||||||
|
*
|
||||||
|
* @param contentId 内容ID
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int addView(Long contentId);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
|
||||||
import com.kexue.skills.entity.CmsContent;
|
import com.kexue.skills.entity.CmsContent;
|
||||||
import com.kexue.skills.entity.CmsContentView;
|
import com.kexue.skills.entity.CmsContentView;
|
||||||
import com.kexue.skills.entity.CmsContentLike;
|
import com.kexue.skills.entity.CmsContentLike;
|
||||||
|
import com.kexue.skills.entity.base.BaseQueryDto;
|
||||||
import com.kexue.skills.entity.dto.CmsContentDto;
|
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||||
import com.kexue.skills.mapper.CmsContentMapper;
|
import com.kexue.skills.mapper.CmsContentMapper;
|
||||||
import com.kexue.skills.mapper.CmsContentViewMapper;
|
import com.kexue.skills.mapper.CmsContentViewMapper;
|
||||||
|
|
@ -17,6 +18,8 @@ import javax.annotation.Resource;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (CmsContent)表服务实现类
|
* (CmsContent)表服务实现类
|
||||||
*
|
*
|
||||||
|
|
@ -43,9 +46,26 @@ public class CmsContentServiceImpl implements CmsContentService {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PageInfo<CmsContent> getPageList(CmsContentDto queryDto) {
|
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());
|
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||||
List<CmsContent> list = this.cmsContentMapper.getPageList(queryDto);
|
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
|
@Override
|
||||||
public CmsContent queryById(Long contentId) {
|
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);
|
CmsContent content = this.cmsContentMapper.queryById(contentId);
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户ID不为空,添加用户查看记录
|
try {
|
||||||
if (userId != null) {
|
// 尝试获取当前登录用户ID
|
||||||
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
// 检查用户是否已经查看过该内容(5分钟内)
|
// 检查用户是否已经查看过该内容(5分钟内)
|
||||||
CmsContentView existingView = cmsContentViewMapper.queryByUserIdAndContentId(userId, contentId);
|
CmsContentView existingView = cmsContentViewMapper.queryByUserIdAndContentId(userId, contentId);
|
||||||
if (existingView == null) {
|
if (existingView == null) {
|
||||||
// 新增查看记录
|
// 新增查看记录
|
||||||
CmsContentView viewRecord = new CmsContentView();
|
CmsContentView viewRecord = new CmsContentView();
|
||||||
viewRecord.setUserId(userId);
|
viewRecord.setUserId(userId);
|
||||||
|
viewRecord.setUserName(StpUtil.getLoginIdAsString());
|
||||||
viewRecord.setContentId(contentId);
|
viewRecord.setContentId(contentId);
|
||||||
viewRecord.setContentTitle(content.getTitle());
|
viewRecord.setContentTitle(content.getTitle());
|
||||||
viewRecord.setViewTime(new Date());
|
viewRecord.setViewTime(new Date());
|
||||||
viewRecord.setDeleteFlag(0);
|
viewRecord.setDeleteFlag(0);
|
||||||
|
viewRecord.setCreateBy(StpUtil.getLoginIdAsString());
|
||||||
|
viewRecord.setUpdateBy(StpUtil.getLoginIdAsString());
|
||||||
cmsContentViewMapper.insert(viewRecord);
|
cmsContentViewMapper.insert(viewRecord);
|
||||||
} else {
|
} else {
|
||||||
// 检查是否超过5分钟
|
// 检查是否超过5分钟
|
||||||
|
|
@ -103,9 +115,12 @@ public class CmsContentServiceImpl implements CmsContentService {
|
||||||
if (existingView.getViewTime().before(fiveMinutesAgo)) {
|
if (existingView.getViewTime().before(fiveMinutesAgo)) {
|
||||||
// 更新查看时间
|
// 更新查看时间
|
||||||
existingView.setViewTime(new Date());
|
existingView.setViewTime(new Date());
|
||||||
|
existingView.setUpdateBy(StpUtil.getLoginIdAsString());
|
||||||
cmsContentViewMapper.update(existingView);
|
cmsContentViewMapper.update(existingView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 未登录用户,不记录查看历史
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
|
|
@ -253,12 +268,15 @@ public class CmsContentServiceImpl implements CmsContentService {
|
||||||
* 添加收藏
|
* 添加收藏
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @param userName 用户名
|
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
@Override
|
@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);
|
CmsContent content = this.cmsContentMapper.queryById(contentId);
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
|
|
@ -291,11 +309,13 @@ public class CmsContentServiceImpl implements CmsContentService {
|
||||||
* 取消收藏
|
* 取消收藏
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
@Override
|
@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);
|
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
|
||||||
if (existingLike == null) {
|
if (existingLike == null) {
|
||||||
|
|
@ -319,15 +339,69 @@ public class CmsContentServiceImpl implements CmsContentService {
|
||||||
* 检查用户是否已收藏该内容
|
* 检查用户是否已收藏该内容
|
||||||
*
|
*
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param userId 用户ID
|
|
||||||
* @return 是否已收藏
|
* @return 是否已收藏
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isFavorited(Long contentId, Long userId) {
|
public boolean isFavorited(Long contentId) {
|
||||||
if (userId == null) {
|
try {
|
||||||
return false;
|
// 获取当前登录用户ID
|
||||||
}
|
Long userId = Long.parseLong(StpUtil.getLoginId().toString());
|
||||||
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
|
CmsContentLike existingLike = cmsContentLikeMapper.queryByUserIdAndContentId(userId, contentId);
|
||||||
return existingLike != null;
|
return existingLike != null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 未登录用户,返回false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加查看记录
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param contentId 内容ID
|
* @param contentId 内容ID
|
||||||
* @param payType 支付方式:1.余额支付 2.积分支付
|
* @param payType 支付方式:1.余额支付 2.积分支付 3.微信支付 4.支付宝支付
|
||||||
* @return 购买结果
|
* @return 购买结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -203,6 +203,11 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
|
||||||
|
|
||||||
// 扣除用户余额
|
// 扣除用户余额
|
||||||
this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
|
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) {
|
} else if (payType == 2) {
|
||||||
// 积分支付
|
// 积分支付
|
||||||
Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付");
|
Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付");
|
||||||
|
|
@ -213,14 +218,24 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
|
||||||
|
|
||||||
// 扣除用户积分
|
// 扣除用户积分
|
||||||
this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
|
this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
|
||||||
} else {
|
|
||||||
Assert.isTrue(false, "不支持的支付方式");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 更新购买记录状态
|
// 6. 更新购买记录状态
|
||||||
purchase.setStatus(2); // 已支付
|
purchase.setStatus(2); // 已支付
|
||||||
purchase.setPurchaseTime(new Date());
|
purchase.setPurchaseTime(new Date());
|
||||||
this.insert(purchase);
|
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, "不支持的支付方式");
|
||||||
|
}
|
||||||
|
|
||||||
return purchase;
|
return purchase;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,9 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
|
||||||
@Resource
|
@Resource
|
||||||
private AccountService accountService;
|
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);
|
this.updateStatus(order.getOrderId(), status, channelOrderNo);
|
||||||
|
|
||||||
// 如果支付成功,更新账户余额
|
// 如果支付成功,处理相关业务逻辑
|
||||||
if (status == 2) { // 2.已支付
|
if (status == 2) { // 2.已支付
|
||||||
// 根据业务类型处理不同的业务逻辑
|
// 根据业务类型处理不同的业务逻辑
|
||||||
if ("recharge".equals(order.getBusinessType())) {
|
if ("recharge".equals(order.getBusinessType())) {
|
||||||
|
|
@ -197,8 +200,17 @@ public class PaymentOrderServiceImpl implements PaymentOrderService {
|
||||||
this.accountService.addBalance(order.getUserId(), order.getAmount(), orderNo,
|
this.accountService.addBalance(order.getUserId(), order.getAmount(), orderNo,
|
||||||
order.getBusinessId(), order.getBusinessType(), "充值成功");
|
order.getBusinessId(), order.getBusinessType(), "充值成功");
|
||||||
} else if ("purchase_content".equals(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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
common:
|
common:
|
||||||
# Redis配置
|
# Redis配置
|
||||||
redis:
|
redis:
|
||||||
# host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
# port: 6379
|
port: 6379
|
||||||
host: 43.248.97.19
|
# host: 43.248.97.19
|
||||||
port: 16379
|
# port: 16379
|
||||||
password: 654321
|
password: 654321
|
||||||
database: 1
|
database: 1
|
||||||
|
|
|
||||||
|
|
@ -104,3 +104,26 @@ jetcache:
|
||||||
web:
|
web:
|
||||||
upload:
|
upload:
|
||||||
path: /kexue/agent-skills/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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: prod
|
active: dev
|
||||||
application:
|
application:
|
||||||
name: agentSkills
|
name: agentSkills
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,98 @@
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</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 id="getList" resultMap="CmsContentMap">
|
||||||
select
|
select
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue