xiongzhu 4 лет назад
Родитель
Сommit
2c511edcc8

+ 34 - 0
src/main/java/com/izouma/nineth/TokenHistory.java

@@ -0,0 +1,34 @@
+package com.izouma.nineth;
+
+import com.izouma.nineth.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import java.math.BigDecimal;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class TokenHistory extends BaseEntity {
+
+    private String tokenId;
+
+    private String operation;
+
+    @Column(precision = 10, scale = 2)
+    private BigDecimal price;
+
+    private String fromUser;
+
+    private Long   fromUserId;
+
+    private String toUser;
+
+    private Long   toUserId;
+}

+ 16 - 0
src/main/java/com/izouma/nineth/domain/Asset.java

@@ -81,6 +81,16 @@ public class Asset extends BaseEntity {
     @ApiModelProperty("铸造者头像")
     @ApiModelProperty("铸造者头像")
     private String minterAvatar;
     private String minterAvatar;
 
 
+    @ApiModelProperty("持有者")
+    @Searchable
+    private String owner;
+
+    @ApiModelProperty("持有者ID")
+    private Long ownerId;
+
+    @ApiModelProperty("持有者头像")
+    private String ownerAvatar;
+
     @ApiModelProperty("图片")
     @ApiModelProperty("图片")
     @Convert(converter = FileObjectListConverter.class)
     @Convert(converter = FileObjectListConverter.class)
     @Column(columnDefinition = "TEXT")
     @Column(columnDefinition = "TEXT")
@@ -119,7 +129,13 @@ public class Asset extends BaseEntity {
     @Enumerated(EnumType.STRING)
     @Enumerated(EnumType.STRING)
     private AssetStatus status;
     private AssetStatus status;
 
 
+    @ApiModelProperty("是否公开展示")
     private boolean publicShow;
     private boolean publicShow;
 
 
+    @ApiModelProperty("是否寄售")
+    private boolean consignment;
+
     private Long publicCollectionId;
     private Long publicCollectionId;
+
+    private int likes;
 }
 }

+ 57 - 0
src/main/java/com/izouma/nineth/domain/GiftOrder.java

@@ -0,0 +1,57 @@
+package com.izouma.nineth.domain;
+
+import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.enums.OrderStatus;
+import com.izouma.nineth.enums.PayMethod;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class GiftOrder extends BaseEntity {
+    private Long userId;
+
+    private Long assetId;
+
+    private Long toUserId;
+
+    @Enumerated(EnumType.STRING)
+    private PayMethod payMethod;
+
+    private OrderStatus status;
+
+    @Column(precision = 10, scale = 2)
+    private BigDecimal gasPrice;
+
+    @ApiModelProperty("交易ID")
+    @Searchable
+    private String transactionId;
+
+    @ApiModelProperty("支付时间")
+    private LocalDateTime payTime;
+
+    @ApiModelProperty("取消时间")
+    private LocalDateTime cancelTime;
+
+    @ApiModelProperty("交易hash")
+    private String txHash;
+
+    private BigInteger gasUsed;
+
+    @ApiModelProperty("区块高度")
+    private BigInteger blockNumber;
+}

+ 10 - 0
src/main/java/com/izouma/nineth/domain/IdentityAuth.java

@@ -36,6 +36,16 @@ public class IdentityAuth extends BaseEntity {
 
 
     private String idBack;
     private String idBack;
 
 
+    private boolean org;
+
+    private String orgName;
+
+    private String orgNo;
+
+    private String orgLicense;
+
+    private String orgLicenseExpire;
+
     @Enumerated(EnumType.STRING)
     @Enumerated(EnumType.STRING)
     private AuthStatus status;
     private AuthStatus status;
 
 

+ 2 - 0
src/main/java/com/izouma/nineth/domain/Order.java

@@ -137,4 +137,6 @@ public class Order extends BaseEntity {
 
 
     @ApiModelProperty("收货地址")
     @ApiModelProperty("收货地址")
     private String address;
     private String address;
+
+    private Long assetId;
 }
 }

+ 3 - 3
src/main/java/com/izouma/nineth/enums/AssetStatus.java

@@ -2,10 +2,10 @@ package com.izouma.nineth.enums;
 
 
 public enum AssetStatus {
 public enum AssetStatus {
     NORMAL("正常"),
     NORMAL("正常"),
-    ON_SALE("出售中"),
-    IN_BLIND_BOX("盲盒中"),
-    TRANSFERRING("转让中"),
+    TRADING("转让中"),
     TRANSFERRED("已转让"),
     TRANSFERRED("已转让"),
+    GIFTING("转赠中"),
+    GIFTED("已转赠"),
     ;
     ;
 
 
     private final String description;
     private final String description;

+ 8 - 0
src/main/java/com/izouma/nineth/repo/GiftOrderRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.GiftOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface GiftOrderRepo extends JpaRepository<GiftOrder, Long>, JpaSpecificationExecutor<GiftOrder> {
+}

+ 2 - 0
src/main/java/com/izouma/nineth/repo/OrderRepo.java

@@ -21,4 +21,6 @@ public interface OrderRepo extends JpaRepository<Order, Long>, JpaSpecificationE
     Optional<Order> findByIdAndDelFalse(Long id);
     Optional<Order> findByIdAndDelFalse(Long id);
 
 
     List<Order> findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus status, LocalDateTime time);
     List<Order> findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus status, LocalDateTime time);
