xiongzhu 4 năm trước cách đây
mục cha
commit
1e63389f74

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

@@ -138,4 +138,52 @@ public class Asset extends BaseEntity {
     private Long publicCollectionId;
 
     private int likes;
+
+    public static Asset create(Collection collection, User user) {
+        return Asset.builder()
+                .userId(user.getId())
+                .collectionId(collection.getId())
+                .minter(collection.getMinter())
+                .minterId(collection.getMinterId())
+                .minterAvatar(collection.getMinterAvatar())
+                .name(collection.getName())
+                .detail(collection.getDetail())
+                .pic(collection.getPic())
+                .properties(collection.getProperties())
+                .privileges(collection.getPrivileges())
+                .category(collection.getCategory())
+                .canResale(collection.isCanResale())
+                .royalties(collection.getRoyalties())
+                .serviceCharge(collection.getServiceCharge())
+                .price(collection.getPrice())
+                .status(AssetStatus.NORMAL)
+                .owner(user.getNickname())
+                .ownerId(user.getId())
+                .ownerAvatar(user.getAvatar())
+                .build();
+    }
+
+    public static Asset create(BlindBoxItem item, User user) {
+        return Asset.builder()
+                .userId(user.getId())
+                .collectionId(item.getCollectionId())
+                .minter(item.getMinter())
+                .minterId(item.getMinterId())
+                .minterAvatar(item.getMinterAvatar())
+                .name(item.getName())
+                .detail(item.getDetail())
+                .pic(item.getPic())
+                .properties(item.getProperties())
+                .privileges(item.getPrivileges())
+                .category(item.getCategory())
+                .canResale(item.isCanResale())
+                .royalties(item.getRoyalties())
+                .serviceCharge(item.getServiceCharge())
+                .price(item.getPrice())
+                .status(AssetStatus.NORMAL)
+                .owner(user.getNickname())
+                .ownerId(user.getId())
+                .ownerAvatar(user.getAvatar())
+                .build();
+    }
 }

+ 3 - 0
src/main/java/com/izouma/nineth/domain/BlindBoxItem.java

@@ -82,6 +82,9 @@ public class BlindBoxItem extends BaseEntity {
     @ApiModelProperty("手续费比例")
     private int serviceCharge;
 
+    @ApiModelProperty("分类")
+    private String category;
+
     @ApiModelProperty("稀有")
     private boolean rare;
 }

