Просмотр исходного кода

Merge branch 'exchange_code'

# Conflicts:
#	netflix-common/pom.xml
#	netflix-dao/src/main/java/com/cyksj/model/entity/ExchangeCode.java
#	netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java
#	netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java
#	netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
zoujiajian 3 лет назад
Родитель
Сommit
9cba4b7a0a
18 измененных файлов с 806 добавлено и 48 удалено
  1. 11 0
      netflix-common/pom.xml
  2. 12 0
      netflix-common/src/main/java/com/cyksj/common/annotation/NoSubmit.java
  3. 32 0
      netflix-common/src/main/java/com/cyksj/common/annotation/UrlCache.java
  4. 15 0
      netflix-common/src/main/java/com/cyksj/common/util/StringUtil.java
  5. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/exchange/ExchangeCodeMapper.java
  6. 10 16
      netflix-dao/src/main/java/com/cyksj/model/entity/ExchangeCode.java
  7. 1 0
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java
  8. 90 0
      netflix-dao/src/main/java/com/cyksj/model/excel/ExcelExchangeCodeData.java
  9. 29 0
      netflix-dao/src/main/java/com/cyksj/model/request/ExchangeCodeReq.java
  10. 19 0
      netflix-dao/src/main/java/com/cyksj/model/request/ExchangeCodeTicket.java
  11. 72 0
      netflix-dao/src/main/java/com/cyksj/model/views/ExchangeCodeView.java
  12. 28 0
      netflix-service/src/main/java/com/cyksj/service/exchange/ExchangeCodeService.java
  13. 313 0
      netflix-service/src/main/java/com/cyksj/service/exchange/impl/ExchangeCodeServiceImpl.java
  14. 8 0
      netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java
  15. 24 27
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  16. 48 0
      netflix-web/src/main/java/com/cyksj/web/aop/NoSubmitAop.java
  17. 68 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/exchange/ExchangeCodeController.java
  18. 13 5
      netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

+ 11 - 0
netflix-common/pom.xml

@@ -81,6 +81,17 @@
             <artifactId>alipay-sdk-java</artifactId>
             <version>4.34.22.ALL</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>28.2-jre</version>
+        </dependency>
     </dependencies>
 
 

+ 12 - 0
netflix-common/src/main/java/com/cyksj/common/annotation/NoSubmit.java

@@ -0,0 +1,12 @@
+package com.cyksj.common.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD) // 作用到方法上
+@Retention(RetentionPolicy.RUNTIME) // 运行时有效
+public @interface NoSubmit {
+}

+ 32 - 0
netflix-common/src/main/java/com/cyksj/common/annotation/UrlCache.java

@@ -0,0 +1,32 @@
+package com.cyksj.common.annotation;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zwhui
+ * @date 2020/7/16 上午11:07
+ */
+
+@Configuration
+public class UrlCache {
+    @Bean
+    public Cache<String, Integer> getCache() {
+        return CacheBuilder.newBuilder().expireAfterWrite(2L, TimeUnit.SECONDS).build();// 缓存有效期为2秒
+    }
+
+    @Bean("recordCache")
+    public Cache<String, String> recordCache() {
+        return CacheBuilder.newBuilder().expireAfterWrite(30L, TimeUnit.SECONDS).build();// 缓存有效期为30秒
+    }
+
+    @Bean("countCache")
+    public Cache<String, String> countCache() {
+        return CacheBuilder.newBuilder().expireAfterWrite(600L, TimeUnit.SECONDS).build();// 缓存有效期为600秒
+    }
+
+}

+ 15 - 0
netflix-common/src/main/java/com/cyksj/common/util/StringUtil.java

@@ -1,5 +1,7 @@
 package com.cyksj.common.util;
 
+import cn.hutool.core.util.RandomUtil;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -33,6 +35,8 @@ public final class StringUtil {
 
 	public static final String EMPTY = "";
 
+	public static final String randomStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#&/_-";
+
 	/**
 	 * provate constructor
 	 */
@@ -393,4 +397,15 @@ public final class StringUtil {
 			return "ERROR!";
 		}
 	}
+
+	/**
+	 * 随即获取length随机字符串
+	 */
+	public static void getRandomStr(StringBuilder sb, Integer length) {
+		Integer baseLength = randomStr.length();
+		for (int i = 0; i < length; i++) {
+			int number = RandomUtil.randomInt(baseLength);
+			sb.append(randomStr.charAt(number));
+		}
+	}
 }

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/exchange/ExchangeCodeMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.manage.exchange;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.ExchangeCode;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/7 16:33
+ */
+public interface ExchangeCodeMapper extends BaseMapper<ExchangeCode> {
+}