+
+    List<Order> findByCollectionId(Long collectionId);
 }
 }

+ 8 - 0
src/main/java/com/izouma/nineth/repo/TokenHistoryRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.TokenHistory;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface TokenHistoryRepo extends JpaRepository<TokenHistory, Long>, JpaSpecificationExecutor<TokenHistory> {
+}

+ 256 - 37
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -1,35 +1,53 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.request.AlipayTradeWapPayRequest;
+import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
+import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.kevinsawicki.http.HttpRequest;
 import com.github.kevinsawicki.http.HttpRequest;
+import com.izouma.nineth.TokenHistory;
+import com.izouma.nineth.config.AlipayProperties;
+import com.izouma.nineth.config.WxPayProperties;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.NFT;
 import com.izouma.nineth.dto.NFT;
 import com.izouma.nineth.dto.NFTAccount;
 import com.izouma.nineth.dto.NFTAccount;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageQuery;
-import com.izouma.nineth.enums.AssetStatus;
-import com.izouma.nineth.enums.CollectionSource;
-import com.izouma.nineth.enums.CollectionType;
+import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.SecurityUtils;
+import com.izouma.nineth.utils.SnowflakeIdWorker;
 import io.ipfs.api.IPFS;
 import io.ipfs.api.IPFS;
 import io.ipfs.api.MerkleNode;
 import io.ipfs.api.MerkleNode;
 import io.ipfs.api.NamedStreamable;
 import io.ipfs.api.NamedStreamable;
 import io.ipfs.multihash.Multihash;
 import io.ipfs.multihash.Multihash;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.net.URLCodec;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
-import org.springframework.retry.annotation.Backoff;
-import org.springframework.retry.annotation.Retryable;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.ui.Model;
 
 
+import javax.transaction.Transactional;
 import java.io.File;
 import java.io.File;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
 
 
 @Service
 @Service
 @AllArgsConstructor
 @AllArgsConstructor
@@ -41,6 +59,15 @@ public class AssetService {
     private NFTService         nftService;
     private NFTService         nftService;
     private CollectionRepo     collectionRepo;
     private CollectionRepo     collectionRepo;
     private ApplicationContext applicationContext;
     private ApplicationContext applicationContext;
+    private OrderRepo          orderRepo;
+    private SysConfigService   sysConfigService;
+    private GiftOrderRepo      giftOrderRepo;
+    private TokenHistoryRepo   tokenHistoryRepo;
+    private AlipayProperties   alipayProperties;
+    private AlipayClient       alipayClient;
+    private WxPayProperties    wxPayProperties;
+    private WxPayService       wxPayService;
+    private Environment        env;
 
 
     public Page<Asset> all(PageQuery pageQuery) {
     public Page<Asset> all(PageQuery pageQuery) {
         return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
         return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
@@ -83,15 +110,29 @@ public class AssetService {
                         .status(AssetStatus.NORMAL)
                         .status(AssetStatus.NORMAL)
                         .ipfsUrl(ipfsUrl)
                         .ipfsUrl(ipfsUrl)
                         .number((int) (assetRepo.countByIpfsUrlAndStatusNot(ipfsUrl, AssetStatus.TRANSFERRED) + 1))
                         .number((int) (assetRepo.countByIpfsUrlAndStatusNot(ipfsUrl, AssetStatus.TRANSFERRED) + 1))
+                        .owner(user.getNickname())
+                        .ownerId(user.getId())
+                        .ownerAvatar(user.getAvatar())
                         .build();
                         .build();
                 assetRepo.save(asset);
                 assetRepo.save(asset);
                 applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));
                 applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));
+
+                tokenHistoryRepo.save(TokenHistory.builder()
+                        .tokenId(asset.getTokenId())
+                        .fromUser(order.getMinter())
+                        .fromUserId(order.getMinterId())
+                        .toUser(user.getNickname())
+                        .toUserId(user.getId())
+                        .operation("出售")
+                        .price(order.getPrice())
+                        .build());
                 return;
                 return;
             }
             }
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("创建token失败", e);
             log.error("创建token失败", e);
         }
         }
         applicationContext.publishEvent(new CreateAssetEvent(this, false, order, null));
         applicationContext.publishEvent(new CreateAssetEvent(this, false, order, null));
+
     }
     }
 
 
     @Async
     @Async
@@ -160,6 +201,9 @@ public class AssetService {
         if (asset.isPublicShow()) {
         if (asset.isPublicShow()) {
             return;
             return;
         }
         }
+        if (asset.getStatus() != AssetStatus.NORMAL) {
+            throw new BusinessException("当前状态不可展示");
+        }
         User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         Collection collection = Collection.builder()
         Collection collection = Collection.builder()
                 .name(asset.getName())
                 .name(asset.getName())
@@ -191,52 +235,82 @@ public class AssetService {
         assetRepo.save(asset);
         assetRepo.save(asset);
     }
     }
 
 