+ 26 - 0
src/main/java/com/izouma/nineth/lock/RedisLockException.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.lock;
+
+/**
+ * Created by h p on 2017/3/9.
+ */
+public class RedisLockException extends RuntimeException {
+
+    public RedisLockException() {
+    }
+
+    public RedisLockException(String message) {
+        super(message);
+    }
+
+    public RedisLockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RedisLockException(Throwable cause) {
+        super(cause);
+    }
+
+    public RedisLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 110 - 0
src/main/java/com/izouma/nineth/lock/RedisLockInterceptor.java

@@ -0,0 +1,110 @@
+package com.izouma.nineth.lock;
+
+import com.google.common.base.Joiner;
+import com.izouma.nineth.utils.RedisLockUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author mashaohua
+ */
+@Aspect
+@Component
+public class RedisLockInterceptor {
+
+    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
+
+    private static final ExpressionParser PARSER = new SpelExpressionParser();
+
+    @Pointcut("@annotation(com.izouma.nineth.lock.RedisLockable)")
+    public void pointcut() {
+    }
+
+    @Around("pointcut()")
+    public Object doAround(ProceedingJoinPoint point) throws Throwable {
+
+        MethodSignature methodSignature = (MethodSignature) point.getSignature();
+        Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), point.getTarget().getClass());
+        String targetName = point.getTarget().getClass().getName();
+        String methodName = point.getSignature().getName();
+        Object[] arguments = point.getArgs();
+
+        RedisLockable redisLock = targetMethod.getAnnotation(RedisLockable.class);
+        long expire = redisLock.expiration();
+        String redisKey = getLockKey(redisLock, targetMethod, targetName, methodName, arguments);
+        String uuid;
+        if (redisLock.isWaiting()) {
+            uuid = waitingLock(redisKey, expire, redisLock.retryCount(), redisLock.retryWaitingTime());
+        } else {
+            uuid = noWaitingLock(redisKey, expire);
+        }
+        if (StringUtils.isNotEmpty(uuid)) {
+            try {
+                return point.proceed();
+            } finally {
+                RedisLockUtil.unLock(redisKey, uuid);
+            }
+        } else {
+            throw new RedisLockException(redisKey);
+        }
+    }
+
+    private String getLockKey(RedisLockable redisLock, Method targetMethod,
+                              String targetName, String methodName, Object[] arguments) {
+        String[] keys = redisLock.key();
+        String prefix = redisLock.prefix();
+        StringBuilder sb = new StringBuilder("lock.");
+        if (StringUtils.isEmpty(prefix)) {
+            sb.append(targetName).append(".").append(methodName);
+        } else {
+            sb.append(prefix);
+        }
+        if (keys != null) {
+            String keyStr = Joiner.on("+ '.' +").skipNulls().join(keys);
+            EvaluationContext context = new StandardEvaluationContext(targetMethod);
+            String[] parameterNames = DISCOVERER.getParameterNames(targetMethod);
+            for (int i = 0; i < parameterNames.length; i++) {
+                context.setVariable(parameterNames[i], arguments[i]);
+            }
+            Object key = PARSER.parseExpression(keyStr).getValue(context);
+            sb.append("#").append(key);
+        }
+        return sb.toString();
+    }
+
+    private String noWaitingLock(String key, long expire) {
+        return RedisLockUtil.lock(key, expire);
+    }
+
+    private String waitingLock(String key, long expire, int retryCount, int retryWaitingTime)
+            throws InterruptedException {
+        int count = 0;
+        while (retryCount == -1 || count <= retryCount) {
+            String uuid = noWaitingLock(key, expire);
+            if (!StringUtils.isEmpty(uuid)) {
+                return uuid;
+            }
+            try {
+                TimeUnit.MILLISECONDS.sleep(retryWaitingTime);
+            } catch (InterruptedException e) {
+                throw e;
+            }
+            count++;
+        }
+        return null;
+    }
+}

+ 26 - 0
src/main/java/com/izouma/nineth/lock/RedisLockable.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.lock;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author mashaohua
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RedisLockable {
+
+    String prefix() default "";
+
+    String[] key() default "";
+
+    long expiration() default 60;
+
+    boolean isWaiting() default false; //锁是否等待,默认为不等待
+
+    int retryCount() default -1; // 锁等待重试次数,-1未不限制
+
+    int retryWaitingTime() default 10; // 锁等待重试间隔时间,默认10毫秒
+}

+ 70 - 0
src/main/java/com/izouma/nineth/redis/SpringRedisUtils.java

