Authored by tanling

优惠券

Showing 28 changed files with 1003 additions and 107 deletions
package com.yohoufo.common.cache;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
... ... @@ -8,6 +11,9 @@ import java.util.Map;
*/
public interface CacheClient {
public void mset(Map<RedisKeyBuilder, ? extends Object> map, long timeout);
public <T> Map<String, T> mget(List<RedisKeyBuilder> keys, Class<T> clazz);
/**
* 设置cache
... ... @@ -51,12 +57,15 @@ public interface CacheClient {
*/
public void delete(String key);
public void mset(Map<String, ? extends Object> map, long timeout);
public <T> void hashPut(String key, String field, T value, long timeout);
public <T> T hashGet(String key, String field, Class<T> clazz);
boolean setNx(String key, int expireInSeconds, Object o);
public void setEx(RedisKeyBuilder key, Object value, long timeout);
public <T> T get(RedisKeyBuilder key, Class<T> clazz);
}
... ...
package com.yohoufo.common.cache;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
... ... @@ -33,6 +29,41 @@ public class RedisGwCacheClient implements CacheClient {
@Redis("gwNoSyncRedis")
private YHHashOperations hashOperations;
public <T> Map<String, T> mget(List<RedisKeyBuilder> keys, Class<T> clazz) {
long beginTime = System.currentTimeMillis();
try {
List<String> multiGet = valueOperations.multiGet(keys);
int size = multiGet.size();
Map<String, T> result = new HashMap<String, T>(size);
String curItem;
for (int i = 0; i < size; i++) {
curItem = multiGet.get(i);
if (null == curItem) {
result.put(keys.get(i).getKey(), null);
LOGGER.debug("cache miss key:{}", keys.get(i));
} else {
//基本类型直接返回
if (String.class.equals(clazz)) {
result.put(keys.get(i).getKey(), (T) curItem);
} else {
result.put(keys.get(i).getKey(), JSON.parseObject(curItem, clazz));
}
LOGGER.debug("cache hit key:{}", keys.get(i));
}
}
return result;
} catch (Exception e) {
LOGGER.warn("get from cache failed!!! keys is: " + keys, e);
HashMap<String, T> hashMap = new HashMap<String, T>(keys.size());
for (RedisKeyBuilder key : keys) {
hashMap.put(key.getKey(), null);
}
return hashMap;
}
}
@Override
public void set(String key, int expireInSeconds, Object o) {
... ... @@ -49,6 +80,28 @@ public class RedisGwCacheClient implements CacheClient {
}
}
/**
* @param key
* @param value
* @param timeout 默认单位秒
*/
public void setEx(RedisKeyBuilder key, Object value, long timeout) {
String redisValue = null;
if (value instanceof String) {
redisValue = (String) value;
} else {
redisValue = JSON.toJSONString(value);
}
long beginTime = System.currentTimeMillis();
try {
valueOperations.set(key, redisValue, timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warn("add cache failed!!! cachekey:{}", key, e);
}
}
@Override
public void setAndSync(String key, int expireInSeconds, Object o) {
if (expireInSeconds > 120) {
... ... @@ -64,6 +117,36 @@ public class RedisGwCacheClient implements CacheClient {
}
}
/**
* @param key
* @param clazz
* @return
*/
public <T> T get(RedisKeyBuilder key, Class<T> clazz) {
long beginTime = System.currentTimeMillis();
T t = null;
try {
String value = valueOperations.get(key);
if (org.apache.commons.lang3.StringUtils.isBlank(value)) {
LOGGER.debug("cache miss key:{}", key);
return null;
}
if (clazz.equals(String.class)) {
t = (T) value;
} else {
t = JSON.parseObject(value, clazz);
}
LOGGER.debug("cache hit key:{}", key);
return t;
} catch (Exception e) {
LOGGER.warn("get from cache failed!!! key:{},error:{}", key, e.getMessage());
return null;
}
}
@Override
public <T> T get(String key, Class<T> clazz) {
try {
... ... @@ -113,28 +196,33 @@ public class RedisGwCacheClient implements CacheClient {
}
}
@Override
public void mset(Map<String, ? extends Object> map, long timeout) {
/**
* @param map
* @param timeout
*/
public void mset(Map<RedisKeyBuilder, ? extends Object> map, long timeout) {
if (MapUtils.isEmpty(map)) {
return;
}
Map<RedisKeyBuilder, String> cacheMap = new HashMap<RedisKeyBuilder, String>(map.size());
try{
Map<RedisKeyBuilder, String> cacheMap = new HashMap(map.size());
for (String key : map.keySet()) {
Object value = map.get(key);
RedisKeyBuilder keyBuilder = RedisKeyBuilder.newInstance().appendFixed(key);
if (value instanceof String) {
cacheMap.put(keyBuilder, (String) value);
} else if (value instanceof Integer) {
cacheMap.put(keyBuilder, value + "");
} else {
cacheMap.put(keyBuilder, JSON.toJSONString(value));
}
Set<? extends Map.Entry<RedisKeyBuilder, ?>> entries = map.entrySet();
for (Map.Entry<RedisKeyBuilder, ?> entry : entries) {
RedisKeyBuilder key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
cacheMap.put(key, (String) value);
} else if (value instanceof Integer) {
cacheMap.put(key, value + "");
} else {
cacheMap.put(key, JSON.toJSONString(value));
}
redis.mset(cacheMap, timeout);
}catch (Exception e){
LOGGER.warn("mset cache failed!!! e is: {}", e);
}
long beginTime = System.currentTimeMillis();
try {
redis.mset(cacheMap,timeout);
} catch (Exception e) {
LOGGER.warn("add cache failed!!! map is: {}", map, e);
}
}
... ...
... ... @@ -62,6 +62,19 @@ public class DateUtil {
return currentTime >= startTime.intValue() && currentTime <= endTime.intValue();
}
public static String getDateFormat(Integer time)
{
if (time == null) {
return "";
}
SimpleDateFormat sdf=new SimpleDateFormat(yyyy_MM_dd);
Calendar c=Calendar.getInstance();
c.setTimeInMillis((time.longValue()*1000));
return sdf.format(c.getTime());
}
/**
* 判断当前时间是否在 开始日期跟结束日期之间
*
... ...
... ... @@ -15,7 +15,11 @@ public interface CouponMapper {
Coupon selectByPrimaryKey(Integer id);
List<Coupon> selectByIds(@Param("ids") List<Integer> id);
Coupon selectByCouponToken(@Param("couponToken") String couponToken);
List<Coupon> selectByCouponIds(@Param("ids") List<Integer> id);
List<Coupon> selectByCouponTokens(@Param("list") List<String> couponTokens);
int updateByPrimaryKeySelective(Coupon record);
... ...
... ... @@ -14,7 +14,7 @@ public interface CouponTypeMapper {
int insertSelective(CouponType record);
CouponType selectByPrimaryKey(Byte id);
CouponType selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(CouponType record);
... ...
... ... @@ -13,10 +13,14 @@ public interface UserCouponMapper {
int insertSelective(UserCoupon record);
UserCoupon selectByUidAndToken(@Param("uid") Integer id, @Param("couponToken") String couponToken);
UserCoupon selectByPrimaryKey(Integer id);
List<UserCoupon> selectUsableCouponByUid(@Param("uid") Integer uid, @Param("now") int now);
int selectCntUsableCouponByUid(@Param("uid") Integer uid, @Param("now") int now);
int updateByPrimaryKeySelective(UserCoupon record);
int updateByPrimaryKey(UserCoupon record);
... ...
... ... @@ -11,7 +11,7 @@ public class Coupon {
private BigDecimal couponAmount;
private Byte couponType;
private Integer couponType;
private Integer couponNum;
... ... @@ -71,11 +71,11 @@ public class Coupon {
this.couponAmount = couponAmount;
}
public Byte getCouponType() {
public Integer getCouponType() {
return couponType;
}
public void setCouponType(Byte couponType) {
public void setCouponType(Integer couponType) {
this.couponType = couponType;
}
... ...
package com.yohoufo.dal.promotion.model;
import lombok.Data;
import lombok.experimental.Builder;
@Data
@Builder
public class CouponAndType {
private Coupon coupon;
private CouponType couponType;
}
... ...
... ... @@ -5,6 +5,17 @@ public class CouponType {
private String caption;
private String alphabet;
public String getAlphabet() {
return alphabet;
}
public void setAlphabet(String alphabet) {
this.alphabet = alphabet;
}
public Integer getId() {
return id;
}
... ...
... ... @@ -7,7 +7,7 @@ public class UserCoupon {
private Integer couponId;
private Byte couponType;
private Integer couponType;
private String couponCode;
... ... @@ -23,6 +23,16 @@ public class UserCoupon {
private Integer createTime;
private String couponToken;
public String getCouponToken() {
return couponToken;
}
public void setCouponToken(String couponToken) {
this.couponToken = couponToken;
}
public Integer getId() {
return id;
}
... ... @@ -47,11 +57,11 @@ public class UserCoupon {
this.couponId = couponId;
}
public Byte getCouponType() {
public Integer getCouponType() {
return couponType;
}
public void setCouponType(Byte couponType) {
public void setCouponType(Integer couponType) {
this.couponType = couponType;
}
... ...
... ... @@ -6,7 +6,7 @@
<result column="coupon_token" jdbcType="VARCHAR" property="couponToken" />
<result column="coupon_name" jdbcType="VARCHAR" property="couponName" />
<result column="coupon_amount" jdbcType="DECIMAL" property="couponAmount" />
<result column="coupon_type" jdbcType="TINYINT" property="couponType" />
<result column="coupon_type" jdbcType="INTEGER" property="couponType" />
<result column="coupon_num" jdbcType="INTEGER" property="couponNum" />
<result column="use_num" jdbcType="SMALLINT" property="useNum" />
<result column="send_num" jdbcType="INTEGER" property="sendNum" />
... ... @@ -33,7 +33,15 @@
where id = #{id,jdbcType=INTEGER}
</select>
<select id="selectByIds" parameterType="java.lang.Integer" resultMap="BaseResultMap">
<select id="selectByCouponToken" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from coupon
where coupon_token = #{couponToken,jdbcType=VARCHAR}
</select>
<select id="selectByCouponIds" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from coupon
... ... @@ -45,6 +53,18 @@
</foreach>
</select>
<select id="selectByCouponTokens" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from coupon
where 1=1
AND coupon_token IN
<foreach item="token" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from coupon
where id = #{id,jdbcType=INTEGER}
... ...
... ... @@ -4,9 +4,10 @@
<resultMap id="BaseResultMap" type="com.yohoufo.dal.promotion.model.CouponType" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="caption" property="caption" jdbcType="VARCHAR" />
<result column="alphabet" property="alphabet" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, caption
id, caption, alphabet
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
... ...
... ... @@ -5,7 +5,8 @@
<id column="id" property="id" jdbcType="INTEGER" />
<result column="uid" property="uid" jdbcType="INTEGER" />
<result column="coupon_id" property="couponId" jdbcType="INTEGER" />
<result column="coupon_type" property="couponType" jdbcType="TINYINT" />
<result column="coupon_token" property="couponToken" jdbcType="VARCHAR" />
<result column="coupon_type" property="couponType" jdbcType="INTEGER" />
<result column="coupon_code" property="couponCode" jdbcType="VARCHAR" />
<result column="status" property="status" jdbcType="TINYINT" />
<result column="order_code" property="orderCode" jdbcType="BIGINT" />
... ... @@ -16,7 +17,7 @@
</resultMap>
<sql id="Base_Column_List" >
id, uid, coupon_id, coupon_type, coupon_code, status, order_code, use_time, start_time,
end_time, create_time
end_time, create_time, coupon_token
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
... ... @@ -27,16 +28,39 @@
</select>
<select id="selectByUidAndToken" resultMap="BaseResultMap" >
select
<include refid="Base_Column_List" />
from user_coupon
where uid = #{uid,jdbcType=INTEGER}
and coupon_token = #{couponToken,jdbcType=VARCHAR}
limit 1
</select>
<select id="selectUsableCouponByUid" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from user_coupon
where uid = #{uid,jdbcType=INTEGER}
<include refid="CouponsLogsQueryUsable" />
</select>
<sql id="CouponsLogsQueryUsable" >
<!-- 在有效时间范围内 -->
and <![CDATA[ start_time > #{now, jdbcType=INTEGER} ]]>
and <![CDATA[ end_time < #{now, jdbcType=INTEGER} ]]>
<!-- 未使用-->
and status = 0
</sql>
<select id="selectCntUsableCouponByUid" resultType="int" parameterType="java.lang.Integer" >
select
count(1)
from user_coupon
where uid = #{uid,jdbcType=INTEGER}
<include refid="CouponsLogsQueryUsable"/>
</select>
... ... @@ -57,7 +81,7 @@
order_code, use_time, start_time,
end_time, create_time)
values (#{id,jdbcType=INTEGER}, #{uid,jdbcType=INTEGER}, #{couponId,jdbcType=INTEGER},
#{couponType,jdbcType=TINYINT}, #{couponCode,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT},
#{couponType,jdbcType=INTEGER}, #{couponCode,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT},
#{orderCode,jdbcType=BIGINT}, #{useTime,jdbcType=INTEGER}, #{startTime,jdbcType=INTEGER},
#{endTime,jdbcType=INTEGER}, #{createTime,jdbcType=INTEGER})
</insert>
... ...
package com.yohoufo.promotion.cache;
public interface ExpiredTime {
/**
* coupons ---缓存时间
*/
int COUPON_CACHE_TIME = 300;
/**
* coupons_type ---缓存时间
*/
int COUPON_TYPE_CACHE_TIME = 3600;
}
... ...
package com.yohoufo.promotion.cache;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
public class KeyBuilder {
public static RedisKeyBuilder buildCouponCacheKey(String couponToken) {
return RedisKeyBuilder.newInstance().appendFixed("ufo:promotion:coupons:couponToken:").appendVar(couponToken);
}
public static RedisKeyBuilder buildCouponTypeCacheKey(int id) {
return RedisKeyBuilder.newInstance().appendFixed("ufo:promotion:coupontype:id:").appendVar(id);
}
/**
* 服务节点 (此key不可以修改为非同步缓存)
*/
public static RedisKeyBuilder promotionServerNode() {
return RedisKeyBuilder.newInstance().appendFixed("ufo:promotion:coupons:serverNode");
}
}
... ...
package com.yohoufo.promotion.common;
/**
* 描述:券状态
* Created by pangjie@yoho.cn on 2016/5/24.
*/
public enum CouponsStatusEnum {
//ERP中老的状态为: 0:未启用 1:启用 3:作废
/**
待审核:通过、驳回、查看详情
通过:查看详情、作废
驳回:修改
作废:查看详情
*/
/**
* 新建之后是待审核
*/
NEED_REVIEW(0, "待审核"),
/**
* 审核通过之后是有效的(对应原来的启用状态)
*/
VALID(1, "有效"),
/**
* 审核通过之后可作废
*/
INVALID(3, "作废"),
/**
* 审批未通过的是驳回状态,驳回状态可编辑,编辑之后变成待审核
*/
REJECT(2, "审核驳回");
/**
* 编码
*/
private int code;
/**
* 描述
*/
private String desc;
/**
* 操作列表
*
* @param code
* @param desc
* @return
*/
CouponsStatusEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
... ...
package com.yohoufo.promotion.common;
public enum ProductLimitType {
PRODUCT(1, "特定商品", "限特定商品");
int code ;
String desc;
/**
* 显示给前端的文案
*/
String text;
ProductLimitType(int code, String desc, String text) {
this.code = code;
this.desc = desc;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}
\ No newline at end of file
... ...
package com.yohoufo.promotion.common;
public enum UseLimitType {
NO_LIMIT(0, "无限制", ""),
AMOUNT_LIMIT(1, "金额", "满{0}可用");
int code ;
String desc;
/**
* 给前端的显示文案
*/
String text;
UseLimitType(int code, String desc, String text) {
this.code = code;
this.desc = desc;
this.text = text;
}
public String getText() {
return text;
}
public int getCode() {
return code;
}
}
... ...
package com.yohoufo.promotion.controller;
import com.yohoufo.common.ApiResponse;
import com.yohoufo.promotion.model.response.CouponInfo;
import com.yohoufo.promotion.model.response.CouponInfoListBO;
import com.yohoufo.promotion.service.ICouponService;
import org.slf4j.Logger;
... ... @@ -11,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping(value = "/coupon")
public class CouponController {
... ... @@ -29,9 +32,9 @@ public class CouponController {
@ResponseBody
public ApiResponse queryCouponList(@RequestParam(value = "uid") Integer uid) {
logger.info("query user coupons, uid: {}", uid);
CouponInfoListBO couponInfoListBo = couponService.queryCouponList(uid);
logger.info("query user coupons uid: {}, response: {}", uid, couponInfoListBo);
logger.info("query user coupons list, uid: {}", uid);
List<CouponInfo> couponInfoListBo = couponService.queryCouponList(uid);
logger.info("query user coupons list uid: {}, response: {}", uid, couponInfoListBo);
return new ApiResponse.ApiResponseBuilder().code(200).data(couponInfoListBo).build();
}
... ... @@ -45,9 +48,26 @@ public class CouponController {
@ResponseBody
public ApiResponse queryCouponCnt(@RequestParam(value = "uid") Integer uid) {
logger.info("query user coupons, uid: {}", uid);
logger.info("query user coupons cnt, uid: {}", uid);
int couponCnt = couponService.queryCouponCnt(uid);
logger.info("query user coupons uid: {}, response: {}", uid, couponCnt);
logger.info("query user coupons cnt uid: {}, v: {}", uid, couponCnt);
return new ApiResponse.ApiResponseBuilder().code(200).data(couponCnt).build();
}
/**
* 领券接口
* @param uid
* @return
*/
@RequestMapping(params = "method=app.coupons.send")
@ResponseBody
public ApiResponse sendCoupon(@RequestParam(value = "uid") Integer uid,
@RequestParam(value = "coupon_token") String couponToken) {
logger.info("send coupons enter, uid: {}", uid);
String couponCode = couponService.senCoupon(uid, couponToken);
logger.info("send coupons success uid: {}, couponCode: {}", uid, couponCode);
return new ApiResponse.ApiResponseBuilder().code(200).data(couponCode).build();
}
}
... ...
package com.yohoufo.promotion.convert;
import com.yohoufo.common.utils.DateUtil;
import com.yohoufo.dal.promotion.model.Coupon;
import com.yohoufo.dal.promotion.model.CouponAndType;
import com.yohoufo.dal.promotion.model.CouponType;
import com.yohoufo.dal.promotion.model.UserCoupon;
import com.yohoufo.promotion.common.ProductLimitType;
import com.yohoufo.promotion.common.UseLimitType;
import com.yohoufo.promotion.model.response.CouponInfo;
import java.text.MessageFormat;
import java.util.Arrays;
public class CouponConvert {
/**
* 用户优惠券信息
* @param userCoupon 用户优惠券
* @param coupon 优惠券
* @param couponType 优惠券类型
* @return
*/
public static CouponInfo convertCouponInfo(UserCoupon userCoupon, Coupon coupon, CouponType couponType) {
CouponInfo couponInfo = new CouponInfo();
couponInfo.setCoupon_code(userCoupon.getCouponCode());
StringBuilder coupon_validity = new StringBuilder();
coupon_validity.append(DateUtil.getDateFormat(userCoupon.getStartTime()));
coupon_validity.append("-");
coupon_validity.append(DateUtil.getDateFormat(userCoupon.getEndTime()));
couponInfo.setCoupon_validity(coupon_validity.toString());
// 优惠券信息
if (coupon != null){
couponInfo.setCoupon_name(coupon.getCouponName());
couponInfo.setCoupon_token(coupon.getCouponToken());
couponInfo.setCoupon_value(coupon.getCouponAmount());
// use_limit_type use_limit_value 满多少元可用
if (coupon.getUseLimitType()!=null && coupon.getUseLimitType().intValue() == UseLimitType.AMOUNT_LIMIT.getCode()){
couponInfo.setUse_rule(MessageFormat.format(UseLimitType.AMOUNT_LIMIT.getText(),coupon.getUseLimitValue().intValue()));
}
// product_limit_type product_limit_value 特定商品
if (coupon.getProductLimitType()!=null && coupon.getProductLimitType().intValue() == ProductLimitType.PRODUCT.getCode()){
couponInfo.setNotes(Arrays.asList(ProductLimitType.PRODUCT.getText()));
}
}
// 优惠券类型信息
if (couponType != null){
couponInfo.setCoupon_type(couponType.getId());
couponInfo.setCoupon_type_name(couponType.getCaption());
}
return couponInfo;
}
/**
* 领取优惠券时构造信息
* @param uid
* @param couponAndType
* @param couponCode
* @return
*/
public static UserCoupon convertUserCoupon(Integer uid,String couponCode, CouponAndType couponAndType) {
UserCoupon userCoupon = new UserCoupon();
userCoupon.setUid(uid);
userCoupon.setCouponId(couponAndType.getCoupon().getId());
userCoupon.setCouponType(couponAndType.getCouponType().getId());
userCoupon.setCouponCode(couponCode);
int now = DateUtil.getCurrentTimeSecond();
userCoupon.setStartTime(now);
// TODO
userCoupon.setEndTime(couponAndType.getCoupon().getEndTime());
userCoupon.setCreateTime(now);
userCoupon.setCouponToken(couponAndType.getCoupon().getCouponToken());
return userCoupon;
}
}
... ...
... ... @@ -16,8 +16,6 @@ public class CouponInfo {
private BigDecimal coupon_value;
private String coupon_value_str;
private String use_rule;
private String coupon_validity;
... ...
package com.yohoufo.promotion.service;
/**
* 描述:券号生成服务
* Created by pangjie@yoho.cn on 2016/5/14.
*/
public interface CouponCodeGenerate {
/**
* 获取券号
*
* @return
*/
String getCode();
}
... ...
package com.yohoufo.promotion.service;
import com.google.common.collect.Lists;
import com.yohoufo.common.utils.DateUtil;
import com.yohoufo.dal.promotion.CouponMapper;
import com.yohoufo.dal.promotion.CouponTypeMapper;
import com.yohoufo.dal.promotion.UserCouponMapper;
import com.yohoufo.dal.promotion.model.Coupon;
import com.yohoufo.dal.promotion.model.CouponType;
import com.yohoufo.dal.promotion.model.UserCoupon;
import com.yohoufo.promotion.model.response.CouponInfoListBO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class CouponServiceImpl implements ICouponService{
@Autowired
UserCouponMapper userCouponMapper;
@Autowired
CouponMapper couponMapper;
@Autowired
CouponTypeMapper couponTypeMapper;
@Override
public CouponInfoListBO queryCouponList(Integer uid) {
// 获取未使用未过期的优惠券
List<UserCoupon> list = userCouponMapper.selectUsableCouponByUid(uid, DateUtil.getCurrentTimeSecond());
if (CollectionUtils.isEmpty(list)){
return CouponInfoListBO.builder().build();
}
// 获取 coupon
List<Integer> couponIdList = list.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
List<Coupon> couponList = couponMapper.selectByIds(couponIdList);
Map<Integer, Coupon> couponMap = couponList.stream().collect(Collectors.toMap(Coupon::getId, Function.identity()));
// 获取 couponTyoe
List<Integer> couponTypeIdList = list.stream().map(UserCoupon::getCouponType).map(Byte::intValue).collect(Collectors.toList());
List<CouponType> couponTypeList = couponTypeMapper.selectByIds(couponTypeIdList);
Map<Integer, CouponType> couponTypeMap = couponTypeList.stream().collect(Collectors.toMap(CouponType::getId, Function.identity()));
return null;
}
@Override
public int queryCouponCnt(Integer uid) {
return 0;
}
}
package com.yohoufo.promotion.service;
import com.yohoufo.dal.promotion.model.Coupon;
import com.yohoufo.dal.promotion.model.CouponAndType;
import com.yohoufo.dal.promotion.model.CouponType;
import java.util.List;
public interface ICouponCacheService {
public CouponType getCouponTypeWithCache(Integer id);
public List<Coupon> getCouponsWithCache(List<String> couponTokenList);
/**
* 根据couponToken获取优惠券模板和类型
* @param couponToken
* @return
*/
public CouponAndType getCouponAndType(String couponToken);
}
... ...
package com.yohoufo.promotion.service;
import com.yohoufo.promotion.model.response.CouponInfo;
import com.yohoufo.promotion.model.response.CouponInfoListBO;
import java.util.List;
public interface ICouponService {
public CouponInfoListBO queryCouponList(Integer uid);
public List<CouponInfo> queryCouponList(Integer uid);
public int queryCouponCnt(Integer uid);
/**
* 领取优惠券
* @param uid
* @param couponToken
* @return
*/
public String senCoupon(Integer uid, String couponToken);
}
... ...
package com.yohoufo.promotion.service.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import com.yohoufo.common.cache.CacheClient;
import com.yohoufo.dal.promotion.CouponMapper;
import com.yohoufo.dal.promotion.CouponTypeMapper;
import com.yohoufo.dal.promotion.model.Coupon;
import com.yohoufo.dal.promotion.model.CouponAndType;
import com.yohoufo.dal.promotion.model.CouponType;
import com.yohoufo.promotion.cache.ExpiredTime;
import com.yohoufo.promotion.cache.KeyBuilder;
import com.yohoufo.promotion.service.ICouponCacheService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class CouponCacheServiceImpl implements ICouponCacheService {
@Autowired
CouponMapper couponMapper;
@Autowired
CouponTypeMapper couponTypeMapper;
@Autowired
private CacheClient cacheClient;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 获取couponType
* @param id
* @return
*/
public CouponType getCouponTypeWithCache(Integer id){
if (id == null){
return null;
}
CouponType couponType = cacheClient.get(KeyBuilder.buildCouponTypeCacheKey(id), CouponType.class);
if (couponType == null) {
couponType = couponTypeMapper.selectByPrimaryKey(id);
if (couponType != null) {
cacheClient.setEx(KeyBuilder.buildCouponTypeCacheKey(id), couponType, ExpiredTime.COUPON_TYPE_CACHE_TIME);
}
}
return couponType;
}
/**
* 根据couponToken获取优惠券模板和类型
* @param couponToken
* @return
*/
public CouponAndType getCouponAndType(String couponToken){
Coupon coupon = getCouponWithCache(couponToken);
if (coupon == null){
logger.warn("getCouponAndType coupon not exist. couponToken is {}", couponToken);
return null;
}
CouponType couponType = getCouponTypeWithCache(coupon.getCouponType());
if (couponType == null){
logger.warn("getCouponAndType coupon not exist. couponToken is {}, couponType is {}", couponToken, coupon.getCouponType());
return null;
}
return CouponAndType.builder().coupon(coupon).couponType(couponType).build();
}
/**
* 获取coupon
* @param couponToken
* @return
*/
public Coupon getCouponWithCache(String couponToken){
Coupon coupon = cacheClient.get(KeyBuilder.buildCouponCacheKey(couponToken), Coupon.class);
if (coupon != null){
return coupon;
}
coupon = couponMapper.selectByCouponToken(couponToken);
if (coupon == null) {
return null;
}
cacheClient.setEx(KeyBuilder.buildCouponCacheKey(couponToken), coupon, ExpiredTime.COUPON_CACHE_TIME);
return coupon;
}
/**
* 批量获取coupon
* @param couponTokenList
* @return
*/
public List<Coupon> getCouponsWithCache(List<String> couponTokenList) {
//构造缓存key对象
List<RedisKeyBuilder> coupCacheKeys = couponTokenList.stream().map(KeyBuilder::buildCouponCacheKey).collect(Collectors.toList());
Map<String, Coupon> couponsMap = cacheClient.mget(coupCacheKeys, Coupon.class);
if (MapUtils.isEmpty(couponsMap)) {
logger.warn("query coupon rule info is empty:{}", couponTokenList);
return Lists.newArrayList();
}
List<String> missCouponsTokens = Lists.newArrayList();
List<Coupon> couponses = Lists.newArrayList();
for (Map.Entry<String, Coupon> entry : couponsMap.entrySet()) {
if (entry.getValue() == null) {
String key = entry.getKey();
missCouponsTokens.add(key.substring(key.lastIndexOf(":") + 1));
} else {
couponses.add(entry.getValue());
}
}
if (CollectionUtils.isEmpty(missCouponsTokens)) {
return couponses;
}
List<Coupon> dbCouponses = couponMapper.selectByCouponTokens(missCouponsTokens);
if (CollectionUtils.isEmpty(dbCouponses)) {
return couponses;
}
for (Coupon coupons : dbCouponses) {
couponses.add(coupons);
}
setCoupons2Cache(dbCouponses);
return couponses;
}
/**
* 设置券缓存
*
* @param couponses
*/
private void setCoupons2Cache(List<Coupon> couponses) {
if (CollectionUtils.isEmpty(couponses)) {
return;
}
//设置缓存异常不影响正常流程
try {
Map<RedisKeyBuilder, Coupon> map = Maps.newHashMap();
couponses.forEach(c -> map.put(KeyBuilder.buildCouponCacheKey(c.getCouponToken()), c));
cacheClient.mset(map, ExpiredTime.COUPON_CACHE_TIME);
} catch (Exception e) {
logger.error("set couponses cache error:{}", e.getMessage());
}
}
}
... ...
package com.yohoufo.promotion.service.impl;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.yoho.core.common.utils.LocalIp;
import com.yoho.core.redis.cluster.annotation.Redis;
import com.yoho.core.redis.cluster.operations.nosync.YHValueOperations;
import com.yohoufo.promotion.cache.KeyBuilder;
import com.yohoufo.promotion.service.CouponCodeGenerate;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.security.SecureRandom;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 描述:券号生成服务
* Created by pangjie@yoho.cn on 2016/5/16.
*/
@Service("couponCodeGenerate")
@Lazy(false)
public class CouponCodeGenerateImpl implements CouponCodeGenerate {
private static final Logger logger = LoggerFactory.getLogger(CouponCodeGenerateImpl.class);
private static final List<String> NODES = Lists.newArrayList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");
private static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static final AtomicInteger NEXT_COUNTER = new AtomicInteger((new SecureRandom()).nextInt());
private static final int RADIX_36=36;
@Redis("yohoNoSyncRedis")
private YHValueOperations valueOperations;
/**
* 标示机器节点
*/
private String node;
public static String toHexString(byte[] bytes) {
char[] chars = new char[bytes.length * 2];
int i = 0;
int var4 = bytes.length;
for (int var5 = 0; var5 < var4; ++var5) {
byte b = bytes[var5];
chars[i++] = HEX_CHARS[b >> 4 & 15];
chars[i++] = HEX_CHARS[b & 15];
}
return new String(chars);
}
private static int dateToTimestampSeconds(Date time) {
return (int) (time.getTime() / 1000L);
}
/**
* 初始化标示节点,用redis的一个自增值+ip地址
*/
@PostConstruct
public void init() {
while (true) {
try {
Long test = valueOperations.increment(KeyBuilder.promotionServerNode(), 1);
long position = test % NODES.size();
String localIp = LocalIp.getLocalIp();
String ipLast = localIp.substring(localIp.lastIndexOf(".")+1);
node = NODES.get((int) position)+ Strings.padStart(Integer.toString(NumberUtils.toInt(ipLast), RADIX_36), 2, '0');
return;
} catch (Exception e) {
logger.error("acquire node exception:{}", e);
}
}
}
@Override
public String getCode() {
int timestamp = dateToTimestampSeconds(new Date());
int counter = NEXT_COUNTER.getAndIncrement() & 1048575;
//时间戳(当前时间到2038-12-24都是6位)+节点+自增(1048575,36进制为4位转换为定长4位)
return Integer.toString(timestamp,RADIX_36) + node + Strings.padStart(Integer.toString(counter, RADIX_36), 4, '0');
}
}
... ...
package com.yohoufo.promotion.service.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import com.yoho.error.ServiceError;
import com.yoho.error.exception.ServiceException;
import com.yohoufo.common.cache.CacheClient;
import com.yohoufo.common.utils.DateUtil;
import com.yohoufo.dal.promotion.CouponMapper;
import com.yohoufo.dal.promotion.CouponTypeMapper;
import com.yohoufo.dal.promotion.UserCouponMapper;
import com.yohoufo.dal.promotion.model.Coupon;
import com.yohoufo.dal.promotion.model.CouponAndType;
import com.yohoufo.dal.promotion.model.CouponType;
import com.yohoufo.dal.promotion.model.UserCoupon;
import com.yohoufo.promotion.cache.ExpiredTime;
import com.yohoufo.promotion.cache.KeyBuilder;
import com.yohoufo.promotion.common.CouponsStatusEnum;
import com.yohoufo.promotion.common.ProductLimitType;
import com.yohoufo.promotion.common.UseLimitType;
import com.yohoufo.promotion.convert.CouponConvert;
import com.yohoufo.promotion.model.response.CouponInfo;
import com.yohoufo.promotion.model.response.CouponInfoListBO;
import com.yohoufo.promotion.service.CouponCodeGenerate;
import com.yohoufo.promotion.service.ICouponCacheService;
import com.yohoufo.promotion.service.ICouponService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class CouponServiceImpl implements ICouponService {
@Autowired
UserCouponMapper userCouponMapper;
@Autowired
ICouponCacheService couponCacheService;
@Autowired
CouponCodeGenerate couponCodeGenerate;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 领取优惠券
* @param uid
* @param couponToken
* @return
*/
public String senCoupon(Integer uid, String couponToken){
// 根据 couponToken 获取coupon&couponType信息
CouponAndType couponAndType = couponCacheService.getCouponAndType(couponToken);
if (couponAndType == null){
logger.warn("query coupon info,coupontype is null:{}", couponToken);
throw new ServiceException(ServiceError.PROMOTION_COUPON_HAS_NOT_EXISTS);
}
// 校验是否可以领取(重复领取,时间是否合法,状态)
checkCanAcquire(uid, couponToken, couponAndType);
// 校验 优惠券发放总数
String couponCode = couponAndType.getCouponType().getAlphabet() + couponCodeGenerate.getCode();
// 发送优惠券
UserCoupon userCoupon = CouponConvert.convertUserCoupon(uid, couponCode, couponAndType);
int count = userCouponMapper.insert(userCoupon);
// 插入失败
if (count != 1){
logger.info("senCoupon failed,{},{},{}", uid, couponToken, couponCode);
return null;
}
logger.info("senCoupon success,{},{},{}", uid, couponToken, couponCode);
// TODO 清理缓存
return couponCode;
}
private void checkCanAcquire(Integer uid, String couponToken, CouponAndType couponAndType) {
// 验证该用户是否重复领取
UserCoupon userCoupon = userCouponMapper.selectByUidAndToken(uid, couponToken);
if (userCoupon != null){
logger.warn("user has acquried this coupon before:{},{}", uid, couponToken);
throw new ServiceException(ServiceError.PROMOTION_COUPON_HAS_RECEIVED);
}
// 验证优惠券的领取时间(开始时间没到,结束时间已过,状态不对)
int now = DateUtil.getCurrentTimeSecond();
if (couponAndType.getCoupon().getStartTime() > now){
logger.info("couponrulebo not arrive get start time:{},{}", uid, couponToken);
throw new ServiceException(ServiceError.PROMOTION_COUPON_NOT_ARRIVE_GET_START_TIME);
}
if (couponAndType.getCoupon().getEndTime() > now){
logger.info("couponrulebo is expired:{},{}", uid, couponToken);
throw new ServiceException(ServiceError.PROMOTION_COUPON_TIME_OUT_CAN_NOT_GET);
}
if (couponAndType.getCoupon().getStatus() != CouponsStatusEnum.VALID.getCode()){
logger.info("couponrulebo status is not valid:{},{}",uid, couponToken);
throw new ServiceException(ServiceError.PROMOTION_COUPON_IS_NOT_VAILD);
}
}
@Override
public List<CouponInfo> queryCouponList(Integer uid) {
// 获取未使用未过期的优惠券
List<UserCoupon> list = userCouponMapper.selectUsableCouponByUid(uid, DateUtil.getCurrentTimeSecond());
if (CollectionUtils.isEmpty(list)){
return Lists.newArrayList();
}
// 批量获取coupon
List<String> couponIdList = list.stream().map(UserCoupon::getCouponToken).collect(Collectors.toList());
List<Coupon> couponList = couponCacheService.getCouponsWithCache(couponIdList);
Map<Integer, Coupon> couponMap = couponList.stream().collect(Collectors.toMap(Coupon::getId, Function.identity()));
List<CouponInfo> couponInfoList = list.stream().map(userCoupon -> {
Coupon coupon = couponMap.get(userCoupon.getCouponId());
// 单个获取couponType
CouponType couponType = couponCacheService.getCouponTypeWithCache(userCoupon.getCouponType()!=null ? userCoupon.getCouponType().intValue() : null);
CouponInfo couponInfo = CouponConvert.convertCouponInfo(userCoupon, coupon, couponType);
return couponInfo;
}).collect(Collectors.toList());
return couponInfoList;
}
@Override
public int queryCouponCnt(Integer uid) {
int cnt = userCouponMapper.selectCntUsableCouponByUid(uid, DateUtil.getCurrentTimeSecond());
return cnt;
}
}
... ...