-    public void Consignment(Long id, BigDecimal price) {
+    public void consignment(Long id, BigDecimal price) {
         Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
         Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
-        Collection collection = null;
-        if (asset.isPublicShow() && asset.getCollectionId() != null) {
-            collection = collectionRepo.findById(asset.getCollectionId()).orElse(null);
+        if (asset.isConsignment()) {
+            throw new BusinessException("已寄售,请勿重新操作");
         }
         }
-        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
-        if (collection == null) {
-            collection = Collection.builder()
-                    .name(asset.getName())
-                    .pic(asset.getPic())
-                    .minter(asset.getMinter())
-                    .minterId(asset.getMinterId())
-                    .minterAvatar(asset.getMinterAvatar())
-                    .owner(owner.getNickname())
-                    .ownerId(owner.getId())
-                    .ownerAvatar(owner.getAvatar())
-                    .detail(asset.getDetail())
-                    .type(CollectionType.DEFAULT)
-                    .source(CollectionSource.TRANSFER)
-                    .sale(0)
-                    .stock(1)
-                    .total(1)
-                    .onShelf(true)
-                    .salable(false)
-                    .price(BigDecimal.valueOf(0))
-                    .properties(asset.getProperties())
-                    .canResale(asset.isCanResale())
-                    .royalties(asset.getRoyalties())
-                    .serviceCharge(asset.getServiceCharge())
-                    .assetId(id)
-                    .build();
+        if (asset.getStatus() != AssetStatus.NORMAL) {
+            throw new BusinessException("当前状态不可寄售");
         }
         }
-        collection.setSalable(true);
-        collection.setPrice(price);
+        if (asset.isPublicShow()) {
+            cancelPublic(asset);
+        }
+        User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
+        Collection collection = Collection.builder()
+                .name(asset.getName())
+                .pic(asset.getPic())
+                .minter(asset.getMinter())
+                .minterId(asset.getMinterId())
+                .minterAvatar(asset.getMinterAvatar())
+                .owner(owner.getNickname())
+                .ownerId(owner.getId())
+                .ownerAvatar(owner.getAvatar())
+                .detail(asset.getDetail())
+                .type(CollectionType.DEFAULT)
+                .source(CollectionSource.TRANSFER)
+                .sale(0)
+                .stock(1)
+                .total(1)
+                .onShelf(true)
+                .salable(true)
+                .price(price)
+                .properties(asset.getProperties())
+                .canResale(asset.isCanResale())
+                .royalties(asset.getRoyalties())
+                .serviceCharge(asset.getServiceCharge())
+                .assetId(id)
+                .build();
         collectionRepo.save(collection);
         collectionRepo.save(collection);
         asset.setPublicShow(true);
         asset.setPublicShow(true);
+        asset.setConsignment(true);
         asset.setPublicCollectionId(collection.getId());
         asset.setPublicCollectionId(collection.getId());
         assetRepo.save(asset);
         assetRepo.save(asset);
     }
     }
 
 
+    public void cancelConsignment(Long id) {
+        Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+        cancelConsignment(asset);
+    }
+
+    public void cancelConsignment(Asset asset) {
+        if (asset.getPublicCollectionId() != null) {
+            List<Order> orders = orderRepo.findByCollectionId(asset.getPublicCollectionId());
+            if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) {
+                throw new BusinessException("已有订单不可取消");
+            }
+            collectionRepo.findById(asset.getCollectionId())
+                    .ifPresent(collection -> {
+                        collection.setSalable(false);
+                        collectionRepo.save(collection);
+                    });
+        }
+        asset.setConsignment(false);
+        assetRepo.save(asset);
+    }
+
     public void cancelPublic(Long id) {
     public void cancelPublic(Long id) {
         Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
         Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+        cancelPublic(asset);
+    }
+
+    public void cancelPublic(Asset asset) {
         if (!asset.isPublicShow()) {
         if (!asset.isPublicShow()) {
             return;
             return;
         }
         }
+        if (asset.isConsignment()) {
+            cancelConsignment(asset);
+        }
         Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
         Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                 .orElseThrow(new BusinessException("无展示记录"));
                 .orElseThrow(new BusinessException("无展示记录"));
         collectionRepo.delete(collection);
         collectionRepo.delete(collection);
@@ -255,4 +329,149 @@ public class AssetService {
         });
         });
         assetRepo.save(asset);
         assetRepo.save(asset);
     }
     }