@@ -0,0 +1,70 @@
+package com.izouma.nineth.redis;
+
+import com.izouma.nineth.utils.ApplicationContextUtil;
+import com.izouma.nineth.utils.JsonUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.scripting.support.StaticScriptSource;
+
+import java.util.List;
+
+public class SpringRedisUtils {
+
+    private static final RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
+
+    private DefaultRedisScript<Long> script;
+
+    public static void set(final String key, Object value) {
+        final String jsonValue = JsonUtils.serialize(value);
+        redisTemplate.opsForValue().set(key, jsonValue);
+    }
+
+
+    public static <T> T get(final String key, Class<T> elementType) {
+        String jsonValue = (String) redisTemplate.opsForValue().get(key);
+        return JsonUtils.deSerialize(jsonValue, elementType);
+    }
+
+    public static boolean setNX(final String key, Object value) {
+        final String jsonValue = JsonUtils.serialize(value);
+        return redisTemplate.opsForValue().setIfAbsent(key, jsonValue);
+    }
+
+    public static boolean setNX(final String key, final String value, final long expired) {
+        return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
+            String result = (String) connection.execute("set",
+                    key.getBytes(), value.getBytes(),
+                    "nx".getBytes(), "ex".getBytes(), String.valueOf(expired).getBytes());
+            if (result == null) {
+                return false;
+            }
+            return "OK".equals(result);
+        });
+    }
+
+    public static <T> T getSet(final String key, Object value, Class<T> clazz) {
+        final String jsonValue = JsonUtils.serialize(value);
+        String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, jsonValue);
+        if (StringUtils.isEmpty(oldValue)) {
+            return null;
+        }
+        return JsonUtils.deSerialize(oldValue, clazz);
+    }
+
+    public static void delete(final String key) {
+        redisTemplate.delete(key);
+    }
+
+    public static long increment(final String key, long expireTime) {
+        return redisTemplate.opsForValue().increment(key, expireTime);
+    }
+
+    public static Object lua(final String script, List<String> keys, List<String> args) {
+        DefaultRedisScript<Object> redisScript = new DefaultRedisScript<>();
+        redisScript.setResultType(Object.class);
+        redisScript.setScriptSource(new StaticScriptSource(script));
+        return redisTemplate.execute(redisScript, keys, args);
+    }
+}

+ 25 - 54
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -21,6 +21,7 @@ import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.lock.RedisLockable;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SecurityUtils;
@@ -73,76 +74,40 @@ public class AssetService {
         return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
     }
 
-    public Asset createAsset(Order order) {
-        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
-        Collection collection = collectionRepo.findById(order.getCollectionId()).orElseThrow(new BusinessException("藏品不存在"));
-        Asset asset = Asset.builder()
-                .userId(user.getId())
-                .orderId(order.getId())
-                .collectionId(order.getCollectionId())
-                .minter(order.getMinter())
-                .minterId(order.getMinterId())
-                .minterAvatar(order.getMinterAvatar())
-                .name(order.getName())
-                .detail(order.getDetail())
-                .pic(order.getPic())
-                .properties(order.getProperties())
-                .privileges(collection.getPrivileges())
-                .category(order.getCategory())
-                .canResale(order.isCanResale())
-                .royalties(order.getRoyalties())
-                .serviceCharge(order.getServiceCharge())
-                .price(order.getPrice())
-                .status(AssetStatus.NORMAL)
-                .owner(user.getNickname())
-                .ownerId(user.getId())
-                .ownerAvatar(user.getAvatar())
-                .build();
+    public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type) {
+        Asset asset = Asset.create(collection, user);
+        asset.setOrderId(orderId);
+        asset.setPrice(price);
+        asset.setIpfsUrl(ipfsUpload(collection.getPic().get(0).getUrl()));
         assetRepo.save(asset);
 
         tokenHistoryRepo.save(TokenHistory.builder()
                 .tokenId(asset.getTokenId())
-                .fromUser(order.getMinter())
-                .fromUserId(order.getMinterId())
+                .fromUser(collection.getMinter())
+                .fromUserId(collection.getMinterId())
                 .toUser(user.getNickname())
                 .toUserId(user.getId())
-                .operation("出售")
-                .price(order.getPrice())
+                .operation(type)
+                .price(price)
                 .build());
 
         return asset;
     }
 