+ 10 - 16
netflix-dao/src/main/java/com/cyksj/model/entity/ExchangeCode.java

@@ -3,18 +3,17 @@ package com.cyksj.model.entity;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.util.Date;
+
 /*
  *项目名: netflix
  *文件名: ExchangeCode
  *创建者: JavaZou
- *创建时间:2022/11/7 14:46
+ *创建时间:2022/11/7 15:43
  */
 @Getter
 @Setter
-public class ExchangeCode extends BaseEntity {
-	/**
-	 * 兑换码
-	 */
+public class ExchangeCode extends BaseEntity{
 	private String code;
 
 	/**
@@ -28,22 +27,17 @@ public class ExchangeCode extends BaseEntity {
 	private Long skuId;
 
 	/**
-	 * 导出状态
-	 */
-	private Boolean exportStatus;
-
-	/**
-	 * 使用人id
+	 * 使用状态
 	 */
-	private Long userId;
+	private Boolean useStatus;
 
 	/**
-	 * 兑换时间
+	 * 使用时间
 	 */
-	private String useTime;
+	private Date useTime;
 
 	/**
-	 * 使用状态
+	 * 使用人id
 	 */
-	private Boolean useStatus;
+	private Long userId;
 }

+ 1 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java

@@ -93,6 +93,7 @@ public class OrderDon extends BaseEntity implements Serializable {
     public enum Type{
         WeChat("wechat","微信"),
         ALI_PAY("zfb","支付宝"),
+        EX_CODE("exchange_code","兑换码")
         ;
         String type;
         String desc;

+ 90 - 0
netflix-dao/src/main/java/com/cyksj/model/excel/ExcelExchangeCodeData.java

@@ -0,0 +1,90 @@
+package com.cyksj.model.excel;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: ExcelExchangeCodeData
+ *创建者: JavaZou
+ *创建时间:2022/11/7 16:44
+ */
+@Getter
+@Setter
+@SearchBean(tables = "exchange_code ec left join user u on u.id = ec.user_id" +
+		" left join goods_don gd on ec.goods_id = gd.id" +
+		" left join goods_don_sku gdk on gdk.id = ec.sku_id")
+public class ExcelExchangeCodeData {
+
+	/**
+	 * 生成日期
+	 */
+	@ExcelProperty("生成日期")
+	@DbField("ec.created_time")
+	private Date createdTime;
+
+	@ExcelProperty("兑换码")
+	@DbField("ec.code")
+	private String code;
+
+	@ExcelIgnore
+	@DbField("ec.goods_id")
+	private Long goodsId;
+
+	/**
+	 * 平台名称
+	 */
+	@ExcelProperty("平台")
+	@DbField("gd.title")
+	private String title;
+
+	/**
+	 * 规格名称,金额
+	 */
+	@ExcelProperty("规格名称")
+	@DbField("concat(gdk.spec_val,'',cast(gdk.price/100 as decimal(10,2)))")
+	private String specVal;
+
+	/**
+	 * 使用状态str
+	 */
+	@ExcelProperty("使用状态")
+	@DbField("if(ec.use_status,'已使用','未使用')")
+	@TableField(exist = false)
+	private String useStatusStr;
+
+	/**
+	 * 使用状态
+	 */
+	@DbField("ec.use_status")
+	@ExcelIgnore
+	private Boolean useStatus;
+
+	/**
+	 * 使用时间
+	 */
+	@ExcelProperty("使用时间")
+	@DbField("ec.use_time")
+	private Date useTime;
+
+	/**
+	 * 使用用户
+	 */
+	@ExcelProperty("使用用户")
+	@DbField("u.nickname")
+	private String nickName;
+
+	/**
+	 * 用户id
+	 */
+	@ExcelProperty("用户id")
+	@DbField("u.id")
+	private Long userId;
+}

+ 29 - 0
netflix-dao/src/main/java/com/cyksj/model/request/ExchangeCodeReq.java

@@ -0,0 +1,29 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeReq
+ *创建者: JavaZou
+ *创建时间:2022/11/7 15:26
+ */
+@Getter
+@Setter
+public class ExchangeCodeReq {
+	/**
+	 * 平台id
+	 */
+	private Long goodsId;
+
+	/**
+	 * 规格id
+	 */
+	private Long skuId;
+
+	/**
+	 * 数量
+	 */
+	private Integer number;
+}

+ 19 - 0
netflix-dao/src/main/java/com/cyksj/model/request/ExchangeCodeTicket.java

@@ -0,0 +1,19 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeTicket
+ *创建者: JavaZou
+ *创建时间:2022/11/8 13:43
+ */
+@Getter
+@Setter
+public class ExchangeCodeTicket {
+	/**
+	 * 兑换码
+	 */
+	private String code;
+}

+ 72 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ExchangeCodeView.java

@@ -0,0 +1,72 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeView
+ *创建者: JavaZou
+ *创建时间:2022/11/7 16:25
+ */
+@Getter
+@Setter
+@SearchBean(tables = "exchange_code ec left join user u on u.id = ec.user_id" +
+		" left join goods_don gd on ec.goods_id = gd.id" +
+		" left join goods_don_sku gdk on gdk.id = ec.sku_id")
+public class ExchangeCodeView {
+	@DbField("ec.id")
+	private Long id;
+
+	@DbField("ec.code")
+	private String code;
+
+	@DbField("ec.goods_id")
+	private Long goodsId;
+
+	/**
+	 * 平台名称
+	 */
+	@DbField("gd.title")
+	private String title;
+
+	/**
+	 * 规格名称,金额
+	 */
+	@DbField("concat(gdk.spec_val,'',cast(gdk.price/100 as decimal(10,2)))")
+	private String specVal;
+
+	/**
+	 * 使用状态
+	 */
+	@DbField("ec.use_status")
+	private Boolean useStatus;
+
+	/**
+	 * 使用时间
+	 */
+	@DbField("ec.use_time")
+	private Date useTime;
+
+	/**
+	 * 使用人id
+	 */
+	@DbField("ec.user_id")
+	private Long userId;
+
+	/**
+	 * 用户昵称
+	 */
+	@DbField("u.nickname")
+	private String nickName;
+
+	/**
+	 * 生成日期
+	 */
+	@DbField("ec.created_time")
+	private Date createdTime;
+}

+ 28 - 0
netflix-service/src/main/java/com/cyksj/service/exchange/ExchangeCodeService.java

@@ -0,0 +1,28 @@
+package com.cyksj.service.exchange;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.dto.Result;
+import com.cyksj.model.entity.ExchangeCode;
+import com.cyksj.model.excel.ExcelExchangeCodeData;
+import com.cyksj.model.request.ExchangeCodeReq;
+import com.cyksj.model.request.ExchangeCodeTicket;
+
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeService
+ *创建者: JavaZou
+ *创建时间:2022/11/7 15:41
+ */
+public interface ExchangeCodeService extends IService<ExchangeCode> {
+	/**
+	 * 生成兑换码
+	 */
+	List<ExcelExchangeCodeData> createExchangeCode(ExchangeCodeReq exchangeCodeReq);
+
+	/**
+	 * 兑换码兑换
+	 */
+	Result<String> getTicketByExchangeCode(ExchangeCodeTicket exchangeCodeTicket, Long userId);
+}

+ 313 - 0
netflix-service/src/main/java/com/cyksj/service/exchange/impl/ExchangeCodeServiceImpl.java

@@ -0,0 +1,313 @@
+package com.cyksj.service.exchange.impl;
+
+import cn.hutool.core.date.DateField;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.common.annotation.NoSubmit;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.snowflake.Sequence;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+import com.cyksj.common.util.StringUtil;
+import com.cyksj.dto.RedisKey;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.*;
+import com.cyksj.mapper.manage.exchange.ExchangeCodeMapper;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.excel.ExcelExchangeCodeData;
+import com.cyksj.model.request.ExchangeCodeReq;
+import com.cyksj.model.request.ExchangeCodeTicket;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.exchange.ExchangeCodeService;
+import com.cyksj.service.order.OrderDonService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeServiceImpl
+ *创建者: JavaZou
+ *创建时间:2022/11/7 15:41
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, ExchangeCode> implements ExchangeCodeService {
+	private final GoodsDonMapper goodsDonMapper;
+
+	private final GoodsDonSkuMapper goodsDonSkuMapper;
+
+	private final ExchangeCodeMapper exchangeCodeMapper;
+
+	private final GroupsRelationMapper groupsRelationMapper;
+
+	private final GroupsMapper groupsMapper;
+
+	private final RedisService redisService;
+
+	private static final Sequence SEQUENCE = new Sequence(0);
+
+	private final UserMapper userMapper;
+
+	private final OrderDonMapper orderDonMapper;
+
+	private final OrderDonService orderDonService;
+
+	private static final GlobalThreadPoolTaskExecutor THREAD_POOL_TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
+
+	private final CustomerServiceMapper customerServiceMapper;
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public List<ExcelExchangeCodeData> createExchangeCode(ExchangeCodeReq exchangeCodeReq) {
+		GoodsDon goodsDon = goodsDonMapper.selectById(exchangeCodeReq.getGoodsId());
+		if (goodsDon == null) {
+			throw BusinessRuntimeException.getInstance("所选平台已被删除");
+		}
+		if (goodsDon.getType() == 2) {
+			throw BusinessRuntimeException.getInstance("实物商品不支持生成兑换码");
+		}
+		GoodsDonSku goodsDonSku = goodsDonSkuMapper.selectById(exchangeCodeReq.getSkuId());
+		if (goodsDonSku == null) {
+			throw BusinessRuntimeException.getInstance("所选规格已被删除");
+		}
+		List<ExcelExchangeCodeData> data = new ArrayList<>(exchangeCodeReq.getNumber());
+		for (int i = 0; i < exchangeCodeReq.getNumber(); i++) {
+			StringBuilder sb = new StringBuilder(32);
+			sb.append(goodsDon.getSecondType() == 1 ? "YQ" : "FY");
+			sb.append(getFillByGoodsId(exchangeCodeReq.getGoodsId()));
+			sb.append(getFillByMonth(exchangeCodeReq.getSkuId()));
+			//随即补充12位随即数,组成20位兑换码
+			StringUtil.getRandomStr(sb, 12);
+			ExchangeCode exchangeCode = new ExchangeCode();
+			exchangeCode.setGoodsId(exchangeCodeReq.getGoodsId());
+			exchangeCode.setSkuId(exchangeCodeReq.getSkuId());
+			exchangeCode.setCode(sb.toString());
+			exchangeCode.setUseStatus(false);
+			exchangeCodeMapper.insert(exchangeCode);
+			ExcelExchangeCodeData excelExchangeCodeData = new ExcelExchangeCodeData();
+			BeanUtils.copyProperties(exchangeCode, excelExchangeCodeData);
+			excelExchangeCodeData.setTitle(goodsDon.getTitle());
+			String specVal = goodsDonSku.getSpecVal();
+			BigDecimal price = goodsDonSku.getPrice();
+			excelExchangeCodeData.setSpecVal(String.format("%s%s", specVal, price.divide(BigDecimal.valueOf(100))));
+			excelExchangeCodeData.setUseStatusStr(exchangeCode.getUseStatus() ? "已使用" : "未使用");
+			data.add(excelExchangeCodeData);
+		}
+		return data;
+	}
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	@NoSubmit
+	public Result<String> getTicketByExchangeCode(ExchangeCodeTicket exchangeCodeTicket, Long userId) {
+		if (StrUtil.isEmpty(exchangeCodeTicket.getCode())) {
+			throw BusinessRuntimeException.getInstance("兑换码有误,请使用正确的兑换码");
+		}
+		ExchangeCode exchangeCode = this.getOne(Wrappers.lambdaQuery(ExchangeCode.class).eq(ExchangeCode::getCode, exchangeCodeTicket.getCode()).last("limit 1"));
+		if (exchangeCode == null) {
+			throw BusinessRuntimeException.getInstance("兑换码有误,请使用正确的兑换码");
+		}
+		if (exchangeCode.getUseStatus()) {
+			throw BusinessRuntimeException.getInstance("该兑换码已使用");
+		}
+		//兑换成功
+		exchangeCode.setUseStatus(true);
+		exchangeCode.setUserId(userId);
+		exchangeCode.setUseTime(DateTime.now());
+		int update = exchangeCodeMapper.update(exchangeCode, Wrappers.lambdaQuery(ExchangeCode.class).eq(ExchangeCode::getId, exchangeCode.getId()).eq(ExchangeCode::getUseStatus, false));
+		if (update != 1) {
+			throw BusinessRuntimeException.getInstance("该兑换码已使用");
+		}
+		//生成车票订单
+		getGroupTripsTicket(goodsDonSkuMapper.selectById(exchangeCode.getSkuId()), userId);
+		log.info("用户:{}通过兑换码:{}兑换成功", userId, exchangeCodeTicket.getCode());
+		return GatewayResponse.SUCCESS.newBuilder().toResult("兑换成功");
+	}
+
+	/**
+	 * 根据平台id获取填充的字符串
+	 */
+	private String getFillByGoodsId(Long goodsId) {
+		String goodsIdStr = String.valueOf(goodsId);
+		if (goodsIdStr.length() == 1) {
+			return String.format("%s%s%s", 0, 0, goodsId);
+		}
+		if (goodsIdStr.length() == 2) {
+			return String.format("%s%s", 0, goodsId);
+		}
+		//最大不超过999
+		if (goodsIdStr.length() > 3) {
+			return goodsIdStr.substring(0, 3);
+		}
+		return goodsIdStr;
+	}
+
+	/**
+	 * 根据月份长度获取填充的字符串
+	 */
+	private String getFillByMonth(Long skuMonth) {
+		String goodsIdStr = String.valueOf(skuMonth);
+		if (goodsIdStr.length() == 1) {
+			return String.format("%s%s%s", 0, 0, skuMonth);
+		}
+		return String.format("%s%s", 0, skuMonth);
+	}
+
+	/**
+	 * 获取车位
+	 */
+	private void getGroupTripsTicket(GoodsDonSku sku, Long userId) {
+		if (sku == null) {
+			throw BusinessRuntimeException.getInstance("规格不存在,请联系客服");
+		}
+		GoodsDon goodsDon = goodsDonMapper.selectById(sku.getGoodsId());
+		if (goodsDon == null) {
+			throw BusinessRuntimeException.getInstance("平台不存在,请联系客服");
+		}
+		User user = userMapper.selectById(userId);
+		if (user == null) {
+			throw BusinessRuntimeException.getInstance("用户不存在");
+		}
+		GroupsTrips groups = groupsMapper.selectOne(Wrappers.lambdaQuery(GroupsTrips.class).eq(GroupsTrips::getSkuId, sku.getId()).gt(GroupsTrips::getAvailableNum, 0).last("limit 1").orderByAsc(GroupsTrips::getAvailableNum));
+		GroupsRelation relation = null;
+		if (groups == null) {
+			//新增车位 并关联
+			relation = createNewGroupTripsAndRelation(sku, groups, relation);
+		} else {
+			//寻找快满编车队进行占座
+			relation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
+					.eq(GroupsRelation::getGroupsId, groups.getId())
+					.eq(GroupsRelation::getStatus, "none")
+					.eq(GroupsRelation::getUserId, 0)
+					.orderByAsc(GroupsRelation::getNum)
+					.last("limit 1")
+			);
+			//如果占位失败 新增车位并关联
+			if (relation == null) {
+				relation = createNewGroupTripsAndRelation(sku, groups, relation);
+			}
+		}
+
+		try {
+			//锁定座位
+			setRelation(relation.getId(), userId);
+		} catch (Exception e) {
+			//失败重新给他分配车位
+			getGroupTripsTicket(sku, userId);
+		}
+		relation.setUserId(userId);
+		relation.setStatus(GroupsRelation.Status.validity);
+		//如果续费增加时间,如果首次则设置当前时间为初始时间
+		Date expiryTime = Optional.ofNullable(relation.getExpiryTime()).orElse(new Date());
+		Date newDate = DateUtil.offset(expiryTime, DateField.MONTH, sku.getMonths() * 1);
+		relation.setExpiryTime(newDate);
+		relation.setStartTime(relation.getStartTime() == null ? new Date() : null);
+		groupsRelationMapper.updateById(relation);
+		Long relationId = relation.getId();
+
+		/* 生成订单 */
+		String donNo = SEQUENCE.nextId().toString();
+		OrderDon orderDon = new OrderDon();
+		orderDon.setOrderNo(donNo);
+		orderDon.setOpenId(user.getOpenId());
+		orderDon.setMoney(BigDecimal.ZERO);
+		orderDon.setSkuId(sku.getId());
+		orderDon.setRelationId(relationId);
+		//默认数量为1
+		orderDon.setNum(1);
+		orderDon.setGoodsId(sku.getGoodsId());
+		orderDon.setStatus(goodsDon.getType() == 1 && goodsDon.getSecondType() == 2 ? OrderDon.Status.complete : OrderDon.Status.hasPayment);
+		//兑换码兑换
+		orderDon.setType(OrderDon.Type.EX_CODE);
+		orderDon.setUserId(user.getId());
+		orderDonMapper.insert(orderDon);
+
+		if (StrUtil.isNotEmpty(user.getOpenId())) {
+			//发送模板消息
+			THREAD_POOL_TASK_EXECUTOR.execute(() -> {
+				try {
+					if (StrUtil.isNotEmpty(user.getOpenId())) {
+						log.info("用户{}兑换车位成功,发送新订单模板消息", user.getId());
+						orderDonService.newOderSendMsgByType(orderDon, goodsDon, sku, user,user.getOpenId());
+					}
+				} catch (Exception e) {
+					log.info("用户{}兑换车位成功,发送消息失败:{}", user.getId(), e);
+				}
+			});
+		}
+
+		THREAD_POOL_TASK_EXECUTOR.execute(() -> {
+			try {
+				//发送至客服消息
+				List<CustomerService> customerServices = customerServiceMapper.selectList(null);
+				for (CustomerService customerService : customerServices) {
+					log.info("向客服{}发送用户兑换码兑换车票消息通知..", customerService.getNickName());
+					orderDonService.newOderSendMsgByType(orderDon, goodsDon, sku, user, customerService.getOpenId());
+				}
+			} catch (Exception e) {
+				log.error("向客服发送用户{}兑换码兑换车票成功,发送消息失败:{}", user.getId(), e);
+			}
+		});
+	}
+
+	private void setRelation(Long relationId, Long userId) {
+		if (redisService.get(RedisKey.GROUPS_RELATION_NUM_KEY + relationId) != null) {
+			throw new BusinessRuntimeException("客官手慢啦,该座位已经有人啦!");
+		} else {
+			redisService.set(RedisKey.GROUPS_RELATION_NUM_KEY + relationId, userId, 60 * 5L);
+			GroupsRelation relation = groupsRelationMapper.selectById(relationId);
+			int count = groupsMapper.decrAvailableNum(relation.getGroupsId());
+			if (count == 0) {
+				log.error("车位异常 groupId:{}", relation.getGroupsId());
+				throw new BusinessRuntimeException("车位异常");
+			}
+		}
+	}
+
+	/**
+	 * 新增新车位并关联车位
+	 */
+	private GroupsRelation createNewGroupTripsAndRelation(GoodsDonSku sku, GroupsTrips groups, GroupsRelation relation) {
+		//通过兑换码兑换车票 新增车位
+		if (groups == null) {
+			groups = new GroupsTrips();
+		}
+		//补充占位
+		if (relation == null) {
+			relation = new GroupsRelation();
+		}
+		groups.setSkuId(sku.getId());
+		groups.setNum(sku.getNum());
+		groups.setAvailableNum(sku.getNum());
+		groups.setStatus(GroupsTrips.Status.waiting);
+		groups.setRemark("等待设置账号");
+		groupsMapper.insert(groups);
+		relation.setGroupsId(groups.getId());
+		relation.setNum(1);
+		relation.setStatus(GroupsRelation.Status.locking);
+		relation.setGroupsId(groups.getId());
+		groupsRelationMapper.insert(relation);
+		//补充其他占位
+		for (int i = 2; i <= groups.getNum(); i++) {
+			GroupsRelation groupsRelation = new GroupsRelation();
+			groupsRelation.setGroupsId(groups.getId());
+			groupsRelation.setNum(i);
+			groupsRelation.setStatus(GroupsRelation.Status.none);
+			groupsRelationMapper.insert(groupsRelation);
+		}
+		return relation;
+	}
+}

+ 8 - 0
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -3,7 +3,10 @@ package com.cyksj.service.order;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.cyksj.model.dto.AliPayParams;
 import com.cyksj.model.dto.H5JsPayParams;
+import com.cyksj.model.entity.GoodsDon;
+import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.User;
 import com.cyksj.model.manage.views.OrderDonView;
 import com.cyksj.model.request.OrderPayRequest;
 
@@ -54,4 +57,9 @@ public interface OrderDonService extends IService<OrderDon> {
      * 支付宝回调
      */
     void aliPayNotify(Map<String, String> paramsMap) throws Exception;
+
+    /**
+     * 发送新订单模板消息
+     */
+	void newOderSendMsgByType(OrderDon order, GoodsDon goodsDon, GoodsDonSku sku, User user,String toPeople) throws Exception;
 }

+ 24 - 27
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -9,6 +9,7 @@ import com.alipay.api.domain.AlipayTradeWapPayModel;
 import com.alipay.api.internal.util.AlipaySignature;
 import com.alipay.api.request.AlipayTradePagePayRequest;
 import com.alipay.api.request.AlipayTradeWapPayRequest;
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.cyksj.common.EnvCommonService;
@@ -19,12 +20,8 @@ import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
 import com.cyksj.common.util.Codec;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.config.WeChatConfig;
-import com.cyksj.config.zfb.BaseZfbConfig;
-import com.cyksj.config.zfb.ZfbConfig;
-import com.cyksj.config.zfb.ZfbProductCode;
 import com.cyksj.dto.RedisKey;
 import com.cyksj.mapper.*;
-import com.cyksj.model.dto.AliPayParams;
 import com.cyksj.model.dto.H5JsPayParams;
 import com.cyksj.model.dto.WxMpTemplateData;
 import com.cyksj.model.dto.WxMpTemplateMessage;
@@ -45,7 +42,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -313,28 +309,29 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		this.updateById(order);
 	}
 
-	private void newOderSendMsgByType(OrderDon order, GoodsDon goodsDon, GoodsDonSku sku, User user, String toPeople) throws Exception {
-		TemplateEnum templateEnum = null;
-		if (EnvCommonService.active.equals(envCommonService.getEnv())) {
-			templateEnum = TemplateEnum.NEW_ORDER_ENV_TEMPLATE;
-		} else {
-			templateEnum = TemplateEnum.NEW_ORDER_PRD_TEMPLATE;
-		}
-		WxMpTemplateMessage templateMessage = new WxMpTemplateMessage()
-				.setToUser(toPeople)
-				.setTemplateId(templateEnum.getTemplateId())
-				//TODO
-				.setUrl(" ");
-		WxMpTemplateMessage.TemplateData data = templateMessage.getData();
-		data.setFirst(new WxMpTemplateData("新订单通知!"));
-		data.setKeyword1(new WxMpTemplateData(order.getOrderNo()));
-		data.setKeyword2(new WxMpTemplateData(String.format("%s,%s", goodsDon.getTitle(), sku.getSpecVal())));
-		data.setKeyword3(new WxMpTemplateData(user.getNickname()));
-		data.setKeyword4(new WxMpTemplateData(order.getMoney().divide(BigDecimal.valueOf(100)).toString()));
-		data.setKeyword5(new WxMpTemplateData(DateUtil.format(order.getCreatedTime(), "yyyy-MM-dd HH:mm:ss")));
-		data.setRemark(new WxMpTemplateData("点击查看详情"));
-		weChatService.sendTemplateMessage(weChatService.getAccessToken(), Jsons.toJson(templateMessage));
-	}
+    @Override
+    public void newOderSendMsgByType(OrderDon order, GoodsDon goodsDon, GoodsDonSku sku, User user, String toPeople) throws Exception {
+        TemplateEnum templateEnum = null;
+        if (EnvCommonService.active.equals(envCommonService.getEnv())) {
+            templateEnum = TemplateEnum.NEW_ORDER_ENV_TEMPLATE;
+        }else {
+            templateEnum = TemplateEnum.NEW_ORDER_PRD_TEMPLATE;
+        }
+        WxMpTemplateMessage templateMessage = new WxMpTemplateMessage()
+                .setToUser(toPeople)
+                .setTemplateId(templateEnum.getTemplateId())
+                //TODO
+                .setUrl(" ");
+        WxMpTemplateMessage.TemplateData data = templateMessage.getData();
+        data.setFirst(new WxMpTemplateData("新订单通知!"));
+        data.setKeyword1(new WxMpTemplateData(order.getOrderNo()));
+        data.setKeyword2(new WxMpTemplateData(String.format("%s,%s", goodsDon.getTitle(), sku.getSpecVal())));
+        data.setKeyword3(new WxMpTemplateData(user.getNickname()));
+        data.setKeyword4(new WxMpTemplateData(order.getMoney().divide(BigDecimal.valueOf(100)).toString()));
+        data.setKeyword5(new WxMpTemplateData(DateUtil.format(order.getCreatedTime(), "yyyy-MM-dd HH:mm:ss")));
+        data.setRemark(new WxMpTemplateData("点击查看详情"));
+        weChatService.sendTemplateMessage(weChatService.getAccessToken(), Jsons.toJson(templateMessage));
+    }
 
 	@Transactional(rollbackFor = Throwable.class)
 	@Override

+ 48 - 0
netflix-web/src/main/java/com/cyksj/web/aop/NoSubmitAop.java

@@ -0,0 +1,48 @@
+package com.cyksj.web.aop;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import com.cyksj.common.annotation.NoSubmit;
+import com.cyksj.enums.GatewayResponse;
+import com.google.common.cache.Cache;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author zwhui
+ * @date 2020/7/16 上午10:55
+ */
+@Aspect
+@Component
+public class NoSubmitAop {
+	private Log logger = LogFactory.getLog(getClass());
+
+	@Autowired
+	private Cache<String, Integer> cache;
+
+	@Around("execution(* com.cyksj..*.*(..)) && @annotation(nrs)")
+	public synchronized Object arround(ProceedingJoinPoint pjp, NoSubmit nrs) throws Throwable{
+
+		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+		HttpServletRequest request = attributes.getRequest();
+		String ipAddr = ServletUtil.getClientIP(request);
+		String key = ipAddr + "-" + request.getServletPath();
+		if (cache.getIfPresent(key) == null) {
+			// 如果缓存中有这个url视为重复提交
+			cache.put(key, 0);
+			return pjp.proceed();
+		} else {
+			logger.error("重复提交");
+			return GatewayResponse.FAIL.newBuilder().toResult("请勿重复提交");
+		}
+	}
+
+}

+ 68 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/exchange/ExchangeCodeController.java

@@ -0,0 +1,68 @@
+package com.cyksj.web.controller.manage.exchange;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.support.ExcelTypeEnum;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.excel.ExcelExchangeCodeData;
+import com.cyksj.model.request.ExchangeCodeReq;
+import com.cyksj.model.views.ExchangeCodeView;
+import com.cyksj.service.exchange.ExchangeCodeService;
+import com.cyksj.web.util.EasyExcelUtils;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: ExchangeCodeController
+ *创建者: JavaZou
+ *创建时间:2022/11/7 14:37
+ */
+@RestController
+@RequestMapping("/manage/exchange/code")
+@RequiredArgsConstructor
+public class ExchangeCodeController {
+
+	private final HttpServletRequest request;
+
+	private final BeanSearcher beanSearcher;
+
+	private final ExchangeCodeService exchangeCodeService;
+
+	/**
+	 * 生成兑换码 并导出
+	 */
+	@GetMapping("/createAndExport")
+	private void createAndExportCode(ExchangeCodeReq exchangeCodeReq, HttpServletResponse response) {
+		if (ObjectUtil.hasEmpty(exchangeCodeReq.getGoodsId(), exchangeCodeReq.getSkuId(), exchangeCodeReq.getNumber())) {
+			throw BusinessRuntimeException.getInstance("必填项为空");
+		}
+		List<ExcelExchangeCodeData> data =  exchangeCodeService.createExchangeCode(exchangeCodeReq);
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, ExcelExchangeCodeData.class, data, "银河兑换码","银河兑换码", ExcelTypeEnum.XLSX, null);
+	}
+
+	/**
+	 * 兑换码列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<ExchangeCodeView>> getExchangeCodeView() {
+		return GatewayResponse.SUCCESS.newBuilder().toResult(beanSearcher.search(ExchangeCodeView.class, MapUtils.flat(request.getParameterMap())));
+	}
+
+	/**
+	 * 导出兑换码
+	 */
+	@GetMapping("/export")
+	public void exportExchangeCode(HttpServletResponse response) {
+		List<ExcelExchangeCodeData> data = beanSearcher.searchAll(ExcelExchangeCodeData.class, MapUtils.flat(request.getParameterMap()));
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, ExcelExchangeCodeData.class, data, "银河兑换码","银河兑换码", ExcelTypeEnum.XLSX, null);
+	}
+}

+ 13 - 5
netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

@@ -3,13 +3,12 @@ package com.cyksj.web.controller.user;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
 import com.cyksj.model.dto.UserWhoami;
-import com.cyksj.service.order.OrderDonService;
+import com.cyksj.model.request.ExchangeCodeTicket;
+import com.cyksj.service.exchange.ExchangeCodeService;
 import com.cyksj.service.user.UserService;
 import com.cyksj.web.util.StpUserUtil;
 import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @author chan
@@ -22,7 +21,7 @@ public class UserController {
 
     private final UserService userService;
 
-    private final OrderDonService orderDonService;
+    private final ExchangeCodeService exchangeCodeService;
 
 
 
@@ -35,4 +34,13 @@ public class UserController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(userWhoami);
     }
 
+    /**
+     * 兑换码兑换车票
+     */
+    @PostMapping("/get/ticket")
+    public Result<String> getTicketByExchangeCode(@RequestBody ExchangeCodeTicket exchangeCodeTicket) {
+        Long userId = StpUserUtil.getLoginIdAsLong();
+        return exchangeCodeService.getTicketByExchangeCode(exchangeCodeTicket, userId);
+    }
+
 }