+
+    @Transactional
+    public GiftOrder gift(Long userId, Long assetId, Long toUserId) {
+        Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("资产不存在"));
+        if (!(asset.getStatus() == AssetStatus.NORMAL)) {
+            throw new BusinessException("当前状态不可转赠");
+        }
+        if (asset.isConsignment()) {
+            throw new BusinessException("请先取消寄售");
+        }
+        if (asset.isPublicShow()) {
+            cancelPublic(asset);
+        }
+
+        asset.setStatus(AssetStatus.GIFTING);
+        assetRepo.save(asset);
+
+        GiftOrder giftOrder = GiftOrder.builder()
+                .userId(userId)
+                .assetId(assetId)
+                .toUserId(toUserId)
+                .gasPrice(sysConfigService.getBigDecimal("gas_fee"))
+                .status(OrderStatus.NOT_PAID)
+                .build();
+        return giftOrderRepo.save(giftOrder);
+    }
+
+    public void payOrderAlipay(Long id, Model model) {
+        try {
+            GiftOrder order = giftOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
+
+            if (order.getStatus() != OrderStatus.NOT_PAID) {
+                throw new BusinessException("订单状态错误");
+            }
+
+            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("total_amount", order.getGasPrice().stripTrailingZeros().toPlainString());
+            bizContent.put("disable_pay_channels", "pcredit,creditCard");
+            if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
+                // 测试环境设为1分
+                bizContent.put("total_amount", "0.01");
+            }
+            bizContent.put("subject", "转赠GAS费");
+            bizContent.put("product_code", "QUICK_WAP_PAY");
+            JSONObject body = new JSONObject();
+            body.put("action", "payGiftOrder");
+            body.put("userId", order.getUserId());
+            body.put("orderId", order.getId());
+            bizContent.put("body", body.toJSONString());
+
+            AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
+            alipayRequest.setReturnUrl(alipayProperties.getReturnUrl());
+            alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
+            alipayRequest.setBizContent(JSON.toJSONString(bizContent));
+
+            String form = alipayClient.pageExecute(alipayRequest).getBody();
+            model.addAttribute("form", form);
+        } catch (BusinessException err) {
+            model.addAttribute("errMsg", err.getError());
+        } catch (Exception e) {
+            model.addAttribute("errMsg", e.getMessage());
+        }
+    }
+
+
+    public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException {
+        GiftOrder order = giftOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));
+        if (order.getStatus() != OrderStatus.NOT_PAID) {
+            throw new BusinessException("订单状态错误");
+        }
+
+        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+        request.setBody("转赠GAS费");
+        request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
+        request.setTotalFee(order.getGasPrice().multiply(BigDecimal.valueOf(100)).intValue());
+        if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
+            // 测试环境设为1分
+//                    request.setTotalFee(1);
+        }
+        request.setSpbillCreateIp("180.102.110.170");
+        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
+        request.setTradeType(tradeType);
+        request.setOpenid(openId);
+        request.setSignType("MD5");
+        JSONObject body = new JSONObject();
+        body.put("action", "payGiftOrder");
+        body.put("userId", order.getUserId());
+        body.put("orderId", order.getId());
+        request.setAttach(body.toJSONString());
+
+        if (WxPayConstants.TradeType.MWEB.equals(tradeType)) {
+            WxPayMwebOrderResult result = wxPayService.createOrder(request);
+            return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
+        } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) {
+            return wxPayService.<WxPayMpOrderResult>createOrder(request);
+        }
+        throw new BusinessException("不支持此付款方式");
+    }
+
+    @Transactional
+    public void giftNotify(Long orderId, PayMethod payMethod, String transactionId) {
+        GiftOrder giftOrder = giftOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        Asset asset = assetRepo.findById(giftOrder.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
+        User newOwner = userRepo.findById(giftOrder.getToUserId()).orElseThrow(new BusinessException("用户不存在"));
+
+        giftOrder.setStatus(OrderStatus.FINISH);
+        giftOrder.setTransactionId(transactionId);
+        giftOrder.setPayTime(LocalDateTime.now());
+        giftOrder.setPayMethod(PayMethod.ALIPAY);
+
+        transfer(asset, newOwner);
+
+        tokenHistoryRepo.save(TokenHistory.builder()
+                .fromUser(asset.getOwner())
+                .fromUserId(asset.getOwnerId())
+                .toUser(newOwner.getNickname())
+                .toUserId(newOwner.getId())
+                .operation("转赠")
+                .build());
+    }
+
+    public void transfer(Asset asset, User toUser) {
+        Asset newAsset = new Asset();
+        BeanUtils.copyProperties(asset, newAsset);
+        newAsset.setId(null);
+        newAsset.setUserId(toUser.getId());
+        newAsset.setOwner(toUser.getNickname());
+        newAsset.setOwnerId(toUser.getId());
+        newAsset.setOwnerAvatar(toUser.getAvatar());
+        newAsset.setPublicShow(false);
+        newAsset.setPublicCollectionId(null);
+        newAsset.setStatus(AssetStatus.NORMAL);
+        assetRepo.save(newAsset);
+
+        asset.setPublicShow(false);
+        asset.setPublicCollectionId(null);
+        asset.setStatus(AssetStatus.GIFTED);
+        asset.setOwner(toUser.getNickname());
+        asset.setOwnerId(toUser.getId());
+        asset.setOwnerAvatar(toUser.getAvatar());
+        assetRepo.save(asset);
+    }
 }
 }

+ 47 - 1
src/main/java/com/izouma/nineth/service/NFTService.java

@@ -7,7 +7,6 @@ import com.alipay.mychain.sdk.domain.transaction.LogEntry;
 import com.antfinancial.mychain.baas.tool.restclient.RestClient;
 import com.antfinancial.mychain.baas.tool.restclient.RestClient;
 import com.antfinancial.mychain.baas.tool.restclient.RestClientProperties;
 import com.antfinancial.mychain.baas.tool.restclient.RestClientProperties;
 import com.antfinancial.mychain.baas.tool.restclient.model.CallRestBizParam;
 import com.antfinancial.mychain.baas.tool.restclient.model.CallRestBizParam;
-import com.antfinancial.mychain.baas.tool.restclient.model.ClientParam;
 import com.antfinancial.mychain.baas.tool.restclient.model.Method;
 import com.antfinancial.mychain.baas.tool.restclient.model.Method;
 import com.antfinancial.mychain.baas.tool.restclient.model.ReceiptDecoration;
 import com.antfinancial.mychain.baas.tool.restclient.model.ReceiptDecoration;
 import com.antfinancial.mychain.baas.tool.restclient.response.BaseResp;
 import com.antfinancial.mychain.baas.tool.restclient.response.BaseResp;
