|
|
@@ -24,10 +24,12 @@ import com.izouma.nineth.domain.*;
|
|
|
import com.izouma.nineth.dto.PageQuery;
|
|
|
import com.izouma.nineth.enums.*;
|
|
|
import com.izouma.nineth.event.CreateAssetEvent;
|
|
|
+import com.izouma.nineth.event.CreateOrderEvent;
|
|
|
import com.izouma.nineth.event.TransferAssetEvent;
|
|
|
import com.izouma.nineth.exception.BusinessException;
|
|
|
import com.izouma.nineth.repo.*;
|
|
|
import com.izouma.nineth.security.Authority;
|
|
|
+import com.izouma.nineth.service.sms.SmsService;
|
|
|
import com.izouma.nineth.utils.JpaUtils;
|
|
|
import com.izouma.nineth.utils.SnowflakeIdWorker;
|
|
|
import lombok.AllArgsConstructor;
|
|
|
@@ -36,6 +38,8 @@ import org.apache.commons.codec.EncoderException;
|
|
|
import org.apache.commons.codec.net.URLCodec;
|
|
|
import org.apache.commons.collections.MapUtils;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.rocketmq.client.producer.SendResult;
|
|
|
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
|
import org.springframework.context.event.EventListener;
|
|
|
import org.springframework.core.env.Environment;
|
|
|
import org.springframework.data.domain.Page;
|
|
|
@@ -71,121 +75,141 @@ public class OrderService {
|
|
|
private AssetRepo assetRepo;
|
|
|
private UserCouponRepo userCouponRepo;
|
|
|
private CollectionService collectionService;
|
|
|
- private RedisTemplate<String, Object> redisTemplate;
|
|
|
private CommissionRecordRepo commissionRecordRepo;
|
|
|
private AdapayProperties adapayProperties;
|
|
|
private GeneralProperties generalProperties;
|
|
|
+ private RocketMQTemplate rocketMQTemplate;
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+ private SnowflakeIdWorker snowflakeIdWorker;
|
|
|
+ private SmsService smsService;
|
|
|
|
|
|
public Page<Order> all(PageQuery pageQuery) {
|
|
|
return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
|
|
|
}
|
|
|
|
|
|
+ public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor) {
|
|
|
+ Long id = snowflakeIdWorker.nextId();
|
|
|
+ SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
|
|
|
+ new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor), 100000);
|
|
|
+ log.info("发送订单到队列: {}, result={}", id, result);
|
|
|
+ return String.valueOf(id);
|
|
|
+ }
|
|
|
+
|
|
|
@Transactional
|
|
|
- public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor) {
|
|
|
- if (qty <= 0) throw new BusinessException("数量必须大于0");
|
|
|
- User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在"));
|
|
|
- Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
|
|
|
- User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
|
|
|
- UserCoupon coupon = null;
|
|
|
- if (userCouponId != null) {
|
|
|
- coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在"));
|
|
|
- if (coupon.isUsed()) {
|
|
|
- throw new BusinessException("该兑换券已使用");
|
|
|
+ public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor, Long id) {
|
|
|
+ long t = System.currentTimeMillis();
|
|
|
+ qty = 1;
|
|
|
+ int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
|
|
|
+ .map(Math::toIntExact)
|
|
|
+ .orElseThrow(new BusinessException("很遗憾,藏品已售罄"));
|
|
|
+ try {
|
|
|
+ if (stock < 0) {
|
|
|
+ throw new BusinessException("很遗憾,藏品已售罄");
|
|
|
}
|
|
|
- if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) {
|
|
|
- throw new BusinessException("该兑换券不可用");
|
|
|
+ Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
|
|
|
+ User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
|
|
|
+ UserCoupon coupon = null;
|
|
|
+ if (userCouponId != null) {
|
|
|
+ coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在"));
|
|
|
+ if (coupon.isUsed()) {
|
|
|
+ throw new BusinessException("该兑换券已使用");
|
|
|
+ }
|
|
|
+ if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) {
|
|
|
+ throw new BusinessException("该兑换券不可用");
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- if (collection.isScheduleSale()) {
|
|
|
- if (collection.getStartTime().isAfter(LocalDateTime.now())) {
|
|
|
- throw new BusinessException("当前还未开售");
|
|
|
+ if (collection.isScheduleSale()) {
|
|
|
+ if (collection.getStartTime().isAfter(LocalDateTime.now())) {
|
|
|
+ throw new BusinessException("当前还未开售");
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- if (!collection.isSalable()) {
|
|
|
- throw new BusinessException("该藏品当前不可购买");
|
|
|
- }
|
|
|
- if (!collection.isOnShelf()) {
|
|
|
- if (!collection.isScanCode()) {
|
|
|
- throw new BusinessException("藏品已下架");
|
|
|
+ if (!collection.isOnShelf()) {
|
|
|
+ if (!collection.isScanCode()) {
|
|
|
+ throw new BusinessException("藏品已下架");
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- if (qty > collection.getStock()) {
|
|
|
- throw new BusinessException("库存不足");
|
|
|
- }
|
|
|
|
|
|
-
|
|
|
- if (collection.getMaxCount() > 0) {
|
|
|
- int count;
|
|
|
- if (StringUtils.isNotBlank(collection.getCountId())) {
|
|
|
- count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
|
|
|
- } else {
|
|
|
- count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
|
|
|
+ if (!collection.isSalable()) {
|
|
|
+ throw new BusinessException("该藏品当前不可购买");
|
|
|
}
|
|
|
- if (count >= collection.getMaxCount()) {
|
|
|
- throw new BusinessException("限购" + collection.getMaxCount() + "件");
|
|
|
+
|
|
|
+ if (collection.getMaxCount() > 0) {
|
|
|
+ int count;
|
|
|
+ if (StringUtils.isNotBlank(collection.getCountId())) {
|
|
|
+ count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
|
|
|
+ } else {
|
|
|
+ count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
|
|
|
+ }
|
|
|
+ if (count >= collection.getMaxCount()) {
|
|
|
+ throw new BusinessException("限购" + collection.getMaxCount() + "件");
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- UserAddress userAddress = null;
|
|
|
- if (addressId != null) {
|
|
|
- userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
|
|
|
- }
|
|
|
+ UserAddress userAddress = null;
|
|
|
+ if (addressId != null) {
|
|
|
+ userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
|
|
|
+ }
|
|
|
|
|
|
- collectionRepo.increaseStock(collectionId, -qty);
|
|
|
- collectionRepo.increaseSale(collectionId, qty);
|
|
|
-
|
|
|
- BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee");
|
|
|
- Order order = Order.builder()
|
|
|
- .userId(userId)
|
|
|
- .collectionId(collectionId)
|
|
|
- .name(collection.getName())
|
|
|
- .pic(collection.getPic())
|
|
|
- .detail(collection.getDetail())
|
|
|
- .properties(collection.getProperties())
|
|
|
- .category(collection.getCategory())
|
|
|
- .canResale(collection.isCanResale())
|
|
|
- .royalties(collection.getRoyalties())
|
|
|
- .serviceCharge(collection.getServiceCharge())
|
|
|
- .type(collection.getType())
|
|
|
- .minterId(collection.getMinterId())
|
|
|
- .minter(minter.getNickname())
|
|
|
- .minterAvatar(minter.getAvatar())
|
|
|
- .qty(qty)
|
|
|
- .price(collection.getPrice())
|
|
|
- .gasPrice(gasFee)
|
|
|
- .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee))
|
|
|
- .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
|
|
|
- .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
|
|
|
- .address(Optional.ofNullable(userAddress).map(u ->
|
|
|
- u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
|
|
|
- .orElse(null))
|
|
|
- .status(OrderStatus.NOT_PAID)
|
|
|
- .assetId(collection.getAssetId())
|
|
|
- .couponId(userCouponId)
|
|
|
- .invitor(invitor)
|
|
|
- .countId(collection.getCountId())
|
|
|
- .build();
|
|
|
- if (coupon != null) {
|
|
|
- coupon.setUsed(true);
|
|
|
- coupon.setUseTime(LocalDateTime.now());
|
|
|
- if (coupon.isNeedGas()) {
|
|
|
- order.setTotalPrice(order.getGasPrice());
|
|
|
- } else {
|
|
|
- order.setTotalPrice(BigDecimal.ZERO);
|
|
|
+ BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee");
|
|
|
+ Order order = Order.builder()
|
|
|
+ .id(Optional.ofNullable(id).orElse(snowflakeIdWorker.nextId()))
|
|
|
+ .userId(userId)
|
|
|
+ .collectionId(collectionId)
|
|
|
+ .name(collection.getName())
|
|
|
+ .pic(collection.getPic())
|
|
|
+ .detail(collection.getDetail())
|
|
|
+ .properties(collection.getProperties())
|
|
|
+ .category(collection.getCategory())
|
|
|
+ .canResale(collection.isCanResale())
|
|
|
+ .royalties(collection.getRoyalties())
|
|
|
+ .serviceCharge(collection.getServiceCharge())
|
|
|
+ .type(collection.getType())
|
|
|
+ .source(collection.getSource())
|
|
|
+ .minterId(collection.getMinterId())
|
|
|
+ .minter(minter.getNickname())
|
|
|
+ .minterAvatar(minter.getAvatar())
|
|
|
+ .qty(qty)
|
|
|
+ .price(collection.getPrice())
|
|
|
+ .gasPrice(gasFee)
|
|
|
+ .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee))
|
|
|
+ .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
|
|
|
+ .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
|
|
|
+ .address(Optional.ofNullable(userAddress).map(u ->
|
|
|
+ u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
|
|
|
+ .orElse(null))
|
|
|
+ .status(OrderStatus.NOT_PAID)
|
|
|
+ .assetId(collection.getAssetId())
|
|
|
+ .couponId(userCouponId)
|
|
|
+ .invitor(invitor)
|
|
|
+ .countId(collection.getCountId())
|
|
|
+ .build();
|
|
|
+ if (coupon != null) {
|
|
|
+ coupon.setUsed(true);
|
|
|
+ coupon.setUseTime(LocalDateTime.now());
|
|
|
+ if (coupon.isNeedGas()) {
|
|
|
+ order.setTotalPrice(order.getGasPrice());
|
|
|
+ } else {
|
|
|
+ order.setTotalPrice(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (collection.getSource() == CollectionSource.TRANSFER) {
|
|
|
- Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
|
|
|
- asset.setStatus(AssetStatus.TRADING);
|
|
|
- assetRepo.save(asset);
|
|
|
- collectionRepo.setOnShelf(collectionId, false);
|
|
|
- }
|
|
|
- order = orderRepo.save(order);
|
|
|
- if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
|
|
|
- notifyOrder(order.getId(), PayMethod.WEIXIN, null);
|
|
|
+ if (collection.getSource() == CollectionSource.TRANSFER) {
|
|
|
+ Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
|
|
|
+ asset.setStatus(AssetStatus.TRADING);
|
|
|
+ assetRepo.save(asset);
|
|
|
+ collectionRepo.setOnShelf(collectionId, false);
|
|
|
+ }
|
|
|
+ order = orderRepo.save(order);
|
|
|
+ if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
|
|
|
+ notifyOrder(order.getId(), PayMethod.WEIXIN, null);
|
|
|
+ }
|
|
|
+ rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
|
|
|
+ log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
|
|
|
+ return order;
|
|
|
+ } catch (Exception e) {
|
|
|
+ collectionService.increaseStock(collectionId, qty);
|
|
|
+ throw e;
|
|
|
}
|
|
|
- return order;
|
|
|
}
|
|
|
|
|
|
public void payOrderAlipay(Long id, Model model) {
|
|
|
@@ -199,7 +223,7 @@ public class OrderService {
|
|
|
JSONObject bizContent = new JSONObject();
|
|
|
bizContent.put("notifyUrl", alipayProperties.getNotifyUrl());
|
|
|
bizContent.put("returnUrl", alipayProperties.getReturnUrl());
|
|
|
- bizContent.put("out_trade_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ bizContent.put("out_trade_no", String.valueOf(snowflakeIdWorker.nextId()));
|
|
|
bizContent.put("total_amount", order.getTotalPrice().stripTrailingZeros().toPlainString());
|
|
|
bizContent.put("disable_pay_channels", "pcredit,creditCard");
|
|
|
if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
|
|
|
@@ -283,7 +307,7 @@ public class OrderService {
|
|
|
}
|
|
|
|
|
|
Map<String, Object> paymentParams = new HashMap<>();
|
|
|
- paymentParams.put("order_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ paymentParams.put("order_no", String.valueOf(snowflakeIdWorker.nextId()));
|
|
|
paymentParams.put("pay_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP).toPlainString());
|
|
|
paymentParams.put("app_id", adapayProperties.getAppId());
|
|
|
paymentParams.put("pay_channel", payChannel);
|
|
|
@@ -405,22 +429,28 @@ public class OrderService {
|
|
|
orderRepo.save(order);
|
|
|
assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
|
|
|
winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
|
|
|
- addSales(winItem.getMinterId(), order.getQty());
|
|
|
} else {
|
|
|
if (collection.getSource() == CollectionSource.TRANSFER) {
|
|
|
Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
|
|
|
assetService.transfer(asset, order.getPrice(), user, "转让", order.getId());
|
|
|
collectionRepo.delete(collection);
|
|
|
+
|
|
|
+ // 发送短信提醒用户转让成功
|
|
|
+// if (asset != null && asset.getUserId() != null) {
|
|
|
+// smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
|
|
|
+// }
|
|
|
+
|
|
|
} else {
|
|
|
orderRepo.save(order);
|
|
|
assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售",
|
|
|
collection.getTotal() > 1 ? collectionService.getNextNumber(order.getCollectionId()) : null);
|
|
|
}
|
|
|
- addSales(collection.getMinterId(), order.getQty());
|
|
|
}
|
|
|
commission(order);
|
|
|
+ collectionService.increaseSale(order.getCollectionId(), order.getQty());
|
|
|
} else if (order.getStatus() == OrderStatus.CANCELLED) {
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
@EventListener
|
|
|
@@ -462,20 +492,19 @@ public class OrderService {
|
|
|
if (order.getStatus() != OrderStatus.NOT_PAID) {
|
|
|
throw new BusinessException("已支付订单无法取消");
|
|
|
}
|
|
|
- Collection collection = collectionRepo.findById(order.getCollectionId())
|
|
|
- .orElseThrow(new BusinessException("藏品不存在"));
|
|
|
- User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
|
|
|
|
|
|
- if (collection.getSource() == CollectionSource.TRANSFER) {
|
|
|
- Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
|
|
|
+ CollectionSource source = Optional.ofNullable(order.getSource()).orElseGet(() ->
|
|
|
+ collectionRepo.findById(order.getCollectionId()).map(Collection::getSource).orElse(null));
|
|
|
+
|
|
|
+ if (source == CollectionSource.TRANSFER) {
|
|
|
+ Asset asset = assetRepo.findById(order.getAssetId()).orElse(null);
|
|
|
if (asset != null) {
|
|
|
asset.setStatus(AssetStatus.NORMAL);
|
|
|
assetRepo.save(asset);
|
|
|
}
|
|
|
- collectionRepo.setOnShelf(collection.getId(), true);
|
|
|
+ collectionRepo.setOnShelf(order.getCollectionId(), true);
|
|
|
}
|
|
|
- collectionRepo.increaseSale(collection.getId(), -order.getQty());
|
|
|
- collectionRepo.increaseStock(collection.getId(), order.getQty());
|
|
|
+ collectionService.increaseStock(order.getCollectionId(), order.getQty());
|
|
|
|
|
|
order.setStatus(OrderStatus.CANCELLED);
|
|
|
order.setCancelTime(LocalDateTime.now());
|
|
|
@@ -488,6 +517,8 @@ public class OrderService {
|
|
|
userCouponRepo.save(coupon);
|
|
|
});
|
|
|
}
|
|
|
+ rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
|
|
|
+ log.info("取消订单{}", order.getId());
|
|
|
}
|
|
|
|
|
|
@Scheduled(fixedRate = 30000)
|
|
|
@@ -497,7 +528,7 @@ public class OrderService {
|
|
|
}
|
|
|
List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
|
|
|
LocalDateTime.now().minusSeconds(210));
|
|
|
- orders.forEach(o -> {
|
|
|
+ orders.parallelStream().forEach(o -> {
|
|
|
try {
|
|
|
cancel(o);
|
|
|
} catch (Exception ignored) {
|
|
|
@@ -508,12 +539,6 @@ public class OrderService {
|
|
|
public void refundCancelled(Order order) {
|
|
|
}
|
|
|
|
|
|
- public synchronized void addSales(Long userId, int num) {
|
|
|
- if (userId != null) {
|
|
|
- userRepo.increaseSales(userId, num);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
public void setNumber() {
|
|
|
for (Collection collection : collectionRepo.findAll()) {
|
|
|
if (collection.getSource() != CollectionSource.OFFICIAL) continue;
|
|
|
@@ -573,7 +598,11 @@ public class OrderService {
|
|
|
request.setTransactionId(order.getTransactionId());
|
|
|
request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
|
|
|
request.setRefundFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
|
|
|
- request.setOutRefundNo(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ request.setOutRefundNo(String.valueOf(snowflakeIdWorker.nextId()));
|
|
|
wxPayService.refund(request);
|
|
|
}
|
|
|
+
|
|
|
+ public Object queryCreateOrder(String id) {
|
|
|
+ return redisTemplate.opsForValue().get("createOrder::" + id);
|
|
|
+ }
|
|
|
}
|