博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家✌
Java知识图谱点击链接:体系化学习Java(Java面试专题)
💕💕 感兴趣的同学可以收藏关注下 ,不然下次找不到哟💕💕
文章目录
- 1、什么是秒杀
- 2、秒杀需要注意哪些问题
- 3、秒杀有几种实现方式
- 4、秒杀实现的逻辑步骤
- 5、基于 Redis 实现一个简单的秒杀
- 6、“秒杀”生产代码部分展示
1、什么是秒杀
秒杀是一种促销方式,通常指在限定的时间内,以极低的价格或折扣销售商品。秒杀通常会吸引大量的消费者,因为消费者可以在短时间内以非常低的价格购买到自己需要的商品,而商家也可以通过秒杀活动促进销售,提高品牌知名度。秒杀活动通常需要考虑库存、价格、时间等多个因素,因此需要进行精细的规划和执行。
2、秒杀需要注意哪些问题
秒杀活动需要注意以下几个问题:
- 库存控制:秒杀活动需要预估销售量,并控制库存,避免出现卖断货或者库存积压的情况。
- 网站流量控制:秒杀活动容易引起大量用户访问,需要考虑网站的并发访问量,以避免网站崩溃或者访问延迟等问题。
- 价格设置:秒杀活动的价格需要具有吸引力,但是也需要考虑到成本和利润,以避免亏本或者影响品牌形象。
- 订单处理:秒杀活动的订单处理需要快速、准确,以满足用户的需求。
- 客服支持:秒杀活动期间需要加强客服支持,及时解答用户的问题和投诉,保障用户的购物体验。
- 安全防范:秒杀活动容易引起黑客攻击、恶意刷单等问题,需要加强安全防范措施,保障用户的账户和交易安全。
秒杀活动还需要注意超卖问题。由于秒杀活动的商品价格通常比平时低很多,会吸引大量用户参与,但是库存有限,容易出现超卖的情况。为了避免超卖问题,可以采取以下措施:
- 合理设置商品数量:在活动策划阶段,需要根据预估的销售量和库存情况,合理设置商品数量,以避免出现超卖的情况。
- 实时更新库存信息:在秒杀活动进行期间,需要实时更新库存信息,避免超卖的情况。可以通过技术手段,比如使用分布式锁、队列等方式实现库存的实时更新。
- 限制用户购买数量:可以限制每个用户购买的数量,避免某些用户恶意抢购导致超卖的情况。
- 及时通知用户:如果出现了超卖的情况,需要及时通知用户,并及时退款或者提供其他的补偿措施,保障用户的权益和满意度。
3、秒杀有几种实现方式
秒杀活动的实现方式有多种,以下是其中几种常用的方式,以及使用的技术实现:
- 队列方式:将用户的请求放入队列中,由队列进行处理,避免并发请求对系统造成压力,同时也可以保证请求的顺序。使用技术:消息队列(如RabbitMQ、Kafka)。
- 分布式锁方式:使用分布式锁来控制并发请求的访问,避免重复的请求对系统造成压力。使用技术:分布式锁(如Redis、Zookeeper)。
- 缓存方式:将商品信息预先缓存到缓存服务器中,当用户请求时从缓存中获取,避免频繁地访问数据库。使用技术:缓存(如Redis、Memcached)。
- 限流方式:通过限制每个用户的请求次数来控制并发请求的数量,避免系统崩溃。使用技术:限流算法(如令牌桶算法、漏桶算法)。
- 异步方式:使用异步处理的方式来处理请求,通过将请求放入消息队列中异步处理,避免系统压力过大。使用技术:消息队列(如RabbitMQ、Kafka)。
以上是常用的几种秒杀活动的实现方式,不同的方式适用于不同的场景和需求,需要根据实际情况进行选择。同时,还需要注意系统的稳定性和安全性,避免出现系统崩溃或者数据泄露等问题。
4、秒杀实现的逻辑步骤
秒杀活动的实现逻辑步骤如下:
-
预热阶段:在秒杀活动开始前,提前进行预热,向用户宣传活动信息,吸引用户的关注。
-
商品准备阶段:在秒杀活动开始前,需要准备好秒杀商品的信息,包括商品名称、价格、库存等信息。
-
用户抢购阶段:秒杀活动开始后,用户可以进入抢购页面进行抢购。在这个阶段,需要进行以下处理:
a. 验证用户身份:首先需要验证用户的身份,确保只有注册用户才能参与抢购活动。
b. 验证商品库存:当用户提交抢购请求时,需要验证商品的库存是否充足,如果库存不足,则返回抢购失败的信息。
c. 生成订单:当用户抢购成功时,需要生成订单,并将订单信息保存到数据库中。
d. 扣减库存:当用户抢购成功时,需要扣减商品的库存数量。
e. 支付订单:当用户抢购成功时,需要进行支付操作,将订单的金额从用户的账户中扣除。 -
结束阶段:当秒杀活动结束后,需要进行以下处理:
a. 结算订单:对于已经生成的订单,需要进行结算操作,将订单的金额结算给商家。
b. 处理退款:对于用户取消订单或者支付失败的情况,需要进行退款操作,将金额返还给用户。
c. 统计数据:需要对秒杀活动的数据进行统计,包括参与人数、抢购成功率、销售额等信息。
以上是秒杀活动的实现逻辑步骤,不同的实现方式可能会有所不同,需要根据实际情况进行选择。同时,还需要注意系统的稳定性和安全性,避免出现系统崩溃或者数据泄露等问题。
5、基于 Redis 实现一个简单的秒杀
实现代码如下:
package com.pany.camp.redis;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
*
* @description: 秒杀样例
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-26 12:37
*/
public class SeckillDemo {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String REDIS_PASSWORD = null;
private static final int REDIS_TIMEOUT = 10000;
private static final int REDIS_MAX_TOTAL = 100;
private static final int REDIS_MAX_IDLE = 10;
private static final int REDIS_MAX_WAIT_MILLIS = 10000;
private static final String STOCK_KEY = "stock";
private static final String ORDER_QUEUE_KEY = "order_queue";
private static final String LOCK_KEY = "lock";
private static JedisPool jedisPool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(REDIS_MAX_TOTAL);
config.setMaxIdle(REDIS_MAX_IDLE);
config.setMaxWaitMillis(REDIS_MAX_WAIT_MILLIS);
jedisPool = new JedisPool(config, REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT, REDIS_PASSWORD);
}
public static void main(String[] args) {
Jedis jedis = jedisPool.getResource();
try {
// 初始化库存数量
jedis.set(STOCK_KEY, "100");
// 模拟多个用户发起秒杀请求
for (int i = 0; i < 200; i++) {
Thread thread = new Thread(new UserThread());
thread.start();
}
} finally {
if (jedis != null) {
jedis.close();
}
}
}
static class UserThread implements Runnable {
public UserThread() {
}
@Override
public void run() {
Jedis jedis = jedisPool.getResource();
try {
handleSecKill(jedis);
} catch (Exception e) {
} finally {
jedis.close();
}
}
/**
* 处理秒杀
*
* @since 1.0.0
* @param
* @param jedis
* @return: void
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-26 12:13
*/
private void handleSecKill(Jedis jedis) {
String userId = Thread.currentThread().getName();
// 检查库存数量是否大于0
String stock = jedis.get(STOCK_KEY);
int stockNum = StringUtils.isBlank(stock) ? 0:Integer.parseInt(stock);
if (stockNum <= 0) {
System.out.println(userId + " 秒杀失败,库存不足");
return;
}
// 将用户的请求加入到队列中
jedis.lpush(ORDER_QUEUE_KEY, userId);
// 获取分布式锁
String lockValue = System.currentTimeMillis() + "";
Long result = jedis.setnx(LOCK_KEY, lockValue);
while (result == 0) {
// 如果获取锁失败,则等待一段时间后重新尝试获取锁
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = jedis.setnx(LOCK_KEY, lockValue);
}
try {
// 再次检查库存数量是否大于0
stock = jedis.get(STOCK_KEY);
if (Integer.parseInt(stock) <= 0) {
System.out.println(userId + " 秒杀失败,库存不足");
return;
}
// 减少库存数量,并将用户的请求从队列中移除
jedis.decr(STOCK_KEY);
jedis.lrem(ORDER_QUEUE_KEY, 0, userId);
System.out.println(userId + " 秒杀成功,库存剩余:" + jedis.get(STOCK_KEY));
} finally {
// 释放锁
if (lockValue.equals(jedis.get(LOCK_KEY))) {
jedis.del(LOCK_KEY);
}
}
}
}
}
输出结果如下:
Thread-30 秒杀成功,库存剩余:99
Thread-60 秒杀成功,库存剩余:98
Thread-65 秒杀成功,库存剩余:97
Thread-110 秒杀成功,库存剩余:96
Thread-77 秒杀成功,库存剩余:95
Thread-107 秒杀成功,库存剩余:94
Thread-140 秒杀成功,库存剩余:93
Thread-109 秒杀成功,库存剩余:92
Thread-103 秒杀成功,库存剩余:91
Thread-104 秒杀成功,库存剩余:90
Thread-67 秒杀成功,库存剩余:89
Thread-111 秒杀成功,库存剩余:88
Thread-112 秒杀成功,库存剩余:87
Thread-73 秒杀成功,库存剩余:86
Thread-114 秒杀成功,库存剩余:85
Thread-115 秒杀成功,库存剩余:84
Thread-116 秒杀成功,库存剩余:83
Thread-117 秒杀成功,库存剩余:82
Thread-118 秒杀成功,库存剩余:81
Thread-82 秒杀成功,库存剩余:80
Thread-120 秒杀成功,库存剩余:79
Thread-121 秒杀成功,库存剩余:78
Thread-137 秒杀成功,库存剩余:77
Thread-123 秒杀成功,库存剩余:76
Thread-138 秒杀成功,库存剩余:75
Thread-124 秒杀成功,库存剩余:74
Thread-108 秒杀成功,库存剩余:73
Thread-127 秒杀成功,库存剩余:72
Thread-141 秒杀成功,库存剩余:71
Thread-93 秒杀成功,库存剩余:70
Thread-142 秒杀成功,库存剩余:69
Thread-159 秒杀成功,库存剩余:68
Thread-143 秒杀成功,库存剩余:67
Thread-174 秒杀成功,库存剩余:66
Thread-126 秒杀成功,库存剩余:65
Thread-162 秒杀成功,库存剩余:64
Thread-145 秒杀成功,库存剩余:63
Thread-175 秒杀成功,库存剩余:62
Thread-147 秒杀成功,库存剩余:61
Thread-131 秒杀成功,库存剩余:60
Thread-148 秒杀成功,库存剩余:59
Thread-165 秒杀成功,库存剩余:58
Thread-133 秒杀成功,库存剩余:57
Thread-149 秒杀成功,库存剩余:56
Thread-150 秒杀成功,库存剩余:55
Thread-166 秒杀成功,库存剩余:54
Thread-179 秒杀成功,库存剩余:53
Thread-96 秒杀成功,库存剩余:52
Thread-136 秒杀成功,库存剩余:51
Thread-152 秒杀成功,库存剩余:50
Thread-153 秒杀成功,库存剩余:49
Thread-102 秒杀成功,库存剩余:48
Thread-163 秒杀成功,库存剩余:47
Thread-154 秒杀成功,库存剩余:46
Thread-155 秒杀成功,库存剩余:45
Thread-156 秒杀成功,库存剩余:44
Thread-157 秒杀成功,库存剩余:43
Thread-158 秒杀成功,库存剩余:42
Thread-128 秒杀成功,库存剩余:41
Thread-160 秒杀成功,库存剩余:40
Thread-161 秒杀成功,库存剩余:39
Thread-92 秒杀成功,库存剩余:38
Thread-130 秒杀成功,库存剩余:37
Thread-192 秒杀成功,库存剩余:36
Thread-193 秒杀成功,库存剩余:35
Thread-194 秒杀成功,库存剩余:34
Thread-167 秒杀成功,库存剩余:33
Thread-168 秒杀成功,库存剩余:32
Thread-169 秒杀成功,库存剩余:31
Thread-170 秒杀成功,库存剩余:30
Thread-171 秒杀成功,库存剩余:29
Thread-139 秒杀成功,库存剩余:28
Thread-172 秒杀成功,库存剩余:27
Thread-173 秒杀成功,库存剩余:26
Thread-144 秒杀成功,库存剩余:25
Thread-146 秒杀成功,库存剩余:24
Thread-177 秒杀成功,库存剩余:23
Thread-178 秒杀成功,库存剩余:22
Thread-151 秒杀成功,库存剩余:21
Thread-180 秒杀成功,库存剩余:20
Thread-181 秒杀成功,库存剩余:19
Thread-182 秒杀成功,库存剩余:18
Thread-183 秒杀成功,库存剩余:17
Thread-184 秒杀成功,库存剩余:16
Thread-185 秒杀成功,库存剩余:15
Thread-186 秒杀成功,库存剩余:14
Thread-187 秒杀成功,库存剩余:13
Thread-188 秒杀成功,库存剩余:12
Thread-189 秒杀成功,库存剩余:11
Thread-190 秒杀成功,库存剩余:10
Thread-191 秒杀成功,库存剩余:9
Thread-164 秒杀成功,库存剩余:8
Thread-132 秒杀成功,库存剩余:7
Thread-134 秒杀成功,库存剩余:6
Thread-195 秒杀成功,库存剩余:5
Thread-196 秒杀成功,库存剩余:4
Thread-197 秒杀成功,库存剩余:3
Thread-198 秒杀成功,库存剩余:2
Thread-199 秒杀成功,库存剩余:1
Thread-200 秒杀成功,库存剩余:0
Thread-176 秒杀失败,库存不足
Thread-44 秒杀失败,库存不足
Thread-75 秒杀失败,库存不足
Thread-40 秒杀失败,库存不足
Thread-99 秒杀失败,库存不足
Thread-42 秒杀失败,库存不足
Thread-90 秒杀失败,库存不足
Thread-50 秒杀失败,库存不足
Thread-135 秒杀失败,库存不足
Thread-70 秒杀失败,库存不足
Thread-74 秒杀失败,库存不足
Thread-66 秒杀失败,库存不足
Thread-94 秒杀失败,库存不足
Thread-24 秒杀失败,库存不足
Thread-95 秒杀失败,库存不足
Thread-54 秒杀失败,库存不足
Thread-129 秒杀失败,库存不足
Thread-3 秒杀失败,库存不足
Thread-86 秒杀失败,库存不足
Thread-28 秒杀失败,库存不足
Thread-113 秒杀失败,库存不足
Thread-9 秒杀失败,库存不足
Thread-88 秒杀失败,库存不足
Thread-98 秒杀失败,库存不足
Thread-41 秒杀失败,库存不足
Thread-87 秒杀失败,库存不足
Thread-58 秒杀失败,库存不足
Thread-72 秒杀失败,库存不足
Thread-4 秒杀失败,库存不足
Thread-85 秒杀失败,库存不足
Thread-63 秒杀失败,库存不足
Thread-100 秒杀失败,库存不足
Thread-10 秒杀失败,库存不足
Thread-80 秒杀失败,库存不足
Thread-33 秒杀失败,库存不足
Thread-106 秒杀失败,库存不足
Thread-43 秒杀失败,库存不足
Thread-83 秒杀失败,库存不足
Thread-17 秒杀失败,库存不足
Thread-76 秒杀失败,库存不足
Thread-35 秒杀失败,库存不足
Thread-84 秒杀失败,库存不足
Thread-78 秒杀失败,库存不足
Thread-125 秒杀失败,库存不足
Thread-27 秒杀失败,库存不足
Thread-119 秒杀失败,库存不足
Thread-15 秒杀失败,库存不足
Thread-89 秒杀失败,库存不足
Thread-39 秒杀失败,库存不足
Thread-68 秒杀失败,库存不足
Thread-71 秒杀失败,库存不足
Thread-91 秒杀失败,库存不足
Thread-32 秒杀失败,库存不足
Thread-62 秒杀失败,库存不足
Thread-34 秒杀失败,库存不足
Thread-81 秒杀失败,库存不足
Thread-49 秒杀失败,库存不足
Thread-97 秒杀失败,库存不足
Thread-7 秒杀失败,库存不足
Thread-79 秒杀失败,库存不足
Thread-22 秒杀失败,库存不足
Thread-23 秒杀失败,库存不足
Thread-25 秒杀失败,库存不足
Thread-53 秒杀失败,库存不足
Thread-38 秒杀失败,库存不足
Thread-37 秒杀失败,库存不足
Thread-29 秒杀失败,库存不足
Thread-6 秒杀失败,库存不足
Thread-19 秒杀失败,库存不足
Thread-14 秒杀失败,库存不足
Thread-48 秒杀失败,库存不足
Thread-31 秒杀失败,库存不足
Thread-51 秒杀失败,库存不足
Thread-64 秒杀失败,库存不足
Thread-122 秒杀失败,库存不足
Thread-61 秒杀失败,库存不足
Thread-55 秒杀失败,库存不足
Thread-36 秒杀失败,库存不足
Thread-52 秒杀失败,库存不足
Thread-69 秒杀失败,库存不足
Thread-2 秒杀失败,库存不足
Thread-26 秒杀失败,库存不足
Thread-12 秒杀失败,库存不足
Thread-20 秒杀失败,库存不足
Thread-57 秒杀失败,库存不足
Thread-8 秒杀失败,库存不足
Thread-59 秒杀失败,库存不足
Thread-13 秒杀失败,库存不足
Thread-47 秒杀失败,库存不足
Thread-5 秒杀失败,库存不足
Thread-46 秒杀失败,库存不足
Thread-21 秒杀失败,库存不足
Thread-101 秒杀失败,库存不足
Thread-45 秒杀失败,库存不足
Thread-1 秒杀失败,库存不足
Thread-18 秒杀失败,库存不足
Thread-16 秒杀失败,库存不足
Thread-56 秒杀失败,库存不足
Thread-105 秒杀失败,库存不足
Thread-11 秒杀失败,库存不足
需要引入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
6、“秒杀”生产代码部分展示
下面是一个基于微信支付的秒杀程序:
package com.aiocloud.group.camp.controller;
import com.aiocloud.camp.common.bean.BeanConvertor;
import com.aiocloud.camp.common.domain.AiocloudPageResult;
import com.aiocloud.camp.common.domain.AiocloudResult;
import com.aiocloud.camp.common.enums.*;
import com.aiocloud.camp.common.utils.DateUtil;
import com.aiocloud.camp.dao.group.vo.GpOrderVO;
import com.aiocloud.group.camp.ai.IGpOrderApp;
import com.aiocloud.group.camp.ai.dto.GpOrderDTO;
import com.aiocloud.group.camp.config.RedisLockConfig;
import com.aiocloud.group.camp.config.WxConfig;
import com.aiocloud.group.camp.utils.CommonUtil;
import com.aiocloud.group.camp.utils.MD5Util;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import static com.aiocloud.group.camp.config.WxConfig.appid;
@RestController
@RequestMapping("/v1/seckill")
public class GpSeckillController {
private static Logger logger = LoggerFactory.getLogger(GpSeckillController.class);
/**
* 超时时间
*/
private static final int TIMEOUT = 10000;
@Resource
private IGpOrderApp gpOrderApp;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisLockConfig redisLock;
@PostMapping("/get")
public AiocloudResult<String> getCommodity(
@RequestParam(value = "seckillId", required = true) String seckillId,
@RequestParam(value = "userId", required = true) String userId
) {
AiocloudResult<String> result = new AiocloudResult<>();
try{
long time = System.currentTimeMillis() + TIMEOUT;
//加锁
String lockKey = "aiocloud:seckill:" + seckillId;
if (!redisLock.lock(lockKey, String.valueOf(time))){
result.setCode(200);
result.setMsg("抱歉,没抢到,换个姿式再来一遍");
result.setData("fail");
return result;
}
Integer stockNum = (Integer) redisTemplate.opsForValue().get(seckillId);
if (stockNum == 0) {
//库存不足
result.setCode(200);
result.setMsg("抱歉,商品已经被抢光了,请留意下次活动");
result.setData("fail");
return result;
} else {
String userKey = seckillId + "-" + userId;
Boolean hasKey = redisTemplate.hasKey(userKey);
if(hasKey) {
result.setCode(200);
result.setMsg("您已经抢购到了一份商品,不能重复抢购");
result.setData("fail");
return result;
}
else {
stockNum--;
redisTemplate.opsForValue().set(seckillId, stockNum);
redisTemplate.opsForValue().set(userKey, stockNum);
}
}
//解锁
redisLock.unlock(lockKey, String.valueOf(time));
result.setData("success");
}catch(Exception e){
result.setCode(AiocloudPageResult.ERROR);
result.setMsg("秒杀商品失败");
}
return result;
}
@PostMapping("/stock")
public AiocloudResult<Integer> getStockNum(
@RequestParam(value = "seckillId", required = true) String seckillId
) {
AiocloudResult<Integer> result = new AiocloudResult<>();
try{
Integer stockNum = (Integer) redisTemplate.opsForValue().get(seckillId);
result.setData(stockNum);
} catch(Exception e) {
logger.error("查询库存异常, cause by: " + e.getMessage());
result.setCode(AiocloudPageResult.ERROR);
result.setMsg("查询库存异常");
}
return result;
}
/**
* @param [openid, outTradeNo, totalAmount, subject, body, request]
* @return com.aiocloud.camp.common.domain.AiocloudResult<java.lang.Object>
* @Title: pay
* @Description: 秒杀微信支付
* @author panyong
* @version 1.0
* @createtime 2021-04-07 13:42
*/
@PostMapping("/pay")
public AiocloudResult<Object> pay(
@RequestParam(value = "commodityId", required = true) String commodityId,
@RequestParam(value = "seckillId", required = true) String seckillId,
@RequestParam(value = "addressId", required = true) String addressId,
@RequestParam(value = "remark", required = true) String remark,
@RequestParam(value = "openid", required = true) String openid,
@RequestParam(value = "outTradeNo", required = true) String outTradeNo,
@RequestParam(value = "totalAmount", required = true) String totalAmount,
@RequestParam(value = "subject", required = true) String subject,
@RequestParam(value = "body", required = true) String body,
HttpServletRequest request
) {
AiocloudResult<Object> result = new AiocloudResult<>();
try {
// 1、参数校验
if (
StringUtils.isBlank(commodityId)
|| StringUtils.isBlank(addressId) || StringUtils.isBlank(openid)
|| StringUtils.isBlank(outTradeNo) || StringUtils.isBlank(totalAmount)
|| StringUtils.isBlank(subject) || StringUtils.isBlank(body) || StringUtils.isBlank(seckillId)
) {
logger.error("参数提供错误");
result.setMsg("参数提供错误");
return result;
}
// 生成订单
GpOrderDTO orderDTO = new GpOrderDTO();
orderDTO.setOutTradeNo(outTradeNo);
orderDTO.setPayStatus(PayStatusEnum.Unpaid.getCode());
orderDTO.setOrderStatus(OrderStatusEnum.OrderHasBeenPlaced.getCode());
orderDTO.setPayWay(PayWayEnum.WeChat.getCode());
orderDTO.setSubject(subject);
orderDTO.setDescription(body);
orderDTO.setOpenid(openid);
orderDTO.setCreateTime(new Date());
BigDecimal bigDecimal = new BigDecimal(totalAmount);
orderDTO.setTotalAmount(bigDecimal);
orderDTO.setCommodityId(commodityId);
orderDTO.setNum(1);
orderDTO.setType(OrderTypeEnum.seckill.getCode());
orderDTO.setRemark(remark);
orderDTO.setAddressId(addressId);
orderDTO.setRelateId(seckillId);
boolean isSuccess = gpOrderApp.add(orderDTO);
if (isSuccess) {
Map<String, String> resultMap = getWxPayMap(outTradeNo, totalAmount, openid, request);
resultMap.put("signType", "MD5");
resultMap.put("total_fee", totalAmount);
String timestamp = String.valueOf(DateUtil.getSecondTimestamp(new Date()));
resultMap.put("timestamp", timestamp);
String stringSignTemp = "appId=" + resultMap.get("appid") +
"&nonceStr=" + resultMap.get("nonce_str") +
"&package=prepay_id=" + resultMap.get("prepay_id") +
"&signType=MD5" +
"&timeStamp=" + resultMap.get("timestamp") +
"&key=" + WxConfig.apiKey;
String finalsign = MD5Util.getMD5Str(stringSignTemp).toUpperCase();
resultMap.put("finalsign", finalsign);
result.setData(resultMap);
}
} catch (Exception e) {
result.setCode(AiocloudResult.ERROR);
result.setMsg("支付失败");
}
return result;
}
@Transactional
public Map<String, String> getWxPayMap(String outTradeNo, String totalAmount, String openId, HttpServletRequest request) throws Exception {
String orderExpireTime = DateUtil.getOrderExpireTime(6 * 60 * 1000L);
SortedMap<String, String> req = new TreeMap<String, String>();
//公众号
req.put("appid", appid);
// 商户号
req.put("mch_id", WxConfig.mchId);
// 32位随机字符串
req.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
req.put("body", "aiocloud开发微信支付测试");
// 商户订单号
req.put("out_trade_no", outTradeNo);
DecimalFormat decimalFormat = new DecimalFormat("###################.###########");
// 标价金额(分)
String total_fee = decimalFormat.format(Double.parseDouble(totalAmount) * 100);
req.put("total_fee", "1");
// 终端IP
req.put("spbill_create_ip", CommonUtil.getIp(request));
// 回调地址
req.put("notify_url", WxConfig.notifyUrl);
// 交易类型
req.put("trade_type", WxConfig.tradeType);
// 超时时间
req.put("time_expire", orderExpireTime);
req.put("openid", openId);
req.put("sign", WXPayUtil.generateSignature(req, WxConfig.apiKey, WXPayConstants.SignType.MD5)); // 签名
// 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, WxConfig.apiKey);
System.err.println(String.format("微信支付预下单请求 xml 格式:\n%s", xmlBody));
//发送 POST 请求 统一下单 API 并携带 xmlBody 内容,然后获得返回接口结果
String res = CommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xmlBody);
System.err.println(String.format("%s", res));
Map<String, String> resultMap = WXPayUtil.xmlToMap(res);
//将返回结果从 xml 格式转换为 map 格式
return resultMap;
}
/**
* 接收微信官方返回觉得支付结果通知
*
* @return
* @throws Exception
*/
@ApiOperation("接收支付结果通知的接口")
@PostMapping("/getNotifyUrl")
@ResponseBody
public String weiXinPayCallBack(HttpServletRequest request) throws Exception {
Map<String, String> rMap = new HashMap<String, String>();
//接收微信官方返回的支付结果
InputStream inputStream = request.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String temp;
StringBuilder stringBuilder = new StringBuilder();
while ((temp = bufferedReader.readLine()) != null) {
stringBuilder.append(temp);
}
//关闭流,先打开后关,后打开先关
bufferedReader.close();
inputStream.close();
Map<String, String> resultMap = WXPayUtil.xmlToMap(stringBuilder.toString());
//判断是不是来自微信官方,进行签名验证
boolean flag = WXPayUtil.isSignatureValid(resultMap, WxConfig.apiKey, WXPayConstants.SignType.MD5);
if (flag) {
//获取支付结果中的result_code,根据此值判断是否进行自身业务实现
String resultCode = resultMap.get("result_code");
if (resultCode.equals("SUCCESS")) {
//获取微信返回的交易号
String tradeNo = resultMap.get("transaction_id");
//商户订单号
String outTradeNo = resultMap.get("out_trade_no");
//支付状态判断和自身业务实现
GpOrderVO order = gpOrderApp.getOrderByOutTradeNo(outTradeNo);
if (order != null) {
//格式化成所需的商品金额
BigDecimal totalAmount = order.getTotalAmount();
double TotalFeeDouble = totalAmount.doubleValue() * 100;
//回调接口和实际订单进行比较
if (TotalFeeDouble == Double.parseDouble(resultMap.get("total_fee"))) {
//修改订单支付状态
order.setPayTime(new Date());
order.setPayStatus(PayStatusEnum.PaymentSuccessful.getCode());
order.setOrderStatus(OrderStatusEnum.CompleteOrder.getCode());
order.setConfirm(OrderConfirmEnum.Delivered.getCode());
order.setTradeNo(tradeNo);
gpOrderApp.update(BeanConvertor.getCopyObject(GpOrderDTO.class, order));
rMap.put("return_code", "SUCCESS");
rMap.put("return_msg", "已收到");
}
} else {
rMap.put("return_code", "FAIL");
rMap.put("return_msg", "已收到");
}
} else {
rMap.put("return_code", "FAIL");
rMap.put("return_msg", "已收到");
}
} else {
rMap.put("return_code", "FAIL");
rMap.put("return_msg", "已收到");
}
//以xml格式给微信官方返回是否成功的应答
return WXPayUtil.mapToXml(rMap);
}
@PostMapping("/wait/pay")
public AiocloudResult<Object> pay(
@RequestParam(value = "outTradeNo", required = true) String outTradeNo,
@RequestParam(value = "openid", required = true) String openid,
HttpServletRequest request
) {
AiocloudResult<Object> result = new AiocloudResult<>();
try {
// 1、参数校验
if (StringUtils.isBlank(outTradeNo)) {
logger.error("参数提供错误");
result.setMsg("参数提供错误");
return result;
}
GpOrderVO order = gpOrderApp.getOrderByOutTradeNo(outTradeNo);
String totalAmount = order.getTotalAmount().toString();
Map<String, String> resultMap = getWxPayMap(outTradeNo, totalAmount, openid, request);
resultMap.put("signType", "MD5");
resultMap.put("total_fee", totalAmount);
String timestamp = String.valueOf(DateUtil.getSecondTimestamp(new Date()));
resultMap.put("timestamp", timestamp);
String stringSignTemp = "appId=" + resultMap.get("appid") +
"&nonceStr=" + resultMap.get("nonce_str") +
"&package=prepay_id=" + resultMap.get("prepay_id") +
"&signType=MD5" +
"&timeStamp=" + resultMap.get("timestamp") +
"&key=" + WxConfig.apiKey;
String finalsign = MD5Util.getMD5Str(stringSignTemp).toUpperCase();
resultMap.put("finalsign", finalsign);
result.setData(resultMap);
} catch (Exception e) {
result.setCode(AiocloudResult.ERROR);
result.setMsg("支付失败");
}
return result;
}
}
💕💕 本文由激流原创,首发于CSDN博客,博客主页 https://blog.csdn.net/qq_37967783?spm=1010.2135.3001.5421
💕💕喜欢的话记得点赞收藏啊