-    public Asset createAsset(Order order, BlindBoxItem winItem) {
-        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
-        Asset asset = Asset.builder()
-                .userId(user.getId())
-                .orderId(order.getId())
-                .collectionId(order.getCollectionId())
-                .minter(winItem.getMinter())
-                .minterId(winItem.getMinterId())
-                .minterAvatar(winItem.getMinterAvatar())
-                .name(winItem.getName())
-                .detail(winItem.getDetail())
-                .pic(winItem.getPic())
-                .properties(winItem.getProperties())
-                .privileges(winItem.getPrivileges())
-                .canResale(winItem.isCanResale())
-                .royalties(winItem.getRoyalties())
-                .serviceCharge(winItem.getServiceCharge())
-                .price(order.getPrice())
-                .status(AssetStatus.NORMAL)
-                .ipfsUrl(ipfsUpload(winItem.getPic().get(0).getUrl()))
-                .build();
+    public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type) {
+        Asset asset = Asset.create(winItem, user);
+        asset.setOrderId(orderId);
+        asset.setPrice(price);
+        asset.setIpfsUrl(ipfsUpload(winItem.getPic().get(0).getUrl()));
         assetRepo.save(asset);
         tokenHistoryRepo.save(TokenHistory.builder()
                 .tokenId(asset.getTokenId())
-                .fromUser(order.getMinter())
-                .fromUserId(order.getMinterId())
+                .fromUser(winItem.getMinter())
+                .fromUserId(winItem.getMinterId())
                 .toUser(user.getNickname())
                 .toUserId(user.getId())
-                .operation("出售")
-                .price(order.getPrice())
+                .operation(type)
+                .price(price)
                 .build());
 
         return asset;
@@ -476,4 +441,10 @@ public class AssetService {
         if (tokenId == null) return new ArrayList<>();
         return tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(tokenId);
     }
+
+    @RedisLockable(key = "#id", expiration = 60, isWaiting = true)
+    public void testLock(String id, String i) throws InterruptedException {
+        Thread.sleep(1000);
+        log.info("" + i);
+    }
 }

+ 7 - 0
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -40,6 +40,7 @@ public class CollectionService {
     private BlindBoxItemRepo blindBoxItemRepo;
     private AppointmentRepo  appointmentRepo;
     private UserRepo         userRepo;
+    private AssetService     assetService;
 
     public Page<Collection> all(PageQuery pageQuery) {
         pageQuery.getQuery().put("del", false);
@@ -244,4 +245,10 @@ public class CollectionService {
         }
         return winItem;
     }
+
+    public void airDrop(Long collectionId, List<Long> userIds) {
+        Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
+        for (Long userId : userIds) {
+        }
+    }
 }

+ 2 - 11
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -294,7 +294,7 @@ public class OrderService {
                 order.setTransactionId(transactionId);
                 order.setPayMethod(payMethod);
                 orderRepo.save(order);
-                Asset asset = assetService.createAsset(order, winItem);
+                Asset asset = assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售");
                 assetService.mint(asset);
             } else {
                 if (collection.getSource() == CollectionSource.TRANSFER) {
@@ -307,7 +307,7 @@ public class OrderService {
                     order.setTransactionId(transactionId);
                     order.setPayMethod(payMethod);
                     orderRepo.save(order);
-                    Asset asset = assetService.createAsset(order);
+                    Asset asset = assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售");
                     assetService.mint(asset);
                 }
             }
@@ -315,15 +315,6 @@ public class OrderService {
         }
     }
 