@@ -101,4 +100,51 @@ public class NFTService {
         }
         }
         throw new BusinessException("创建nft失败");
         throw new BusinessException("创建nft失败");
     }
     }
+
+
+    public NFT transferToken(String tokenId, String fromAccount, String toAccount) throws Exception {
+        JSONArray jsonArray = new JSONArray();
+        jsonArray.add(Utils.getIdentityByName(fromAccount));
+        jsonArray.add(Utils.getIdentityByName(toAccount));
+        jsonArray.add(Long.parseLong(tokenId, 16));
+
+        CallRestBizParam callRestBizParam = CallRestBizParam.builder()
+                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .bizid(restClientProperties.getBizid())
+                .account(restClientProperties.getAccount())
+                .contractName(Constants.CONTRACT_NAME)
+                .methodSignature("safeTransferFrom(identity,identity,uint256)")
+                .inputParamListStr(jsonArray.toJSONString())
+                .outTypes("[]")//合约返回值类型
+                .mykmsKeyId(restClientProperties.getKmsId())
+                .method(Method.CALLCONTRACTBIZASYNC)
+                .tenantid(restClientProperties.getTenantid())
+                .gas(500000L)
+                .build();
+        BaseResp resp = restClient.bizChainCallWithReceipt(callRestBizParam);
+        if (!resp.isSuccess()) {
+            log.info("EVM合约执行失败: " + resp.getCode() + ", " + resp.getData());
+        }
+        if ("200".equals(resp.getCode())) {
+            log.info("EVM合约执行成功");
+            // 合约调用交易回执内容
+            ReceiptDecoration txReceipt = JSON.parseObject(resp.getData(), ReceiptDecoration.class);
+            BigInteger gasUsed = txReceipt.getGasUsed();
+            long result = txReceipt.getResult();
+            log.info("EVM合约交易内容: 哈希 " + txReceipt.getHash() + ", 消耗燃料 " + gasUsed + ", 结果 " + result);
+            for (LogEntry logEntry : txReceipt.getLogs()) {
+                if (logEntry.getTopics().get(0).equals(HashUtils.Keccak256("Transfer(identity,identity,uint256)"))) {
+                    String transferTokenId = logEntry.getTopics().get(3);
+                    txReceipt.getBlockNumber();
+                    NFT nft = new NFT(txReceipt.getHash(), tokenId, txReceipt.getBlockNumber(), txReceipt.getGasUsed());
+                    log.info("NFT转移成功 {}", nft);
+                    return nft;
+                }
+            }
+        } else {
+            // 异步交易未成功需要根据状态码判断交易状态
+            log.error("EVM合约执行未成功: " + resp.getCode());
+        }
+        throw new BusinessException("创建nft失败");
+    }
 }
 }

+ 40 - 37
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -15,9 +15,7 @@ import com.izouma.nineth.config.WxPayProperties;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageQuery;
-import com.izouma.nineth.enums.CollectionType;
-import com.izouma.nineth.enums.OrderStatus;
-import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.repo.*;
@@ -58,6 +56,7 @@ public class OrderService {
     private AssetService     assetService;
     private AssetService     assetService;
     private SysConfigService sysConfigService;
     private SysConfigService sysConfigService;
     private BlindBoxItemRepo blindBoxItemRepo;
     private BlindBoxItemRepo blindBoxItemRepo;
+    private AssetRepo        assetRepo;
 
 
     public Page<Order> all(PageQuery pageQuery) {
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
@@ -121,9 +120,17 @@ public class OrderService {
                                 u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
                                 u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
                         .orElse(null))
                         .orElse(null))
                 .status(OrderStatus.NOT_PAID)
                 .status(OrderStatus.NOT_PAID)
+                .assetId(collection.getAssetId())
                 .build();
                 .build();
-        return orderRepo.save(order);
 
 
+        if (collection.getSource() == CollectionSource.TRANSFER) {
+            Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
+            asset.setStatus(AssetStatus.TRADING);
+            assetRepo.save(asset);
+            collection.setOnShelf(false);
+            collectionRepo.save(collection);
+        }
+        return orderRepo.save(order);
     }
     }
 
 
     public void payOrderAlipay(Long id, Model model) {
     public void payOrderAlipay(Long id, Model model) {
@@ -166,36 +173,7 @@ public class OrderService {
         }
         }
     }
     }
 
 
