秒杀剩余部分+支付
parent
399e9bb1c6
commit
d6f55e9bf1
|
@ -3,14 +3,17 @@ package com.group.common.domin.pojo;
|
|||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 秒杀订单表
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("t_kill_order")
|
||||
public class KillOrderEntity {
|
||||
/**
|
||||
|
@ -38,5 +41,9 @@ public class KillOrderEntity {
|
|||
* 秒杀订单创建时间
|
||||
*/
|
||||
private Date orderCreate ;
|
||||
/**
|
||||
* 秒杀商品价格
|
||||
*/
|
||||
private BigDecimal killPrice;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.group.common.domin.pojo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -17,38 +18,44 @@ import java.util.Date;
|
|||
@Builder
|
||||
public class PayDetail {
|
||||
/**
|
||||
*
|
||||
* 订单详情主键
|
||||
*/
|
||||
@TableId
|
||||
private Integer payId ;
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer payId;
|
||||
/**
|
||||
* 订单支付金额
|
||||
*/
|
||||
@TableField("pay_title")
|
||||
private BigDecimal payTitle ;
|
||||
@TableField("pay_price")
|
||||
private BigDecimal payPrice;
|
||||
/**
|
||||
* 订单id
|
||||
*/
|
||||
@TableField("pay_order")
|
||||
private Integer payOrder ;
|
||||
private Integer payOrder;
|
||||
/**
|
||||
* 支付宝编号
|
||||
*/
|
||||
@TableField("pay_pay")
|
||||
private String payPay ;
|
||||
private String payPay;
|
||||
/**
|
||||
* 明细状态 1-待支付 2-已支付 3-支付失败
|
||||
* 明细状态 1-待支付 2-支付成功 3-支付失败
|
||||
*/
|
||||
@TableField("pay_state")
|
||||
private Integer payState ;
|
||||
private Integer payState;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField("pay_create")
|
||||
private Date payCreate ;
|
||||
private Date payCreate;
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
@TableField("pay_payed")
|
||||
private Date payPayed ;
|
||||
private String payPayed;
|
||||
/**
|
||||
* 订单支付流水编号
|
||||
*/
|
||||
@TableField("pay_detail")
|
||||
private String payDetail;
|
||||
|
||||
}
|
||||
|
|
|
@ -23,5 +23,10 @@
|
|||
<groupId>com.song</groupId>
|
||||
<artifactId>group-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,49 @@
|
|||
package com.group.goods.config;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class RabbitAdminConfig {
|
||||
|
||||
@Value("${spring.rabbitmq.host}")
|
||||
private String host;
|
||||
@Value("${spring.rabbitmq.username}")
|
||||
private String username;
|
||||
@Value("${spring.rabbitmq.password}")
|
||||
private String password;
|
||||
@Value("${spring.rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
|
||||
/**
|
||||
* 构建RabbitMQ的连接工厂
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(host);
|
||||
connectionFactory.setUsername(username);
|
||||
connectionFactory.setPassword(password);
|
||||
connectionFactory.setVirtualHost(virtualhost);
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化RabbitAdmin
|
||||
* @param connectionFactory
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.group.goods.config;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
public class RabbitMQConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback{
|
||||
|
||||
public static final String QUEUE = "order_queue";
|
||||
public static final String EXCHANGE = "order_exchange";
|
||||
public static final String ROUTINGKEY = "order_routing_key";
|
||||
|
||||
|
||||
|
||||
public static final Logger logger = LoggerFactory.getLogger(RabbitMQConfig.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
|
||||
//创建消息转换器
|
||||
@Bean
|
||||
public MessageConverter messageConverter(){
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public Queue queue(){
|
||||
return new Queue(QUEUE,true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DirectExchange directExchange(){
|
||||
|
||||
return new DirectExchange(EXCHANGE);
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding(){
|
||||
return BindingBuilder.bind(queue()).to(directExchange()).with(ROUTINGKEY);
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void rabbitTemplate(){
|
||||
|
||||
rabbitTemplate.setConfirmCallback(this);
|
||||
rabbitTemplate.setReturnsCallback(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void confirm(CorrelationData correlationData, boolean ack, String s) {
|
||||
|
||||
if (ack){
|
||||
logger.info("{}消息到达交换机",correlationData.getId());
|
||||
}else {
|
||||
logger.info("{}消息丢失", correlationData.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnedMessage(ReturnedMessage returnedMessage) {
|
||||
|
||||
logger.error("{}消息未到达队列",returnedMessage.getMessage().getMessageProperties().getMessageId());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.group.goods.config;
|
||||
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.codec.JsonJacksonCodec;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* redisson配置
|
||||
*/
|
||||
@Component
|
||||
public class RedissonConfig {
|
||||
|
||||
@Value("${spring.redis.host}")
|
||||
private String host;
|
||||
|
||||
@Value("${spring.redis.port}")
|
||||
private String post;
|
||||
|
||||
@Bean
|
||||
public RedissonClient redissonClient(){
|
||||
Config config = new Config();
|
||||
config.useSingleServer().setAddress(host+post);
|
||||
config.setCodec(new JsonJacksonCodec());
|
||||
return Redisson.create(config);
|
||||
}
|
||||
}
|
|
@ -19,18 +19,11 @@ public class SecKillController {
|
|||
private SecKillService secKillService;
|
||||
|
||||
/**
|
||||
* 秒杀商品 - 采用最简单方式进行实现
|
||||
* 秒杀商品 - 采用最简单方式进行实现 -使用了redisson框架
|
||||
*/
|
||||
@GetMapping("/buyGoodsByOne/{id}")
|
||||
public R buyGoodsByOne(@PathVariable("id")Integer id){
|
||||
return secKillService.buyGoodsByOne(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒杀方式 -- 采用降低锁粒度的方式实现
|
||||
*/
|
||||
|
||||
/**
|
||||
* 秒杀商品 -- 采用redisson信号量的方式进行解决这个问题
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package com.group.goods.feign;
|
||||
|
||||
import com.group.common.domin.pojo.KillOrderEntity;
|
||||
import com.group.common.result.R;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
|
@ -11,5 +15,11 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
public interface KillOrderService {
|
||||
|
||||
@GetMapping("/killOrder/exitResult")
|
||||
public boolean exitResult(@RequestParam Integer goodsId, Integer userId);
|
||||
public boolean exitResult(@RequestParam Integer goodsId,@RequestParam Integer userId);
|
||||
|
||||
@PostMapping("/killOrder/addOrder")
|
||||
public R addOrder(@RequestBody KillOrderEntity killOrderEntity);
|
||||
|
||||
@PostMapping ("/deleteOrder")
|
||||
public void deleteOrder(@RequestBody KillOrderEntity killOrderEntity);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package com.group.goods.mq;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.group.common.domin.pojo.KillOrderEntity;
|
||||
import com.group.common.domin.pojo.SeckillEntity;
|
||||
import com.group.common.redis.RedisCache;
|
||||
import com.group.goods.config.RabbitMQConfig;
|
||||
import com.group.goods.feign.KillOrderService;
|
||||
import com.group.goods.pojo.ReqKill;
|
||||
import com.group.goods.service.SecKillService;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 消费者
|
||||
*/
|
||||
@Log4j2
|
||||
@Component
|
||||
public class Consumer {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
private SecKillService secKillService;
|
||||
|
||||
@Autowired
|
||||
private KillOrderService killOrderService;
|
||||
|
||||
|
||||
@RabbitListener(queues = RabbitMQConfig.QUEUE)
|
||||
public void addOrder(Message message, Channel channel) {
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
String messageId = properties.getMessageId();
|
||||
//判断消息是否重复消费
|
||||
if (redisCache.hasKey(messageId)) {
|
||||
//当这个messageId存在时,证明已经消费过
|
||||
return;
|
||||
}
|
||||
//拿到传送来的消息
|
||||
String o = (String) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||
ReqKill reqKill = JSON.parseObject(o, ReqKill.class);
|
||||
//执行业务
|
||||
//通过商品id获取对应的实体类
|
||||
SeckillEntity entity = secKillService.getById(reqKill.getGoodsId());
|
||||
//生成秒杀订单编号
|
||||
String killId = IdUtil.getSnowflakeNextId()+"";
|
||||
//生成订单实体类
|
||||
//生成订单 商品id 用户id 当前时间 支付金额 商品编号
|
||||
KillOrderEntity order = KillOrderEntity
|
||||
.builder()
|
||||
.goodsId(reqKill.getGoodsId())
|
||||
.userId(reqKill.getUserId())
|
||||
.killPrice(entity.getSeckillPrice())
|
||||
.killId(killId)
|
||||
.orderCreate(new Date())
|
||||
.build();
|
||||
killOrderService.addOrder(order);
|
||||
//将messageId存入redis缓存中
|
||||
redisCache.setCacheObject(messageId, messageId, 30L, TimeUnit.MINUTES);
|
||||
//手动确认消息
|
||||
try {
|
||||
channel.basicAck(properties.getDeliveryTag(),false);
|
||||
} catch (IOException e) {
|
||||
//当消息消费确认失败
|
||||
//删除添加的订单
|
||||
killOrderService.deleteOrder(order);
|
||||
//将消息重新放回队列中
|
||||
try {
|
||||
channel.basicReject(properties.getDeliveryTag(),true);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.group.goods.mq;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.group.common.redis.RedisCache;
|
||||
import com.group.goods.config.RabbitMQConfig;
|
||||
import com.group.goods.pojo.ReqKill;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 生产者
|
||||
*/
|
||||
@Component
|
||||
public class Product {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* mq异步生成订单
|
||||
*/
|
||||
public void addOrder(ReqKill reqKill) {
|
||||
long messageId = IdUtil.getSnowflakeNextId();
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setMessageId( messageId+"");
|
||||
//将reqKill放到队列中,等待消费
|
||||
Message message = rabbitTemplate
|
||||
.getMessageConverter()
|
||||
.toMessage(JSON.toJSONString(reqKill)
|
||||
, messageProperties);
|
||||
rabbitTemplate.sendAndReceive(
|
||||
RabbitMQConfig.EXCHANGE,
|
||||
RabbitMQConfig.ROUTINGKEY,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.group.goods.pojo;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* mq请求订单生成类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ReqKill {
|
||||
private Integer userId;
|
||||
private Integer goodsId;
|
||||
}
|
|
@ -9,4 +9,5 @@ import com.group.common.result.R;
|
|||
*/
|
||||
public interface SecKillService extends IService<SeckillEntity> {
|
||||
R buyGoodsByOne(Integer id);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.group.goods.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.group.common.domin.pojo.KillOrderEntity;
|
||||
import com.group.common.domin.pojo.SessionEntity;
|
||||
import com.group.common.redis.RedisCache;
|
||||
import com.group.common.result.R;
|
||||
|
@ -9,10 +8,12 @@ import com.group.common.util.UserUtil;
|
|||
import com.group.goods.feign.KillOrderService;
|
||||
import com.group.goods.mapper.SecKillMapper;
|
||||
import com.group.common.domin.pojo.SeckillEntity;
|
||||
import com.group.goods.mq.Product;
|
||||
import com.group.goods.pojo.ReqKill;
|
||||
import com.group.goods.service.SecKillService;
|
||||
import com.group.goods.service.SessionService;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -43,7 +44,10 @@ public class SecKillServiceImpl extends ServiceImpl<SecKillMapper, SeckillEntity
|
|||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private Redisson redisson;
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Autowired
|
||||
private Product productor;
|
||||
|
||||
@Override
|
||||
public R buyGoodsByOne(Integer id) {
|
||||
|
@ -62,26 +66,33 @@ public class SecKillServiceImpl extends ServiceImpl<SecKillMapper, SeckillEntity
|
|||
if (session.getSessionEnd().getTime()<time){
|
||||
throw new RuntimeException("该场次已经结束");
|
||||
}
|
||||
RLock lock = redisson.getLock("productId:" + id);
|
||||
RLock lock = redissonClient.getLock("productId:" + id);
|
||||
try {
|
||||
lock.tryLock(1,3L,TimeUnit.MINUTES);
|
||||
//校验用户是否购买过
|
||||
//获取用户id
|
||||
Integer userId = UserUtil.getUserId(request);
|
||||
//通过用户id和秒杀商品id,查询该用户是否第一次购买该商品
|
||||
if (killOrderService.exitResult(id,userId)){
|
||||
throw new RuntimeException("用户已购买该商品");
|
||||
}
|
||||
//根据id查询秒杀商品信息,判断库存是否足够
|
||||
//库存信息存储在redis中,通过键"product_id:"+goods_id 来存储,通过键来获取商品库存
|
||||
|
||||
//校验用户是否购买过
|
||||
//获取用户id
|
||||
Integer userId = UserUtil.getUserId(request);
|
||||
//通过用户id和秒杀商品id,查询该用户是否第一次购买该商品
|
||||
if (killOrderService.exitResult(id,userId)){
|
||||
throw new RuntimeException("用户已购买该商品");
|
||||
Integer stock = redisCache.getCacheObject("product_id:" + id);
|
||||
if (stock<=0){
|
||||
throw new RuntimeException("库存不足,请不要再次点击");
|
||||
}
|
||||
//减少库存,把数据同步redis中
|
||||
redisCache.decreaseKey("product_id:"+id);
|
||||
//异步生成订单
|
||||
productor.addOrder(ReqKill.builder().goodsId(id).userId(userId).build());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
//根据id查询秒杀商品信息,判断库存是否足够
|
||||
//库存信息存储在redis中,通过键"product_id:"+goods_id 来存储,通过键来获取商品库存
|
||||
|
||||
Integer stock = redisCache.getCacheObject("product_id:" + id);
|
||||
if (stock<=0){
|
||||
throw new RuntimeException("库存不足,请不要再次点击");
|
||||
}
|
||||
//减少库存,把数据同步到数据库和redis中
|
||||
redisCache.decreaseKey("product_id:"+id);
|
||||
|
||||
|
||||
return null;
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,5 +23,18 @@
|
|||
<groupId>com.song</groupId>
|
||||
<artifactId>group-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-sdk-java</artifactId>
|
||||
<version>4.35.79.ALL</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-easysdk</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,40 @@
|
|||
package com.group.order.config;
|
||||
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "alipay")
|
||||
public class AliPayConfig {
|
||||
|
||||
// 支付宝的AppId
|
||||
private String appId;
|
||||
// 应用私钥
|
||||
private String appPrivateKey;
|
||||
// 支付宝公钥
|
||||
private String alipayPublicKey;
|
||||
// 支付宝通知本地的接口完整地址
|
||||
private String notifyUrl;
|
||||
//支付宝跳转的地址
|
||||
private String gatewayUrl ;
|
||||
private String format;
|
||||
private String charset;
|
||||
//签名方式
|
||||
private String signType;
|
||||
//回调本地地址
|
||||
private String returnUrl;
|
||||
|
||||
private String productCode;
|
||||
|
||||
@Bean
|
||||
public AlipayClient alipayClient(){
|
||||
return new DefaultAlipayClient(gatewayUrl,appId,appPrivateKey,format,charset,alipayPublicKey,signType);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.group.order.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
|
||||
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
|
||||
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
|
||||
source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
|
@ -1,16 +1,22 @@
|
|||
package com.group.order.controller;
|
||||
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.group.common.domin.pojo.KillOrderEntity;
|
||||
import com.group.common.domin.pojo.PayDetail;
|
||||
import com.group.common.result.R;
|
||||
import com.group.order.payMethod.PayContext;
|
||||
import com.group.order.payMethod.PayType;
|
||||
import com.group.order.config.AliPayConfig;
|
||||
import com.group.order.pojo.ReqPay;
|
||||
import com.group.order.service.KillOrderService;
|
||||
import com.group.order.service.PayDetailService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 秒杀订单控制层
|
||||
*/
|
||||
|
@ -27,33 +33,90 @@ public class KillOrderController {
|
|||
* 如果为true,证明该用户已经购买过该商品
|
||||
*/
|
||||
@GetMapping("/exitResult")
|
||||
public boolean exitResult(@RequestParam Integer goodsId,Integer userId){
|
||||
public boolean exitResult(@RequestParam Integer goodsId, @RequestParam Integer userId) {
|
||||
KillOrderEntity one = killOrderService.getOne(new LambdaQueryWrapper<KillOrderEntity>()
|
||||
.eq(KillOrderEntity::getGoodsId, goodsId)
|
||||
.eq(KillOrderEntity::getUserId, userId));
|
||||
return one != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成秒杀订单
|
||||
*/
|
||||
@PostMapping("/addOrder")
|
||||
public R addOrder(@RequestBody KillOrderEntity killOrderEntity) {
|
||||
if (killOrderService.save(killOrderEntity)) {
|
||||
return R.error();
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除生成的订单
|
||||
*/
|
||||
@PostMapping("/deleteOrder")
|
||||
public void deleteOrder(@RequestBody KillOrderEntity killOrderEntity) {
|
||||
killOrderService.removeById(killOrderEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付秒杀订单
|
||||
*/
|
||||
@PostMapping("/payKill")
|
||||
public R payKill(@RequestBody ReqPay pay){
|
||||
return killOrderService.payKill(pay);
|
||||
public R payKill(@RequestBody ReqPay pay, HttpServletResponse response) {
|
||||
return killOrderService.payKill(pay, response);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private PayContext payContext;
|
||||
private AliPayConfig aliPayConfig;
|
||||
|
||||
@Autowired
|
||||
private PayDetailService payDetailService;
|
||||
|
||||
/**
|
||||
* 测试支付模式
|
||||
* 支付宝回调
|
||||
*/
|
||||
@GetMapping("/test")
|
||||
public R test(@RequestParam Integer type){
|
||||
// PayType pay = payContext.getPay(type);
|
||||
// pay.pay(PayDetail.builder().build());
|
||||
return R.ok("支付成功");
|
||||
@PostMapping("/notify") // 注意这里必须是POST接口
|
||||
public void payNotify(HttpServletRequest request) throws Exception {
|
||||
System.out.println("支付宝触发回调");
|
||||
if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
|
||||
System.out.println("=========支付宝异步回调========");
|
||||
Map<String, String> params = new HashMap<>();
|
||||
Map<String, String[]> requestParams = request.getParameterMap();
|
||||
for (String name : requestParams.keySet()) {
|
||||
params.put(name, request.getParameter(name));
|
||||
}
|
||||
String sign = params.get("sign");
|
||||
String content = AlipaySignature.getSignCheckContentV1(params);
|
||||
boolean checkSignature = AlipaySignature.rsa256CheckContent(content, sign, aliPayConfig.getAlipayPublicKey(), "UTF-8"); // 验证签名
|
||||
// 支付宝验签
|
||||
if (checkSignature) {
|
||||
// 验签通过
|
||||
System.out.println("交易名称: " + params.get("subject"));
|
||||
System.out.println("交易状态: " + params.get("trade_status"));
|
||||
System.out.println("支付宝交易凭证号: " + params.get("trade_no"));
|
||||
System.out.println("商户订单号: " + params.get("out_trade_no"));
|
||||
System.out.println("交易金额: " + params.get("total_amount"));
|
||||
System.out.println("买家在支付宝唯一id: " + params.get("buyer_id"));
|
||||
System.out.println("买家付款时间: " + params.get("gmt_payment"));
|
||||
System.out.println("买家付款金额: " + params.get("buyer_pay_amount"));
|
||||
String tradeNo = params.get("out_trade_no"); // 订单编号
|
||||
String gmtPayment = params.get("gmt_payment"); // 支付时间
|
||||
String alipayTradeNo = params.get("trade_no"); // 支付宝交易编号
|
||||
// 更新订单状态为已支付,设置支付信息
|
||||
PayDetail pay = payDetailService
|
||||
.getOne(new LambdaQueryWrapper<PayDetail>()
|
||||
.eq(PayDetail::getPayDetail, tradeNo));
|
||||
pay.setPayPayed(gmtPayment);
|
||||
pay.setPayState(2);
|
||||
pay.setPayPay(alipayTradeNo);
|
||||
payDetailService.updateById(pay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,49 @@
|
|||
package com.group.order.payMethod;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.request.AlipayTradePagePayRequest;
|
||||
import com.group.common.domin.pojo.PayDetail;
|
||||
import com.group.order.config.AliPayConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 支付宝支付
|
||||
*/
|
||||
@Component
|
||||
public class AliPay implements PayType {
|
||||
|
||||
@Autowired
|
||||
private AlipayClient alipayClient;
|
||||
|
||||
@Autowired
|
||||
private AliPayConfig aliPayConfig;
|
||||
|
||||
|
||||
@Override
|
||||
public void pay(PayDetail payDetail) {
|
||||
public void pay(PayDetail payDetail, HttpServletResponse response) {
|
||||
AlipayTradePagePayRequest request = getAlipayTradePagePayRequest(payDetail);
|
||||
// 执行请求,拿到响应的结果,返回给浏览器
|
||||
String form = "";
|
||||
try {
|
||||
form = alipayClient.pageExecute(request).getBody(); // 调用SDK生成表单
|
||||
} catch (AlipayApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
response.setContentType("text/html;charset=" + aliPayConfig.getCharset());
|
||||
try {
|
||||
response.getWriter().write(form);// 直接将完整的表单html输出到页面
|
||||
response.getWriter().flush();
|
||||
response.getWriter().close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
System.out.println("支付宝支付");
|
||||
}
|
||||
|
||||
|
@ -17,4 +51,17 @@ public class AliPay implements PayType {
|
|||
public String getName() {
|
||||
return PayEnum.BYALI.getName();
|
||||
}
|
||||
|
||||
private AlipayTradePagePayRequest getAlipayTradePagePayRequest(PayDetail payDetail) {
|
||||
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
|
||||
request.setNotifyUrl(aliPayConfig.getNotifyUrl());
|
||||
JSONObject bizContent = new JSONObject();
|
||||
bizContent.set("out_trade_no", payDetail.getPayDetail()); // 订单详情的编号
|
||||
bizContent.set("total_amount",payDetail.getPayPrice()); // 订单的总金额
|
||||
bizContent.set("subject", payDetail.getPayOrder()); // 支付的名称
|
||||
bizContent.set("product_code", aliPayConfig.getProductCode());
|
||||
request.setBizContent(bizContent.toString());
|
||||
request.setReturnUrl(aliPayConfig.getReturnUrl());// 支付完成后自动跳转到本地页面的路径
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,15 @@ package com.group.order.payMethod;
|
|||
import com.group.common.domin.pojo.PayDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 银行卡支付
|
||||
*/
|
||||
@Component
|
||||
public class CardPay implements PayType{
|
||||
@Override
|
||||
public void pay(PayDetail payDetail) {
|
||||
public void pay(PayDetail payDetail, HttpServletResponse response) {
|
||||
System.out.println("银行卡支付");
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ public class PayContext implements InitializingBean, ApplicationContextAware {
|
|||
*/
|
||||
public PayType getPay(Integer type){
|
||||
String payType=null;
|
||||
|
||||
if (type==1){
|
||||
payType=PayEnum.BYALI.getName();
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ package com.group.order.payMethod;
|
|||
import com.group.common.domin.pojo.PayDetail;
|
||||
import com.group.common.result.R;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 抽象接口
|
||||
*/
|
||||
public interface PayType {
|
||||
|
||||
public void pay(PayDetail payDetail);
|
||||
public void pay(PayDetail payDetail,HttpServletResponse response);
|
||||
|
||||
public String getName();
|
||||
}
|
||||
|
|
|
@ -3,13 +3,15 @@ package com.group.order.payMethod;
|
|||
import com.group.common.domin.pojo.PayDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
*微信支付
|
||||
*/
|
||||
@Component
|
||||
public class WeChat implements PayType{
|
||||
@Override
|
||||
public void pay(PayDetail payDetail) {
|
||||
public void pay(PayDetail payDetail, HttpServletResponse response) {
|
||||
System.out.println("微信支付");
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@ import com.group.common.domin.pojo.KillOrderEntity;
|
|||
import com.group.common.result.R;
|
||||
import com.group.order.pojo.ReqPay;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 秒杀订单服务层
|
||||
*/
|
||||
public interface KillOrderService extends IService<KillOrderEntity> {
|
||||
R payKill(ReqPay pay);
|
||||
R payKill(ReqPay pay, HttpServletResponse response);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.group.order.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.group.common.domin.pojo.PayDetail;
|
||||
|
||||
/**
|
||||
* 订单支付详情业务层
|
||||
*/
|
||||
public interface PayDetailService extends IService<PayDetail> {
|
||||
}
|
|
@ -12,10 +12,14 @@ import com.group.order.payMethod.PayContext;
|
|||
import com.group.order.payMethod.PayType;
|
||||
import com.group.order.pojo.ReqPay;
|
||||
import com.group.order.service.KillOrderService;
|
||||
import com.group.order.service.PayDetailService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 秒杀订单实现层
|
||||
|
@ -24,35 +28,49 @@ import java.util.Date;
|
|||
public class KillOrderServiceImpl extends ServiceImpl<KillOrderMapper, KillOrderEntity>
|
||||
implements KillOrderService {
|
||||
|
||||
@Autowired
|
||||
private PayDetailMapper payDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private PayContext payContext;
|
||||
|
||||
@Autowired
|
||||
private PayDetailService payDetailService;
|
||||
|
||||
@Override
|
||||
public R payKill(ReqPay pay) {
|
||||
public R payKill(ReqPay pay, HttpServletResponse response) {
|
||||
//安全校验
|
||||
//防止幂等性
|
||||
//防止幂等性,避免已经成功或者失败的订单重新支付
|
||||
KillOrderEntity order = getById(pay.getOrderId());
|
||||
if (order.getKillState()!=1){
|
||||
if (order.getKillState()==2){
|
||||
return R.error("订单已经支付,请不要重复支付");
|
||||
}
|
||||
if (order.getKillState()==3){
|
||||
return R.error("订单已经过期,请重新下单");
|
||||
}
|
||||
//判断是否有支付流水正在处理中
|
||||
List<PayDetail> details = payDetailService
|
||||
.list()
|
||||
.stream()
|
||||
.filter(c -> c.getPayOrder().equals(pay.getOrderId()))
|
||||
.collect(Collectors.toList());
|
||||
if (!details.isEmpty()){
|
||||
PayDetail detail = details.get(details.size() - 1);
|
||||
if (detail.getPayState()==1){
|
||||
return R.error("订单正在支付中,请不要重复支付");
|
||||
}
|
||||
if (detail.getPayState()==2){
|
||||
return R.error("该订单已经支付成功,请不要重复支付");
|
||||
}
|
||||
}
|
||||
//生成详细支付订单
|
||||
PayDetail payDetail = PayDetail.builder()
|
||||
.payPay(IdUtil.getSnowflakeNextIdStr())
|
||||
.payDetail(IdUtil.getSnowflakeNextIdStr())
|
||||
.payOrder(pay.getOrderId())
|
||||
.payCreate(new Date())
|
||||
.payTitle(pay.getPrice())
|
||||
.payPrice(pay.getPrice())
|
||||
.build();
|
||||
payDetailMapper.insert(payDetail);
|
||||
PayType pay1 = payContext.getPay(pay.getType());
|
||||
pay1.pay(payDetail);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return null;
|
||||
payDetailService.save(payDetail);
|
||||
PayType type = payContext.getPay(pay.getType());
|
||||
type.pay(payDetail,response);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.group.order.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.group.common.domin.pojo.PayDetail;
|
||||
import com.group.order.mapper.PayDetailMapper;
|
||||
import com.group.order.service.PayDetailService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 订单支付详情实现层
|
||||
*/
|
||||
@Service
|
||||
public class PayDetailServiceImpl extends ServiceImpl<PayDetailMapper, PayDetail>
|
||||
implements PayDetailService {
|
||||
|
||||
}
|
Loading…
Reference in New Issue