-    public void createAsset(Long orderId, Long itemId) {
-        assetService.createAsset(orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")),
-                blindBoxItemRepo.findById(itemId).orElseThrow(new BusinessException("item不存在")));
-    }
-
-    public void createAsset(Long orderId) {
-        assetService.createAsset(orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")));
-    }
-
     @EventListener
     public void onCreateAsset(CreateAssetEvent event) {
         Asset asset = event.getAsset();

+ 33 - 0
src/main/java/com/izouma/nineth/utils/ApplicationContextUtil.java

@@ -0,0 +1,33 @@
+package com.izouma.nineth.utils;
+
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by IDEA
+ * User: mashaohua
+ * Date: 2016-10-19 10:21
+ * Desc:
+ */
+@Component
+public class ApplicationContextUtil implements ApplicationContextAware {
+
+    private static ApplicationContext context;
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        ApplicationContextUtil.context = context;
+    }
+
+    public static ApplicationContext getContext() {
+        return context;
+    }
+
+    public static Object getBean(String beanName) {
+        return context.getBean(beanName);
+    }
+
+}

+ 28 - 0
src/main/java/com/izouma/nineth/utils/JsonUtils.java

@@ -1,7 +1,10 @@
 package com.izouma.nineth.utils;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -9,6 +12,9 @@ import java.util.List;
 import java.util.Map;
 
 public class JsonUtils {
+    final static String defaultDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
+
+
     public static class Builder {
         private Map<String, Object> map = new HashMap<>();
 
@@ -34,4 +40,26 @@ public class JsonUtils {
             return this;
         }
     }
+
+    public static <T> String serialize(T t) {
+        JSON.DEFFAULT_DATE_FORMAT = defaultDateFormat;
+        return serialize(t, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat);
+    }
+
+    public static <T> String serialize(T t, SerializerFeature... features) {
+        return JSON.toJSONString(t, features);
+    }
+
+    public static <T> T deSerialize(String string, Class<T> tClass) {
+        return JSON.parseObject(string, tClass);
+    }
+
+    public static <T> List<T> deSerializeList(String string, Class<T> tClass) {
+        return JSON.parseArray(string, tClass);
+    }
+
+    public static <T> T complexJsonToObject(String jsonString) {
+        return JSON.parseObject(jsonString, new TypeReference<T>() {
+        }.getType());
+    }
 }

+ 53 - 0
src/main/java/com/izouma/nineth/utils/RedisLockUtil.java

@@ -0,0 +1,53 @@
+package com.izouma.nineth.utils;
+
+import com.izouma.nineth.redis.SpringRedisUtils;
+
+import java.util.Collections;
+import java.util.UUID;
+
+/**
+ * Created by shma on 2018/8/29.
+ */
+public final class RedisLockUtil {
+
+    private static final int DEFAULT_EXPIRE = 60;
+
+    private static final String SCRIPT =
+            "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
+            + "then\n"
+            + "    return redis.call(\"del\",KEYS[1])\n"
+            + "else\n"
+            + "    return 0\n"
+            + "end";
+
+    private RedisLockUtil() {
+        super();
+    }
+
+    /**
+     *
+     * @param key 锁的key
+     * @return 返回value为null,则锁失败,不为null则锁成功
+     */
+    public static String lock(String key) {
+        return lock(key, DEFAULT_EXPIRE);
+    }
+
+    public static boolean lock(String key, String value) {
+        return lock(key, value, DEFAULT_EXPIRE);
+    }
+
+    public static String lock(String key, long expire) {
+        String value = UUID.randomUUID().toString();
+        boolean nx = SpringRedisUtils.setNX(key, value, expire);
+        return nx ? value : null;
+    }
+
+    public static boolean lock(String key, String value, long expire) {
+        return SpringRedisUtils.setNX(key, value, expire);
+    }
+
+    public static void unLock(String key, String value) {
+        SpringRedisUtils.lua(SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
+    }
+}

+ 0 - 9
src/main/java/com/izouma/nineth/web/OrderController.java

@@ -78,15 +78,6 @@ public class OrderController extends BaseController {
                 collectionId, qty, addressId, couponId);
     }
 
-    @GetMapping("/createAsset")
-    public void createAsset(@RequestParam Long orderId, @RequestParam(required = false) Long itemId) {
-        if (itemId == null) {
-            orderService.createAsset(orderId);
-        } else {
-            orderService.createAsset(orderId, itemId);
-        }
-    }
-
     @PostMapping("/hide")
     public void hide(@RequestParam Long id) {
         Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));

+ 5 - 1
src/test/java/com/izouma/nineth/service/AssetServiceTest.java

@@ -22,7 +22,6 @@ class AssetServiceTest extends ApplicationTests {
     void createAsset() {
         BlindBoxItem item = blindBoxItemRepo.findById(1860L).get();
         Order order = orderRepo.findById(1922L).get();
-        assetService.createAsset(order, item);
     }
 
     @Test
@@ -33,4 +32,9 @@ class AssetServiceTest extends ApplicationTests {
     @Test
     void testCancelConsignment() {
     }
+
+    @Test
+    public void testLock() throws InterruptedException {
+        assetService.testLock("123", "ddd");
+    }
 }