-    public String payOrderWeixinH5(Long id) throws WxPayException, EncoderException {
-        Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在"));
-        if (order.getStatus() != OrderStatus.NOT_PAID) {
-            throw new BusinessException("订单状态错误");
-        }
-
-        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
-        request.setBody(order.getName());
-        request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
-        request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
-        if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
-            // 测试环境设为1分
-//                    request.setTotalFee(1);
-        }
-        request.setSpbillCreateIp("180.102.110.170");
-        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
-        request.setTradeType(WxPayConstants.TradeType.MWEB);
-        request.setSignType("MD5");
-        JSONObject body = new JSONObject();
-        body.put("action", "payOrder");
-        body.put("userId", order.getUserId());
-        body.put("orderId", order.getId());
-        request.setAttach(body.toJSONString());
-
-        WxPayMwebOrderResult result = wxPayService.createOrder(request);
-
-        return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
-    }
-
-    public Object payOrderWeixin(Long id, String openId) throws WxPayException {
+    public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException {
         Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在"));
         Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在"));
         if (order.getStatus() != OrderStatus.NOT_PAID) {
         if (order.getStatus() != OrderStatus.NOT_PAID) {
             throw new BusinessException("订单状态错误");
             throw new BusinessException("订单状态错误");
@@ -219,12 +197,20 @@ public class OrderService {
         body.put("userId", order.getUserId());
         body.put("userId", order.getUserId());
         body.put("orderId", order.getId());
         body.put("orderId", order.getId());
         request.setAttach(body.toJSONString());
         request.setAttach(body.toJSONString());
+        if (WxPayConstants.TradeType.MWEB.equals(tradeType)) {
+            WxPayMwebOrderResult result = wxPayService.createOrder(request);
+            return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
+        } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) {
+            return wxPayService.<WxPayMpOrderResult>createOrder(request);
+        }
+        throw new BusinessException("不支持此付款方式");
 
 
-        return wxPayService.<WxPayMpOrderResult>createOrder(request);
     }
     }
 
 
     public void notifyAlipay(Long orderId, PayMethod payMethod, String transactionId) {
     public void notifyAlipay(Long orderId, PayMethod payMethod, String transactionId) {
         Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
         Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+        Collection collection = collectionRepo.findById(order.getCollectionId())
+                .orElseThrow(new BusinessException("藏品不存在"));
         if (order.getStatus() == OrderStatus.NOT_PAID) {
         if (order.getStatus() == OrderStatus.NOT_PAID) {
             if (order.getType() == CollectionType.BLIND_BOX) {
             if (order.getType() == CollectionType.BLIND_BOX) {
                 List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(order.getCollectionId());
                 List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(order.getCollectionId());
@@ -294,8 +280,18 @@ public class OrderService {
                 orderRepo.save(order);
                 orderRepo.save(order);
                 assetService.createAsset(order);
                 assetService.createAsset(order);
             }
             }
-        } else if (order.getStatus() == OrderStatus.CANCELLED) {
 
 
+            if (collection.getSource() == CollectionSource.TRANSFER) {
+                Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
+                if (asset != null) {
+                    asset.setStatus(AssetStatus.TRANSFERRED);
+                    asset.setPublicShow(false);
+                    asset.setPublicCollectionId(null);
+                    assetRepo.save(asset);
+                    collectionRepo.delete(collection);
+                }
+            }
+        } else if (order.getStatus() == OrderStatus.CANCELLED) {
         }
         }
     }
     }
 
 
@@ -337,6 +333,13 @@ public class OrderService {
                 .orElseThrow(new BusinessException("藏品不存在"));
                 .orElseThrow(new BusinessException("藏品不存在"));
         User minter = userRepo.findById(collection.getMinterId()).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);
+            if (asset != null) {
+                asset.setStatus(AssetStatus.NORMAL);
+            }
+            collection.setOnShelf(true);
+        }
         collection.setSale(collection.getSale() - 1);
         collection.setSale(collection.getSale() - 1);
         collection.setStock(collection.getStock() + 1);
         collection.setStock(collection.getStock() + 1);
         collectionRepo.save(collection);
         collectionRepo.save(collection);
@@ -353,7 +356,7 @@ public class OrderService {
     public void batchCancel() {
     public void batchCancel() {
         List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
         List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
                 LocalDateTime.now().minusMinutes(5));
                 LocalDateTime.now().minusMinutes(5));
-        orders.stream().parallel().forEach(this::cancel);
+        orders.stream().forEach(this::cancel);
     }
     }
 
 
     public void refundCancelled(Order order) {
     public void refundCancelled(Order order) {

+ 17 - 5
src/main/java/com/izouma/nineth/service/UserService.java

@@ -22,7 +22,6 @@ import com.izouma.nineth.service.storage.StorageService;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.SecurityUtils;
-import io.jsonwebtoken.Jwts;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -33,17 +32,13 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.PageImpl;
 import org.springframework.data.jpa.domain.Specification;
 import org.springframework.data.jpa.domain.Specification;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
-import javax.persistence.criteria.CriteriaBuilder;
-import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Predicate;
-import javax.persistence.criteria.Root;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -83,6 +78,7 @@ public class UserService {
 
 
         specification = specification.and((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
         specification = specification.and((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
             List<Predicate> and = new ArrayList<>();
             List<Predicate> and = new ArrayList<>();
+            and.add(criteriaBuilder.equal(root.get("del"), false));
             if (!pageQuery.getQuery().containsKey("admin")) {
             if (!pageQuery.getQuery().containsKey("admin")) {
                 and.add(criteriaBuilder.equal(root.get("admin"), false));
                 and.add(criteriaBuilder.equal(root.get("admin"), false));
             }
             }
@@ -358,4 +354,20 @@ public class UserService {
             throw new BusinessException("校验失败");
             throw new BusinessException("校验失败");
         }
         }
     }
     }
+
+    public Map<String, Object> searchByPhone(String phone) {
+        if (AuthStatus.SUCCESS != SecurityUtils.getAuthenticatedUser().getAuthStatus()) {
+            throw new BusinessException("实名认证后才能赠送");
+        }
+        User user = userRepo.findByPhoneAndDelFalse(phone).orElseThrow(new BusinessException("用户不存在或未认证"));
+        if (AuthStatus.SUCCESS != user.getAuthStatus()) {
+            throw new BusinessException("用户不存在或未认证");
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put("id", user.getId());
+        map.put("avatar", user.getAvatar());
+        map.put("phone", user.getPhone().replaceAll("(?<=.{3}).*(?=.{4})", "****"));
+        map.put("realName", user.getRealName().replaceAll(".*(?=.)", "**"));
+        return map;
+    }
 }
 }

+ 26 - 2
src/main/java/com/izouma/nineth/web/AssetController.java

@@ -1,19 +1,22 @@
 package com.izouma.nineth.web;
 package com.izouma.nineth.web;
 
 
 import com.izouma.nineth.domain.Asset;
 import com.izouma.nineth.domain.Asset;
-import com.izouma.nineth.service.AssetService;
+import com.izouma.nineth.domain.GiftOrder;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.AssetRepo;
 import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
+import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.List;
 
 
 @RestController
 @RestController
@@ -59,19 +62,40 @@ public class AssetController extends BaseController {
     }
     }
 
 
     @PostMapping("/publicShow")
     @PostMapping("/publicShow")
+    @ApiOperation("公开展示")
     public void publicShow(@RequestParam Long id) {
     public void publicShow(@RequestParam Long id) {
         assetService.publicShow(id);
         assetService.publicShow(id);
     }
     }
 
 
     @PostMapping("/cancelPublic")
     @PostMapping("/cancelPublic")
+    @ApiOperation("取消展示")
     public void cancelPublic(@RequestParam Long id) {
     public void cancelPublic(@RequestParam Long id) {
         assetService.cancelPublic(id);
         assetService.cancelPublic(id);
     }
     }
 
 
     @PostMapping("/usePrivilege")
     @PostMapping("/usePrivilege")
+    @ApiOperation("使用特权")
     public void usePrivilege(@RequestParam Long assetId, @RequestParam Long privilegeId) {
     public void usePrivilege(@RequestParam Long assetId, @RequestParam Long privilegeId) {
         assetService.usePrivilege(assetId, privilegeId);
         assetService.usePrivilege(assetId, privilegeId);
     }
     }
+
+    @PostMapping("/consignment")
+    @ApiOperation("寄售")
+    public void consignment(@RequestParam Long id, @RequestParam BigDecimal price) {
+        assetService.consignment(id, price);
+    }
+
+    @PostMapping("/cancelConsignment")
+    @ApiOperation("取消寄售")
+    public void cancelConsignment(@RequestParam Long id) {
+        assetService.cancelConsignment(id);
+    }
+
+    @PostMapping("/gift")
+    @ApiOperation("转赠")
+    public GiftOrder gift(@RequestParam Long assetId, @RequestParam Long toUserId) {
+        return assetService.gift(SecurityUtils.getAuthenticatedUser().getId(), assetId, toUserId);
+    }
 }
 }
 
 
 
 

+ 35 - 15
src/main/java/com/izouma/nineth/web/OrderPayController.java

@@ -1,6 +1,8 @@
 package com.izouma.nineth.web;
 package com.izouma.nineth.web;
 
 
+import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.exception.WxPayException;
+import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.service.OrderService;
 import com.izouma.nineth.service.OrderService;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -17,36 +19,54 @@ import java.util.regex.Pattern;
 @AllArgsConstructor
 @AllArgsConstructor
 public class OrderPayController {
 public class OrderPayController {
     private final OrderService orderService;
     private final OrderService orderService;
+    private final AssetService assetService;
 
 
     @RequestMapping(value = "/alipay", method = RequestMethod.GET)
     @RequestMapping(value = "/alipay", method = RequestMethod.GET)
     public String payOrderAlipay(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) {
     public String payOrderAlipay(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) {
-
-        boolean weixin = Pattern.matches(".*(micromessenger).*", userAgent.toLowerCase());
-        boolean ios = Pattern.matches(".*(ipad|iphone).*", userAgent.toLowerCase());
-        boolean android = Pattern.matches(".*(android).*", userAgent.toLowerCase());
-        model.addAttribute("weixin", weixin);
-        model.addAttribute("ios", ios);
-        model.addAttribute("android", android);
+        detectUA(userAgent, model);
         orderService.payOrderAlipay(id, model);
         orderService.payOrderAlipay(id, model);
         return "AlipayHtml";
         return "AlipayHtml";
     }
     }
 
 
     @RequestMapping(value = "/weixin_h5")
     @RequestMapping(value = "/weixin_h5")
     public String payOrderWeixinH5(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) throws EncoderException, WxPayException {
     public String payOrderWeixinH5(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) throws EncoderException, WxPayException {
+        detectUA(userAgent, model);
+        model.addAttribute("payUrl", orderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, null));
+        return "WeixinPayHtml";
+    }
+
+    @RequestMapping(value = "/weixin")
+    @ResponseBody
+    public Object payOrderWeixin(@RequestParam Long id, @RequestParam String openId) throws WxPayException, EncoderException {
+        return orderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, openId);
+    }
 
 
-        boolean weixin = Pattern.matches(".*(micromessenger).*", userAgent.toLowerCase());
-        boolean ios = Pattern.matches(".*(ipad|iphone).*", userAgent.toLowerCase());
-        boolean android = Pattern.matches(".*(android).*", userAgent.toLowerCase());
+    @RequestMapping(value = "/gift/alipay", method = RequestMethod.GET)
+    public String payGiftOrderAlipay(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) {
+        detectUA(userAgent, model);
+        orderService.payOrderAlipay(id, model);
+        return "AlipayHtml";
+    }
+
+    @RequestMapping(value = "/gift/weixin_h5")
+    public String payGiftOrderWeixinH5(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) throws EncoderException, WxPayException {
+        detectUA(userAgent, model);
+        model.addAttribute("payUrl", assetService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, null));
+        return "WeixinPayHtml";
+    }
+
+    public void detectUA(String ua, Model model) {
+        boolean weixin = Pattern.matches(".*(micromessenger).*", ua.toLowerCase());
+        boolean ios = Pattern.matches(".*(ipad|iphone).*", ua.toLowerCase());
+        boolean android = Pattern.matches(".*(android).*", ua.toLowerCase());
         model.addAttribute("weixin", weixin);
         model.addAttribute("weixin", weixin);
         model.addAttribute("ios", ios);
         model.addAttribute("ios", ios);
         model.addAttribute("android", android);
         model.addAttribute("android", android);
-        model.addAttribute("payUrl", orderService.payOrderWeixinH5(id));
-        return "WeixinPayHtml";
     }
     }
 
 
