diff --git a/mallinkAdmin/src/main/java/com/simple/config/RedisConfig.java b/mallinkAdmin/src/main/java/com/simple/config/RedisConfig.java index 4c5551668..80e60dc4f 100644 --- a/mallinkAdmin/src/main/java/com/simple/config/RedisConfig.java +++ b/mallinkAdmin/src/main/java/com/simple/config/RedisConfig.java @@ -46,4 +46,5 @@ public class RedisConfig extends CachingConfigurerSupport { return jedisPool; } + } diff --git a/mallinkService/src/main/java/com/simple/common/ErrorCode.java b/mallinkService/src/main/java/com/simple/common/ErrorCode.java index 59d585b98..3ed749c3d 100644 --- a/mallinkService/src/main/java/com/simple/common/ErrorCode.java +++ b/mallinkService/src/main/java/com/simple/common/ErrorCode.java @@ -30,6 +30,8 @@ public enum ErrorCode{ LOGIN_DENIED(1014, "登录失败"), TOKEN_INVALID(1015, "TOKEN无效"), + TOO_MANY_REQUEST(1050, "太多的请求访问"), + /** * 业务级别 diff --git a/mallinkService/src/main/java/com/simple/service/impl/WxOrderServiceImpl.java b/mallinkService/src/main/java/com/simple/service/impl/WxOrderServiceImpl.java index 6141cbc7a..8314b3a6b 100644 --- a/mallinkService/src/main/java/com/simple/service/impl/WxOrderServiceImpl.java +++ b/mallinkService/src/main/java/com/simple/service/impl/WxOrderServiceImpl.java @@ -13,6 +13,7 @@ import com.simple.exception.MallinkException; import com.simple.mapper.WxCouponMapper; import com.simple.mapper.WxOrderMapper; import com.simple.service.WxOrderService; +import com.simple.utils.RedisLock; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -25,6 +26,9 @@ public class WxOrderServiceImpl implements WxOrderService { private Logger logger = Logger.getLogger(getClass()); + @Autowired + RedisLock redisLock; + @Autowired WxCouponMapper wxCouponMapper; @@ -49,17 +53,38 @@ public class WxOrderServiceImpl implements WxOrderService { @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class}) public WxOrder saveOrder(WxOrder record) { + + String couponIdStr = String.valueOf(record.getCouponId()); + //加锁 + long time = System.currentTimeMillis() + RedisLock.TIMEOUT; + String timeStr = String.valueOf(time); + if(!redisLock.lock(couponIdStr, timeStr)) { + logger.error("此券被锁定, couponId: " + record.getCouponId()); + throw new MallinkException(ErrorCode.TOO_MANY_REQUEST); + } + try { // 检查 优惠券 库存 WxCoupon coupon = wxCouponMapper.selectByPrimaryKey(record.getCouponId()); if (coupon.getRemainInventory() <= 0) { - logger.error("coupon not found, couponId: " + record.getCouponId()); + //解锁 + redisLock.unlock(couponIdStr, timeStr); + + logger.error("此券库存为0, couponId: " + record.getCouponId()); throw new MallinkException(ErrorCode.REMAIN_IS_EMPTY); } // 减库存 coupon.setRemainInventory(coupon.getRemainInventory() - 1); wxCouponMapper.updateByPrimaryKeySelective(coupon); + } catch (RuntimeException e) { + //解锁 + redisLock.unlock(couponIdStr, timeStr); + logger.error("此券减库存失败, couponId: " + record.getCouponId()); + throw new MallinkException(ErrorCode.ORDER_IS_FAIL); + } + + try { // 保存订单 final IdWorker idWorker = IdWorker.get(); record.setId(idWorker.nextId()); diff --git a/mallinkService/src/main/java/com/simple/service/impl/WxPayOrderServiceImpl.java b/mallinkService/src/main/java/com/simple/service/impl/WxPayOrderServiceImpl.java index 2e9f08808..dd7c056f0 100644 --- a/mallinkService/src/main/java/com/simple/service/impl/WxPayOrderServiceImpl.java +++ b/mallinkService/src/main/java/com/simple/service/impl/WxPayOrderServiceImpl.java @@ -14,7 +14,6 @@ import com.simple.domain.po.WxPayOrder; import com.simple.enums.EnumOrderStatus; import com.simple.enums.EnumPayStatus; import com.simple.enums.EnumPayWay; -import com.simple.exception.BizMessageException; import com.simple.exception.MallinkException; import com.simple.mapper.WxOrderMapper; import com.simple.mapper.WxPayOrderMapper; @@ -32,7 +31,6 @@ import org.springframework.stereotype.Service; import com.simple.common.IdWorker; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import tk.mybatis.mapper.entity.Example; @Service public class WxPayOrderServiceImpl implements WxPayOrderService { @@ -48,6 +46,11 @@ public class WxPayOrderServiceImpl implements WxPayOrderService { @Autowired WxOrderService wxOrderService; + String weappId = ""; + String wechatMchId = ""; + String partnerKey = ""; + String wxpayNotifyUrl = ""; + JSONObject errorMap = JSON.parseObject("{\"NOAUTH\":{\"detail\":\"商户无此接口权限\",\"reason\":\"商户未开通此接口权限\",\"resolution\":\"请商户前往申请此接口权限\"}," + "\"NOTENOUGH\":{\"detail\":\"余额不足\",\"reason\":\"用户帐号余额不足\",\"resolution\":\"用户帐号余额不足,请用户充值或更换支付卡后再支付\"}," + "\"ORDERPAID\":{\"detail\":\"商户订单已支付\",\"reason\":\"商户订单已支付,无需重复操作\",\"resolution\":\"商户订单已支付,无需更多操作\"}," + @@ -66,7 +69,8 @@ public class WxPayOrderServiceImpl implements WxPayOrderService { @Override public ResultData createPayOrder(WxPayOrder record, EnumPayWay payWay) { - String partnerKey = null; + final IdWorker idworker = IdWorker.get(); + try { // 1. check 订单 WxOrder order = wxOrderMapper.selectByPrimaryKey(record.getOrderId()); @@ -80,9 +84,10 @@ public class WxPayOrderServiceImpl implements WxPayOrderService { } // TODO body String body = ""; - // 2. 创建支付订单 + // 2. check 是否有支付订单 + // 3. 创建支付订单 Date currentDate = new Date(); - final IdWorker idworker = IdWorker.get(); + Long id = idworker.nextId(); record.setId(id); record.setCreateTime(currentDate); @@ -99,18 +104,22 @@ public class WxPayOrderServiceImpl implements WxPayOrderService { int sqlRow = wxPayOrderMapper.insertSelective(record); if(sqlRow == 1) { + return new ResultData(); + } + /* + { // 统一下单 String noncestr = Utility.generate32UUID(); WxPayOrderP wxPayOrderP = new WxPayOrderP(); - //wxPayOrderP.setAppid(wechatAppId); - //wxPayOrderP.setMch_id(wechatMchId); + wxPayOrderP.setAppid(weappId); + wxPayOrderP.setMch_id(wechatMchId); wxPayOrderP.setNonce_str(noncestr); - //wxPayOrderP.setBody(body); + wxPayOrderP.setBody(body); wxPayOrderP.setOut_trade_no(record.getPayOrderNo()); BigDecimal llp = order.getPayment().multiply(new BigDecimal(100)); wxPayOrderP.setTotal_fee(llp.intValue()); wxPayOrderP.setSpbill_create_ip(record.getIp()); // 终端IP - //wxPayOrderP.setNotify_url(wxpayNotifyUrl); + wxPayOrderP.setNotify_url(wxpayNotifyUrl); wxPayOrderP.setTrade_type(WxPay.TradeType.APP.name()); // 终端类型 wxPayOrderP.setProduct_id(String.valueOf(order.getId())); // 订单ID wxPayOrderP.setTime_start(Utility.getDataFormatStringYYYYMMDDHHmmss(currentTime)); @@ -139,6 +148,7 @@ public class WxPayOrderServiceImpl implements WxPayOrderService { return new ResultData(ErrorCode.PAYORDER_ERROR.getCode(), errObj.toJSONString(), returnMap); } } + */ return new ResultData(); } catch (RuntimeException e) { throw new MallinkException(ErrorCode.PAYORDER_ERROR); diff --git a/mallinkService/src/main/java/com/simple/utils/RedisLock.java b/mallinkService/src/main/java/com/simple/utils/RedisLock.java new file mode 100644 index 000000000..cec6ccbdd --- /dev/null +++ b/mallinkService/src/main/java/com/simple/utils/RedisLock.java @@ -0,0 +1,70 @@ +package com.simple.utils; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * RedisLock + * User: Stormeye + * Date: 2018/8/12 + * Explain:Redis分布式锁 + */ +@Component +public class RedisLock { + private org.apache.log4j.Logger logger = Logger.getLogger(getClass()); + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + public static final int TIMEOUT = 10*1000;//超时时间 10s + + /** + * 加锁 + * @param key productId - 商品的唯一标志 + * @param value 当前时间+超时时间 + * @return + */ + public boolean lock(String key,String value){ + if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//对应setnx命令 + //可以成功设置,也就是key不存在 + return true; + } + + //判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁 + String currentValue = stringRedisTemplate.opsForValue().get(key); + //如果锁过期 + if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){//currentValue不为空且小于当前时间 + //获取上一个锁的时间value + String oldValue =stringRedisTemplate.opsForValue().getAndSet(key,value);//对应getset,如果key存在 + + //假设两个线程同时进来,key被占用了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。 + //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。 + if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) ){ + //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发 + return true; + } + } + return false; + } + + + /** + * 解锁 + * @param key + * @param value + */ + public void unlock(String key,String value){ + try { + String currentValue = stringRedisTemplate.opsForValue().get(key); + if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value) ){ + stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key + } + } catch (Exception e) { + logger.error("[Redis分布式锁] 解锁出现异常了,{}",e); + } + } + +}