| @@ -46,4 +46,5 @@ public class RedisConfig extends CachingConfigurerSupport { | |||
| return jedisPool; | |||
| } | |||
| } | |||
| @@ -30,6 +30,8 @@ public enum ErrorCode{ | |||
| LOGIN_DENIED(1014, "登录失败"), | |||
| TOKEN_INVALID(1015, "TOKEN无效"), | |||
| TOO_MANY_REQUEST(1050, "太多的请求访问"), | |||
| /** | |||
| * 业务级别 | |||
| @@ -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()); | |||
| @@ -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); | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||