-    @RequestMapping(value = "/weixin")
+    @RequestMapping(value = "/gift/weixin")
     @ResponseBody
     @ResponseBody
-    public Object payOrderWeixin(@RequestParam Long id, @RequestParam String openId) throws WxPayException {
-        return orderService.payOrderWeixin(id, openId);
+    public Object payGiftOrderWeixin(@RequestParam Long id, @RequestParam String openId) throws WxPayException, EncoderException {
+        return assetService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, openId);
     }
     }
 }
 }

+ 6 - 1
src/main/java/com/izouma/nineth/web/UserController.java

@@ -12,7 +12,6 @@ import com.izouma.nineth.security.JwtTokenUtil;
 import com.izouma.nineth.security.JwtUserFactory;
 import com.izouma.nineth.security.JwtUserFactory;
 import com.izouma.nineth.service.FollowService;
 import com.izouma.nineth.service.FollowService;
 import com.izouma.nineth.service.UserService;
 import com.izouma.nineth.service.UserService;
-import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperation;
@@ -27,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 @AllArgsConstructor
 @AllArgsConstructor
 @RestController
 @RestController
@@ -184,6 +184,11 @@ public class UserController extends BaseController {
     public void verifyTradeCode(@RequestParam String tradeCode) {
     public void verifyTradeCode(@RequestParam String tradeCode) {
         userService.verifyTradeCode(SecurityUtils.getAuthenticatedUser().getId(), tradeCode);
         userService.verifyTradeCode(SecurityUtils.getAuthenticatedUser().getId(), tradeCode);
     }
     }
+
+    @PostMapping("/searchByPhone")
+    public Map<String, Object> searchByPhone(@RequestParam String phone) {
+        return userService.searchByPhone(phone);
+    }
 }
 }
 
 
 
 

+ 1 - 3
src/main/vue/src/views/UserList.vue

@@ -83,9 +83,7 @@ export default {
     },
     },
     methods: {
     methods: {
         beforeGetData() {
         beforeGetData() {
-            if (this.search) {
-                return { search: this.search };
-            }
+            return { search: this.search, query: {} };
         },
         },
         afterGetData(res) {
         afterGetData(res) {
             // let i = this.tableData.findIndex(i => i.username === 'root');
             // let i = this.tableData.findIndex(i => i.username === 'root');

+ 6 - 0
src/test/java/com/izouma/nineth/service/NFTServiceTest.java

@@ -24,4 +24,10 @@ public class NFTServiceTest extends ApplicationTests {
     @Test
     @Test
     public void testCreateToken() {
     public void testCreateToken() {
     }
     }
+
+    @Test
+    public void transferToken() throws Exception {
+        nftService.transferToken("0000000000000000000000000000000000000000000000000000000000000097",
+                "9th_BHlKkGWw", "9th_test");
+    }
 }
 }

+ 2 - 4
src/test/java/com/izouma/nineth/service/OrderServiceTest.java

@@ -1,15 +1,13 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
+import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.domain.Order;
 import com.izouma.nineth.domain.Order;
-import com.izouma.nineth.domain.User;
 import org.apache.commons.codec.EncoderException;
 import org.apache.commons.codec.EncoderException;
 import org.junit.Test;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 
 
-import static org.junit.Assert.*;
-
 public class OrderServiceTest extends ApplicationTests {
 public class OrderServiceTest extends ApplicationTests {
     @Autowired
     @Autowired
     private OrderService orderService;
     private OrderService orderService;
@@ -17,7 +15,7 @@ public class OrderServiceTest extends ApplicationTests {
     @Test
     @Test
     public void create() throws EncoderException, WxPayException {
     public void create() throws EncoderException, WxPayException {
         Order order = orderService.create(30L, 201L, 1, null);
         Order order = orderService.create(30L, 201L, 1, null);
-        String url = orderService.payOrderWeixinH5(order.getId());
+        String url = (String) orderService.payOrderWeixin(order.getId(), WxPayConstants.TradeType.MWEB, null);
         System.out.println(url);
         System.out.println(url);
     }
     }
 }
 }