ouyang 3 anni fa
commit
d21b11ba00
100 ha cambiato i file con 5897 aggiunte e 0 eliminazioni
  1. 33 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 21 0
      .idea/compiler.xml
  4. 16 0
      .idea/misc.xml
  5. 8 0
      .idea/modules.xml
  6. 6 0
      .idea/vcs.xml
  7. 114 0
      .mvn/wrapper/MavenWrapperDownloader.java
  8. BIN
      .mvn/wrapper/maven-wrapper.jar
  9. 1 0
      .mvn/wrapper/maven-wrapper.properties
  10. 286 0
      0001-3d.patch
  11. 431 0
      awesome_admin_v2.sql
  12. 15 0
      build_all.sh
  13. 6 0
      build_h5.sh
  14. 15 0
      build_jar.sh
  15. 12 0
      cleadData.sql
  16. 8 0
      install-jar.sh
  17. BIN
      lib/ipfs-multihash-v1.3.0.jar
  18. BIN
      lib/mozjpeg4j-1.1.jar
  19. BIN
      lib/mychain-rest-client-0.10.2.11-with-dependencies.jar
  20. BIN
      lib/mychainx-java-sdk-0.10.2.9-with-dependencies.jar
  21. BIN
      lib/pngquant4j-1.0.1.jar
  22. BIN
      libs/org/libjpegturbo/mozjpeg4j/1.1/mozjpeg4j-1.1.jar
  23. 9 0
      libs/org/libjpegturbo/mozjpeg4j/1.1/mozjpeg4j-1.1.pom
  24. 12 0
      libs/org/libjpegturbo/mozjpeg4j/maven-metadata-local.xml
  25. BIN
      libs/org/pngquant/pngquant4j/1.0.1/pngquant4j-1.0.1.jar
  26. 9 0
      libs/org/pngquant/pngquant4j/1.0.1/pngquant4j-1.0.1.pom
  27. 12 0
      libs/org/pngquant/pngquant4j/maven-metadata-local.xml
  28. 286 0
      mvnw
  29. 161 0
      mvnw.cmd
  30. 4 0
      pics2pic.sql
  31. 425 0
      pom.xml
  32. 33 0
      src/main/bench/index.js
  33. 6 0
      src/main/bench/package.json
  34. 70 0
      src/main/bench/yarn.lock
  35. 1 0
      src/main/contract/.gitignore
  36. 1080 0
      src/main/contract/9th.sol
  37. 0 0
      src/main/contract/9th_sol_AccessControl.bin
  38. 1 0
      src/main/contract/9th_sol_Address.bin
  39. 0 0
      src/main/contract/9th_sol_Context.bin
  40. 1 0
      src/main/contract/9th_sol_Counters.bin
  41. 0 0
      src/main/contract/9th_sol_ERC165.bin
  42. 0 0
      src/main/contract/9th_sol_ERC721.bin
  43. 0 0
      src/main/contract/9th_sol_ERC721Burnable.bin
  44. 0 0
      src/main/contract/9th_sol_ERC721Pausable.bin
  45. 0 0
      src/main/contract/9th_sol_ERC721PresetMinterPauserAutoId.bin
  46. 1 0
      src/main/contract/9th_sol_EnumerableMap.bin
  47. 1 0
      src/main/contract/9th_sol_EnumerableSet.bin
  48. 0 0
      src/main/contract/9th_sol_IERC165.bin
  49. 0 0
      src/main/contract/9th_sol_IERC721.bin
  50. 0 0
      src/main/contract/9th_sol_IERC721Enumerable.bin
  51. 0 0
      src/main/contract/9th_sol_IERC721Metadata.bin
  52. 0 0
      src/main/contract/9th_sol_IERC721Receiver.bin
  53. 0 0
      src/main/contract/9th_sol_Pausable.bin
  54. 1 0
      src/main/contract/9th_sol_SafeMath.bin
  55. 1 0
      src/main/contract/9th_sol_Strings.bin
  56. BIN
      src/main/contract/alipay-solc-0.6.4.tgz
  57. BIN
      src/main/contract/mychain_solc
  58. 181 0
      src/main/contract/package-lock.json
  59. 175 0
      src/main/java/cn/com/sandpay/cashier/sdk/CertUtil.java
  60. 411 0
      src/main/java/cn/com/sandpay/cashier/sdk/CryptoUtil.java
  61. 325 0
      src/main/java/cn/com/sandpay/cashier/sdk/HttpClient.java
  62. 39 0
      src/main/java/cn/com/sandpay/cashier/sdk/RandomStringGenerator.java
  63. 278 0
      src/main/java/cn/com/sandpay/cashier/sdk/SDKConfig.java
  64. 132 0
      src/main/java/cn/com/sandpay/cashier/sdk/SDKUtil.java
  65. 23 0
      src/main/java/com/izouma/nineth/Application.java
  66. 7 0
      src/main/java/com/izouma/nineth/JsonView/UserView.java
  67. 45 0
      src/main/java/com/izouma/nineth/TokenHistory.java
  68. 12 0
      src/main/java/com/izouma/nineth/annotations/Debounce.java
  69. 14 0
      src/main/java/com/izouma/nineth/annotations/OperLog.java
  70. 15 0
      src/main/java/com/izouma/nineth/annotations/RedisLock.java
  71. 12 0
      src/main/java/com/izouma/nineth/annotations/Searchable.java
  72. 12 0
      src/main/java/com/izouma/nineth/annotations/SearchableOne.java
  73. 71 0
      src/main/java/com/izouma/nineth/aspect/DebounceAspect.java
  74. 67 0
      src/main/java/com/izouma/nineth/aspect/RedisLockAspect.java
  75. 33 0
      src/main/java/com/izouma/nineth/aspect/debounce/DebounceTask.java
  76. 34 0
      src/main/java/com/izouma/nineth/config/AdapayConfig.java
  77. 20 0
      src/main/java/com/izouma/nineth/config/AdapayProperties.java
  78. 27 0
      src/main/java/com/izouma/nineth/config/AddResponseHeaderFilter.java
  79. 21 0
      src/main/java/com/izouma/nineth/config/AlipayConfig.java
  80. 20 0
      src/main/java/com/izouma/nineth/config/AlipayProperties.java
  81. 131 0
      src/main/java/com/izouma/nineth/config/CacheConfig.java
  82. 19 0
      src/main/java/com/izouma/nineth/config/Constants.java
  83. 148 0
      src/main/java/com/izouma/nineth/config/DateConfig.java
  84. 6 0
      src/main/java/com/izouma/nineth/config/ErrorCode.java
  85. 5 0
      src/main/java/com/izouma/nineth/config/EventNames.java
  86. 35 0
      src/main/java/com/izouma/nineth/config/GeneralProperties.java
  87. 30 0
      src/main/java/com/izouma/nineth/config/HibernateJsonConfig.java
  88. 36 0
      src/main/java/com/izouma/nineth/config/LocalDateTimeSerializerConfig.java
  89. 37 0
      src/main/java/com/izouma/nineth/config/RedisKeys.java
  90. 25 0
      src/main/java/com/izouma/nineth/config/SandPayConfig.java
  91. 16 0
      src/main/java/com/izouma/nineth/config/SandPayProperties.java
  92. 19 0
      src/main/java/com/izouma/nineth/config/SchedulingConfig.java
  93. 16 0
      src/main/java/com/izouma/nineth/config/SnowflakeIdWorkerConfig.java
  94. 24 0
      src/main/java/com/izouma/nineth/config/SpringSecurityAuditorAware.java
  95. 104 0
      src/main/java/com/izouma/nineth/config/WebMvcConfig.java
  96. 30 0
      src/main/java/com/izouma/nineth/config/WxMaConfiguration.java
  97. 14 0
      src/main/java/com/izouma/nineth/config/WxMaProperties.java
  98. 74 0
      src/main/java/com/izouma/nineth/config/WxMpConfiguration.java
  99. 13 0
      src/main/java/com/izouma/nineth/config/WxMpProperties.java
  100. 47 0
      src/main/java/com/izouma/nineth/config/WxPayConfiguration.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
+
+.DS_Store

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 21 - 0
.idea/compiler.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="chuangqi_back" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel target="11">
+      <module name="chuangqi_back" target="11" />
+    </bytecodeTargetLevel>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="chuangqi_back" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 16 - 0
.idea/misc.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="jpab" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/chuangqi_back.iml" filepath="$PROJECT_DIR$/.idea/chuangqi_back.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 114 - 0
.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL =
+            "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if (mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if (mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: : " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if (!outputFile.getParentFile().exists()) {
+            if (!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}

BIN
.mvn/wrapper/maven-wrapper.jar


+ 1 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip

+ 286 - 0
0001-3d.patch

@@ -0,0 +1,286 @@
+From 30c5e5f1f7b70d18d7006266640f6e24b1a000b9 Mon Sep 17 00:00:00 2001
+From: xiongzhu <692949348@qq.com>
+Date: Wed, 29 Dec 2021 18:15:08 +0800
+Subject: [PATCH] 3d
+
+---
+ .../com/izouma/nineth/domain/Collection.java  |  4 ++
+ .../izouma/nineth/service/AssetService.java   |  2 +-
+ .../com/izouma/nineth/utils/FileUtils.java    | 66 +++++++++++++++++++
+ .../nineth/web/FileUploadController.java      | 33 ++++++++--
+ src/main/vue/src/components/FileUpload.vue    |  7 +-
+ src/main/vue/src/views/BlindBoxEdit.vue       |  4 +-
+ src/main/vue/src/views/CollectionEdit.vue     | 13 +++-
+ 7 files changed, 119 insertions(+), 10 deletions(-)
+
+diff --git a/src/main/java/com/izouma/nineth/domain/Collection.java b/src/main/java/com/izouma/nineth/domain/Collection.java
+index f3ee2ed..75c7949 100644
+--- a/src/main/java/com/izouma/nineth/domain/Collection.java
++++ b/src/main/java/com/izouma/nineth/domain/Collection.java
+@@ -37,6 +37,10 @@ public class Collection extends BaseEntity {
+     @Convert(converter = FileObjectListConverter.class)
+     private List<FileObject> pic;
+ 
++    @Column(columnDefinition = "TEXT")
++    @Convert(converter = FileObjectListConverter.class)
++    private FileObject model3d;
++
+     @ApiModelProperty("铸造者")
+     @Searchable
+     private String minter;
+diff --git a/src/main/java/com/izouma/nineth/service/AssetService.java b/src/main/java/com/izouma/nineth/service/AssetService.java
+index 134a056..01e1b01 100644
+--- a/src/main/java/com/izouma/nineth/service/AssetService.java
++++ b/src/main/java/com/izouma/nineth/service/AssetService.java
+@@ -289,7 +289,7 @@ public class AssetService {
+                 .toUserId(toUser.getId())
+                 .toAvatar(toUser.getAvatar())
+                 .operation(reason)
+-                .price("转赠".equals(reason) ? price : null)
++                .price("转赠".equals(reason) ? null : price)
+                 .build());
+ 
+         asset.setPublicShow(false);
+diff --git a/src/main/java/com/izouma/nineth/utils/FileUtils.java b/src/main/java/com/izouma/nineth/utils/FileUtils.java
+index 3545e2c..d4454d9 100644
+--- a/src/main/java/com/izouma/nineth/utils/FileUtils.java
++++ b/src/main/java/com/izouma/nineth/utils/FileUtils.java
+@@ -3,13 +3,18 @@ package com.izouma.nineth.utils;
+ import org.apache.commons.lang3.StringUtils;
+ 
+ import java.io.*;
++import java.nio.charset.Charset;
++import java.nio.charset.StandardCharsets;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+ import java.nio.file.attribute.PosixFileAttributeView;
+ import java.nio.file.attribute.PosixFileAttributes;
+ import java.nio.file.attribute.PosixFilePermission;
+ import java.nio.file.attribute.PosixFilePermissions;
++import java.util.Optional;
+ import java.util.Set;
++import java.util.zip.ZipEntry;
++import java.util.zip.ZipInputStream;
+ 
+ public class FileUtils {
+ 
+@@ -200,5 +205,66 @@ public class FileUtils {
+ 
+     }
+ 
++    public static void unzip(InputStream in, File destDir) throws IOException {
++        try {
++            unzip(in, destDir, StandardCharsets.UTF_8);
++        } catch (Exception e) {
++            unzip(in, destDir, Charset.forName("GB2312"));
++        }
++    }
++
++    public static void unzip(InputStream in, File destDir, Charset charset) throws IOException {
++        byte[] buffer = new byte[1024];
++        ZipInputStream zis = new ZipInputStream(in);
++        ZipEntry zipEntry = zis.getNextEntry();
++        while (zipEntry != null) {
++            File newFile = newFile(destDir, zipEntry);
++            if (zipEntry.isDirectory()) {
++                if (!newFile.isDirectory() && !newFile.mkdirs()) {
++                    throw new IOException("Failed to create directory " + newFile);
++                }
++            } else {
++                // fix for Windows-created archives
++                File parent = newFile.getParentFile();
++                if (!parent.isDirectory() && !parent.mkdirs()) {
++                    throw new IOException("Failed to create directory " + parent);
++                }
+ 
++                // write file content
++                FileOutputStream fos = new FileOutputStream(newFile);
++                int len;
++                while ((len = zis.read(buffer)) > 0) {
++                    fos.write(buffer, 0, len);
++                }
++                fos.close();
++            }
++            zipEntry = zis.getNextEntry();
++        }
++        zis.closeEntry();
++        zis.close();
++    }
++
++    public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
++        File destFile = new File(destinationDir, zipEntry.getName());
++
++        String destDirPath = destinationDir.getCanonicalPath();
++        String destFilePath = destFile.getCanonicalPath();
++
++        if (!destFilePath.startsWith(destDirPath + File.separator)) {
++            throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
++        }
++
++        return destFile;
++    }
++
++    public static File findInDir(File dir, String ext) {
++        if (!(dir.exists() && dir.isDirectory())) return null;
++        for (File file : Optional.ofNullable(dir.listFiles()).orElse(new File[0])) {
++            String name = file.getName().toLowerCase();
++            if (name.endsWith(ext.toLowerCase()) && !file.isHidden()) {
++                return file;
++            }
++        }
++        return null;
++    }
+ }
+diff --git a/src/main/java/com/izouma/nineth/web/FileUploadController.java b/src/main/java/com/izouma/nineth/web/FileUploadController.java
+index 47580f3..114788e 100644
+--- a/src/main/java/com/izouma/nineth/web/FileUploadController.java
++++ b/src/main/java/com/izouma/nineth/web/FileUploadController.java
+@@ -10,10 +10,10 @@ import org.apache.commons.io.FilenameUtils;
+ import org.apache.commons.lang3.ArrayUtils;
+ import org.apache.commons.lang3.RandomStringUtils;
+ import org.apache.commons.lang3.StringUtils;
++import org.apache.poi.util.TempFile;
+ import org.bytedeco.javacv.FFmpegFrameGrabber;
+ import org.bytedeco.javacv.Frame;
+ import org.bytedeco.javacv.Java2DFrameConverter;
+-import org.pngquant.PngQuant;
+ import org.springframework.beans.factory.annotation.Autowired;
+ import org.springframework.web.bind.annotation.PostMapping;
+ import org.springframework.web.bind.annotation.RequestMapping;
+@@ -26,10 +26,7 @@ import java.awt.image.BufferedImage;
+ import java.io.*;
+ import java.net.URLConnection;
+ import java.text.SimpleDateFormat;
+-import java.util.Base64;
+-import java.util.Date;
+-import java.util.Objects;
+-import java.util.Optional;
++import java.util.*;
+ import java.util.regex.Pattern;
+ 
+ 
+@@ -182,4 +179,30 @@ public class FileUploadController {
+ 
+         return new FileObject(file.getOriginalFilename(), url, thumbUrl, file.getContentType());
+     }
++
++    @PostMapping("/3dModel")
++    public FileObject upload3dModel(@RequestParam("file") MultipartFile file) throws IOException {
++        if (!"zip".equalsIgnoreCase(FilenameUtils.getExtension(file.getOriginalFilename()))) {
++            throw new BusinessException("只能上传zip");
++        }
++        File destDir = TempFile.createTempDirectory(RandomStringUtils.randomAlphabetic(20));
++        com.izouma.nineth.utils.FileUtils.unzip(file.getInputStream(), destDir);
++        File fbxFile = com.izouma.nineth.utils.FileUtils.findInDir(destDir, ".fbx");
++        if (fbxFile == null) {
++            throw new BusinessException("找不到fbx文件");
++        }
++        File fbxDir = fbxFile.getParentFile();
++        String basePath = "fbx/"
++                + new SimpleDateFormat("yyyy-MM_dd-HH").format(new Date()) + "/"
++                + RandomStringUtils.randomAlphabetic(16);
++        List<String> urls = new ArrayList<>();
++        for (File listFile : fbxDir.listFiles()) {
++            if (!listFile.isHidden() && !listFile.isDirectory()) {
++                urls.add(storageService.uploadFromInputStream(new FileInputStream(listFile), basePath + "/" + listFile.getName()));
++            }
++        }
++        String fbxUrl = urls.stream().filter(s -> s.toLowerCase().endsWith(".fbx")).findAny()
++                .orElseThrow(new BusinessException("找不到fbx文件"));
++        return new FileObject(fbxFile.getName(), fbxUrl, null, "fbx");
++    }
+ }
+diff --git a/src/main/vue/src/components/FileUpload.vue b/src/main/vue/src/components/FileUpload.vue
+index beed236..e5257e1 100644
+--- a/src/main/vue/src/components/FileUpload.vue
++++ b/src/main/vue/src/components/FileUpload.vue
+@@ -1,13 +1,14 @@
+ <template>
+     <el-upload
+         class="file-upload"
+-        :action="uploadUrl"
++        :action="customUrl || uploadUrl"
+         :on-success="onSuccess"
+         :headers="headers"
+         :file-list="fileList"
+         :limit="filesLimit"
+         :on-exceed="onExceed"
+         :on-preview="onPreview"
++        :accept="accept || '*/*'"
+         ref="upload"
+     >
+         <el-button type="primary" size="mini" slot="trigger"> 点击上传 </el-button>
+@@ -62,7 +63,9 @@ export default {
+         format: {
+             type: String,
+             default: 'string'
+-        }
++        },
++        customUrl: {},
++        accept: {}
+     },
+     data() {
+         return {
+diff --git a/src/main/vue/src/views/BlindBoxEdit.vue b/src/main/vue/src/views/BlindBoxEdit.vue
+index 773a3aa..2459522 100644
+--- a/src/main/vue/src/views/BlindBoxEdit.vue
++++ b/src/main/vue/src/views/BlindBoxEdit.vue
+@@ -189,6 +189,7 @@
+     </div>
+ </template>
+ <script>
++import resolveUrl from 'resolve-url';
+ export default {
+     name: 'BlindBoxEdit',
+     created() {
+@@ -428,7 +429,8 @@ export default {
+                 id: [{ required: true, message: '请选择作品' }],
+                 total: [{ required: true, message: '请输入数量' }]
+             },
+-            cateogories: ['勋章', '收藏品', '数字艺术', '门票', '游戏', '音乐', '使用', '其他']
++            cateogories: ['勋章', '收藏品', '数字艺术', '门票', '游戏', '音乐', '使用', '其他'],
++            customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel')
+         };
+     },
+     methods: {
+diff --git a/src/main/vue/src/views/CollectionEdit.vue b/src/main/vue/src/views/CollectionEdit.vue
+index d794f0d..5574fc7 100644
+--- a/src/main/vue/src/views/CollectionEdit.vue
++++ b/src/main/vue/src/views/CollectionEdit.vue
+@@ -29,6 +29,15 @@
+                         ></object-upload>
+                         <div class="tip">支持JPG、PNG、GIF、MP4,推荐长宽比1:1</div>
+                     </el-form-item>
++                    <el-form-item prop="model3d" label="3D模型">
++                        <file-upload
++                            :limit="1"
++                            v-model="formData.model3d"
++                            :customUrl="customUrl"
++                            accept="application/zip"
++                        ></file-upload>
++                        <div class="tip">请将FBX文件与贴图打包成zip压缩包上传</div>
++                    </el-form-item>
+                     <el-form-item prop="minterId" label="铸造者">
+                         <minter-select
+                             v-model="formData.minterId"
+@@ -221,6 +230,7 @@
+     </div>
+ </template>
+ <script>
++import resolveUrl from 'resolve-url';
+ export default {
+     name: 'CollectionEdit',
+     created() {
+@@ -418,7 +428,8 @@ export default {
+             privelegeRules: {
+                 detail: [{ required: true, message: '请填写内容' }],
+                 remark: [{ required: true, message: '请填写说明' }]
+-            }
++            },
++            customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel')
+         };
+     },
+     methods: {
+-- 
+2.30.1 (Apple Git-130)
+

+ 431 - 0
awesome_admin_v2.sql

@@ -0,0 +1,431 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : 微球
+ Source Server Type    : MySQL
+ Source Server Version : 50616
+ Source Host           : rdsave1o67m1ido6gwp6public.mysql.rds.aliyuncs.com:3306
+ Source Schema         : awesome_admin_v2
+
+ Target Server Type    : MySQL
+ Target Server Version : 50616
+ File Encoding         : 65001
+
+ Date: 07/08/2020 17:51:04
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for authority
+-- ----------------------------
+DROP TABLE IF EXISTS `authority`;
+CREATE TABLE `authority` (
+  `name` varchar(50) NOT NULL,
+  `description` varchar(50) NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `authority` VALUES ('ROLE_ADMIN', '管理员');
+INSERT INTO `authority` VALUES ('ROLE_DEV', '开发者');
+INSERT INTO `authority` VALUES ('ROLE_USER', '普通用户');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for child
+-- ----------------------------
+DROP TABLE IF EXISTS `child`;
+CREATE TABLE `child` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `child_name` varchar(255) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7dag1cncltpyhoc2mbwka356h` (`parent_id`),
+  CONSTRAINT `FK7dag1cncltpyhoc2mbwka356h` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of child
+-- ----------------------------
+BEGIN;
+INSERT INTO `child` VALUES (3, '2019-12-25 11:17:02', 'system', '2019-12-25 11:17:02', 'system', '222', 2);
+INSERT INTO `child` VALUES (5, '2019-12-25 11:18:19', 'system', '2019-12-25 11:18:19', 'system', '222', 4);
+INSERT INTO `child` VALUES (7, '2019-12-25 14:50:50', 'system', '2019-12-25 14:50:50', 'system', '222', 6);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for city
+-- ----------------------------
+DROP TABLE IF EXISTS `city`;
+CREATE TABLE `city` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for district
+-- ----------------------------
+DROP TABLE IF EXISTS `district`;
+CREATE TABLE `district` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `full_name` varchar(255) DEFAULT NULL,
+  `lat` double NOT NULL,
+  `leaf` bit(1) NOT NULL,
+  `level` int(11) NOT NULL,
+  `lng` double NOT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `parent` bigint(20) DEFAULT NULL,
+  `pinyin` varchar(255) DEFAULT NULL,
+  `child_count` int(11) NOT NULL,
+  `city_code` varchar(10) DEFAULT NULL,
+  `city_count` int(11) NOT NULL,
+  `district_count` int(11) NOT NULL,
+  `street_count` int(11) NOT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for guid_package
+-- ----------------------------
+DROP TABLE IF EXISTS `guid_package`;
+CREATE TABLE `guid_package` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `price` decimal(10,2) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for guide
+-- ----------------------------
+DROP TABLE IF EXISTS `guide`;
+CREATE TABLE `guide` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `sort` int(11) NOT NULL,
+  `text` text,
+  `type` varchar(255) DEFAULT NULL,
+  `video` varchar(255) DEFAULT NULL,
+  `voice` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for hibernate_sequence
+-- ----------------------------
+DROP TABLE IF EXISTS `hibernate_sequence`;
+CREATE TABLE `hibernate_sequence` (
+  `next_val` bigint(20) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of hibernate_sequence
+-- ----------------------------
+BEGIN;
+INSERT INTO `hibernate_sequence` VALUES (15);
+INSERT INTO `hibernate_sequence` VALUES (15);
+INSERT INTO `hibernate_sequence` VALUES (15);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for menu
+-- ----------------------------
+DROP TABLE IF EXISTS `menu`;
+CREATE TABLE `menu` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `active` bit(1) DEFAULT NULL,
+  `enabled` bit(1) DEFAULT NULL,
+  `icon` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `parent` bigint(20) DEFAULT NULL,
+  `path` varchar(255) DEFAULT NULL,
+  `root` bit(1) DEFAULT NULL,
+  `sort` int(11) DEFAULT NULL,
+  `category` varchar(255) DEFAULT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `menu` VALUES (3, NULL, NULL, '2020-07-31 14:31:40', '管理员(1)', b'1', b'1', 'fas fa-desktop', '开发', 2, '', b'1', 4, '开发', b'0');
+INSERT INTO `menu` VALUES (4, NULL, NULL, '2020-07-31 14:31:51', '管理员(1)', b'1', b'1', 'fas fa-code', '代码生成', 3, '/genCodeList', b'0', 5, '开发', b'0');
+INSERT INTO `menu` VALUES (5, NULL, NULL, '2020-07-31 14:31:56', '管理员(1)', b'1', b'1', 'fas fa-bug', '接口调试', 3, '/api', b'0', 6, '开发', b'0');
+INSERT INTO `menu` VALUES (6, NULL, NULL, '2020-07-09 15:01:05', '管理员(1)', b'1', b'1', 'fas fa-user', '用户管理', 2, '/userList', b'1', 2, '用户', b'0');
+INSERT INTO `menu` VALUES (8, '2020-07-09 15:01:40', NULL, '2020-07-09 15:01:45', '管理员(1)', b'1', b'1', '', '菜单权限', 9, '/menuAuthority', b'0', 8, NULL, b'0');
+INSERT INTO `menu` VALUES (9, NULL, NULL, '2020-07-09 15:01:10', '管理员(1)', b'1', b'1', 'fas fa-cog', '配置', 2, '', b'1', 3, '系统', b'0');
+INSERT INTO `menu` VALUES (10, NULL, NULL, '2020-07-29 16:54:21', '管理员(1)', b'1', b'1', '', '菜单配置', 9, '/menus', b'0', 7, '系统', b'0');
+INSERT INTO `menu` VALUES (14, NULL, NULL, '2020-07-09 15:01:45', '管理员(1)', b'1', b'1', '', '参数配置', 9, '/sysConfigList', b'0', 9, '系统', b'0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for menu_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `menu_authority`;
+CREATE TABLE `menu_authority` (
+  `menu_id` bigint(20) NOT NULL,
+  `authority` varchar(50) NOT NULL,
+  PRIMARY KEY (`menu_id`,`authority`),
+  KEY `FKlj7sftrck7uk1kcjsy0doo56` (`authority`),
+  CONSTRAINT `FK4hopjqfvkhdagmk110y1jk17q` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`),
+  CONSTRAINT `FKlj7sftrck7uk1kcjsy0doo56` FOREIGN KEY (`authority`) REFERENCES `authority` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of menu_authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `menu_authority` VALUES (6, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (8, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (9, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (10, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (14, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (3, 'ROLE_DEV');
+INSERT INTO `menu_authority` VALUES (4, 'ROLE_DEV');
+INSERT INTO `menu_authority` VALUES (5, 'ROLE_DEV');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for parent
+-- ----------------------------
+DROP TABLE IF EXISTS `parent`;
+CREATE TABLE `parent` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `parent_name` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of parent
+-- ----------------------------
+BEGIN;
+INSERT INTO `parent` VALUES (2, '2019-12-25 11:17:02', 'system', '2019-12-25 11:17:02', 'system', '111');
+INSERT INTO `parent` VALUES (4, '2019-12-25 11:18:19', 'system', '2019-12-25 11:18:19', 'system', '111');
+INSERT INTO `parent` VALUES (6, '2019-12-25 14:50:49', 'system', '2019-12-25 14:50:49', 'system', '111');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for scenic
+-- ----------------------------
+DROP TABLE IF EXISTS `scenic`;
+CREATE TABLE `scenic` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `city_id` bigint(20) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for scenic_spot
+-- ----------------------------
+DROP TABLE IF EXISTS `scenic_spot`;
+CREATE TABLE `scenic_spot` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `city_id` bigint(20) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  `scenic_id` bigint(20) DEFAULT NULL,
+  `x` int(11) NOT NULL,
+  `y` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for sms_record
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_record`;
+CREATE TABLE `sms_record` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `code` varchar(255) DEFAULT NULL,
+  `expired` bit(1) DEFAULT NULL,
+  `expires_at` datetime DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `scope` varchar(255) DEFAULT NULL,
+  `session_id` varchar(255) DEFAULT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for spot
+-- ----------------------------
+DROP TABLE IF EXISTS `spot`;
+CREATE TABLE `spot` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for spot_guid
+-- ----------------------------
+DROP TABLE IF EXISTS `spot_guid`;
+CREATE TABLE `spot_guid` (
+  `spot_id` bigint(20) NOT NULL,
+  `guid_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`spot_id`,`guid_id`),
+  KEY `FKbftxye4brw7vfq05d7rv0mtca` (`guid_id`),
+  CONSTRAINT `FK5xnrr1sk76u0xy3j15p6r96q9` FOREIGN KEY (`spot_id`) REFERENCES `scenic_spot` (`id`),
+  CONSTRAINT `FKbftxye4brw7vfq05d7rv0mtca` FOREIGN KEY (`guid_id`) REFERENCES `guide` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for super_user
+-- ----------------------------
+DROP TABLE IF EXISTS `super_user`;
+CREATE TABLE `super_user` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `avatar` varchar(255) DEFAULT NULL,
+  `enabled` bit(1) NOT NULL,
+  `nickname` varchar(255) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `username` varchar(50) NOT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UK_jfokkpxg19r117eil158ooo9d` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for super_user_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `super_user_authority`;
+CREATE TABLE `super_user_authority` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`),
+  KEY `FKxjxyodse6n0n00ewvoq8xv8` (`authority_name`),
+  CONSTRAINT `FK7p0vq0b1vy9f0sqvgrdqw1q70` FOREIGN KEY (`user_id`) REFERENCES `super_user` (`id`),
+  CONSTRAINT `FKxjxyodse6n0n00ewvoq8xv8` FOREIGN KEY (`authority_name`) REFERENCES `authority` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for sys_config
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_config`;
+CREATE TABLE `sys_config` (
+  `name` varchar(25) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `description` varchar(255) DEFAULT NULL,
+  `type` varchar(255) DEFAULT NULL,
+  `value` varchar(255) DEFAULT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for user
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `avatar` varchar(255) DEFAULT NULL,
+  `city` varchar(255) DEFAULT NULL,
+  `country` varchar(255) DEFAULT NULL,
+  `email` varchar(255) DEFAULT NULL,
+  `language` varchar(255) DEFAULT NULL,
+  `nickname` varchar(255) DEFAULT NULL,
+  `open_id` varchar(255) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `province` varchar(255) DEFAULT NULL,
+  `sex` varchar(255) DEFAULT NULL,
+  `username` varchar(50) NOT NULL,
+  `del` bit(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of user
+-- ----------------------------
+BEGIN;
+INSERT INTO `user` VALUES (1, '2020-03-07 18:34:36', NULL, '2020-07-31 14:32:10', '管理员(1)', 'https://zhumj.oss-cn-hangzhou.aliyuncs.com/image/user.jpg', NULL, NULL, NULL, NULL, '管理员', NULL, '$2a$10$ztJSpmth7fPgDK0I5stwnO59e5ht2copnDL8lyt2xif6lXBzk/ZdO', NULL, NULL, NULL, 'root', b'0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for user_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `user_authority`;
+CREATE TABLE `user_authority` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of user_authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `user_authority` VALUES (1, 'ROLE_ADMIN');
+INSERT INTO `user_authority` VALUES (1, 'ROLE_DEV');
+INSERT INTO `user_authority` VALUES (1, 'ROLE_USER');
+INSERT INTO `user_authority` VALUES (11, 'ROLE_USER');
+INSERT INTO `user_authority` VALUES (14, 'ROLE_ADMIN');
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 15 - 0
build_all.sh

@@ -0,0 +1,15 @@
+git checkout
+git pull
+(cd src/main/vue && yarn)
+(cd src/main/vue && yarn build)
+rsync -av --delete src/main/vue/dist/ /var/www/9th/admin/
+(cd src/main/nine-space && yarn)
+(cd src/main/nine-space && yarn build)
+rsync -av --delete src/main/nine-space/dist/ /var/www/9th/9th/
+(cd src/main/pc-space && yarn)
+(cd src/main/pc-space && yarn build)
+rsync -av --delete src/main/pc-space/dist/ /var/www/9th/9th-pc/
+mvn clean package
+systemctl stop 9th
+mv -f target/9th-0.0.1-SNAPSHOT.jar /var/www/9th/9th-0.0.1-SNAPSHOT.jar
+systemctl start 9th

+ 6 - 0
build_h5.sh

@@ -0,0 +1,6 @@
+git checkout
+git pull
+mvn clean package
+systemctl stop 9th
+mv -f target/9th-0.0.1-SNAPSHOT.jar /var/www/9th/9th-0.0.1-SNAPSHOT.jar
+systemctl start 9th

+ 15 - 0
build_jar.sh

@@ -0,0 +1,15 @@
+git checkout
+git pull
+(cd src/main/vue && yarn)
+(cd src/main/vue && yarn build)
+rsync -av --delete src/main/vue/dist/ /var/www/9th/admin/
+(cd src/main/nine-space && yarn)
+(cd src/main/nine-space && yarn build)
+rsync -av --delete src/main/nine-space/dist/ /var/www/9th/9th/
+(cd src/main/pc-space && yarn)
+(cd src/main/pc-space && yarn build)
+rsync -av --delete src/main/pc-space/dist/ /var/www/9th/9th-pc/
+mvn clean package
+systemctl stop 9th
+mv -f target/9th-0.0.1-SNAPSHOT.jar /var/www/9th/9th-0.0.1-SNAPSHOT.jar
+systemctl start 9th

+ 12 - 0
cleadData.sql

@@ -0,0 +1,12 @@
+delete from order_info;
+delete from collection_info;
+delete from asset;
+delete from like_info;
+delete from blind_box_item;
+delete from appointment;
+delete from user_authority where user_id > 1;
+delete from user_address;
+delete from user_token;
+delete from banner;
+delete from identity_auth;
+delete from user where id > 196;

+ 8 - 0
install-jar.sh

@@ -0,0 +1,8 @@
+
+mvn org.apache.maven.plugins:maven-install-plugin:2.5.1:install-file -DgroupId=org.libjpegturbo -DartifactId=mozjpeg4j -Dpackaging=jar -Dversion=1.1 -Dfile=lib/mozjpeg4j-1.1.jar -DlocalRepositoryPath=libs
+
+mvn org.apache.maven.plugins:maven-install-plugin:2.5.1:install-file -DgroupId=org.pngquant -DartifactId=pngquant4j -Dpackaging=jar -Dversion=1.0.1 -Dfile=lib/pngquant4j-1.0.1.jar -DlocalRepositoryPath=libs
+
+mvn install:install-file -DgroupId=com.antfinancial.baas -DartifactId=mychain-rest-lib -Dpackaging=jar -Dversion=0.10.2.11 -Dfile=lib/mychain-rest-client-0.10.2.11-with-dependencies.jar
+
+mvn install:install-file -DgroupId=com.alipay.mychainx -DartifactId=mychainx-sdk -Dpackaging=jar -Dversion=0.10.2.9 -Dfile=lib/mychainx-java-sdk-0.10.2.9-with-dependencies.jar

BIN
lib/ipfs-multihash-v1.3.0.jar


BIN
lib/mozjpeg4j-1.1.jar


BIN
lib/mychain-rest-client-0.10.2.11-with-dependencies.jar


BIN
lib/mychainx-java-sdk-0.10.2.9-with-dependencies.jar


BIN
lib/pngquant4j-1.0.1.jar


BIN
libs/org/libjpegturbo/mozjpeg4j/1.1/mozjpeg4j-1.1.jar


+ 9 - 0
libs/org/libjpegturbo/mozjpeg4j/1.1/mozjpeg4j-1.1.pom

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.libjpegturbo</groupId>
+  <artifactId>mozjpeg4j</artifactId>
+  <version>1.1</version>
+  <description>POM was created from install:install-file</description>
+</project>

+ 12 - 0
libs/org/libjpegturbo/mozjpeg4j/maven-metadata-local.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>org.libjpegturbo</groupId>
+  <artifactId>mozjpeg4j</artifactId>
+  <versioning>
+    <release>1.1</release>
+    <versions>
+      <version>1.1</version>
+    </versions>
+    <lastUpdated>20211106131217</lastUpdated>
+  </versioning>
+</metadata>

BIN
libs/org/pngquant/pngquant4j/1.0.1/pngquant4j-1.0.1.jar


+ 9 - 0
libs/org/pngquant/pngquant4j/1.0.1/pngquant4j-1.0.1.pom

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.pngquant</groupId>
+  <artifactId>pngquant4j</artifactId>
+  <version>1.0.1</version>
+  <description>POM was created from install:install-file</description>
+</project>

+ 12 - 0
libs/org/pngquant/pngquant4j/maven-metadata-local.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>org.pngquant</groupId>
+  <artifactId>pngquant4j</artifactId>
+  <versioning>
+    <release>1.0.1</release>
+    <versions>
+      <version>1.0.1</version>
+    </versions>
+    <lastUpdated>20211106131218</lastUpdated>
+  </versioning>
+</metadata>

+ 286 - 0
mvnw

@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+  # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        wget "$jarUrl" -O "$wrapperJarPath"
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        curl -o "$wrapperJarPath" "$jarUrl"
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 161 - 0
mvnw.cmd

@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+	IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    echo Found %WRAPPER_JAR%
+) else (
+    echo Couldn't find %WRAPPER_JAR%, downloading it ...
+	echo Downloading from: %DOWNLOAD_URL%
+    powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+    echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%

+ 4 - 0
pics2pic.sql

@@ -0,0 +1,4 @@
+alter table collection_info change pics pic text null;
+
+
+alter table blind_box_item change pics pic text null;

+ 425 - 0
pom.xml

@@ -0,0 +1,425 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.6.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.izouma</groupId>
+    <artifactId>adcs</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>adcs</name>
+    <description>adcs</description>
+
+    <repositories>
+        <repository>
+            <id>nexus-aliyun</id>
+            <url>https://maven.aliyun.com/repository/central</url>
+        </repository>
+        <repository>
+            <id>Local repository</id>
+            <url>file://${basedir}/libs</url>
+        </repository>
+        <repository>
+            <id>jitpack.io</id>
+            <url>https://jitpack.io</url>
+        </repository>
+    </repositories>
+
+    <properties>
+        <java.version>11</java.version>
+        <skipTests>true</skipTests>
+        <poi.verion>3.17</poi.verion>
+        <javawx.version>3.5.0</javawx.version>
+        <aliyun.oss.version>2.8.3</aliyun.oss.version>
+        <aliyun.core.version>4.1.0</aliyun.core.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.retry</groupId>
+            <artifactId>spring-retry</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aspects</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-envers</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.9.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-mp</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-miniapp</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-pay</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-open</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>${aliyun.oss.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>${aliyun.core.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.kevinsawicki</groupId>
+            <artifactId>http-request</artifactId>
+            <version>6.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>${poi.verion}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>${poi.verion}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib</artifactId>
+            <version>3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>2.2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.11</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>1.7</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-tools</artifactId>
+            <version>2.0</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.belerweb/pinyin4j -->
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.1</version>
+        </dependency>
+        <!-- swagger -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.37</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ning</groupId>
+            <artifactId>async-http-client</artifactId>
+            <version>1.9.32</version>
+        </dependency>
+        <!-- 钉钉 -->
+
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.libjpegturbo</groupId>
+            <artifactId>mozjpeg4j</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.pngquant</groupId>
+            <artifactId>pngquant4j</artifactId>
+            <version>1.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.antfinancial.baas</groupId>
+            <artifactId>mychain-rest-lib</artifactId>
+            <version>0.10.2.11</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-tcnative-boringssl-static</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.16.57.ALL</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ipfs</groupId>
+            <artifactId>java-ipfs-http-client</artifactId>
+            <version>v1.3.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.javafaker</groupId>
+            <artifactId>javafaker</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv-platform</artifactId>
+            <version>1.5.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-hibernate5</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-hibernate4</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>net.coobird</groupId>
+            <artifactId>thumbnailator</artifactId>
+            <version>0.4.17</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.huifu.adapay.core</groupId>
+            <artifactId>adapay-core-sdk</artifactId>
+            <version>1.2.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.huifu.adapay</groupId>
+            <artifactId>adapay-java-sdk</artifactId>
+            <version>1.2.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.2.8</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-validator</groupId>
+            <artifactId>commons-validator</artifactId>
+            <version>1.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+            <version>5.7.21</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.vladimir-bukhtoyarov</groupId>
+            <artifactId>bucket4j-core</artifactId>
+            <version>7.3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.marschall</groupId>
+            <artifactId>hibernate-batch-sequence-generator</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 33 - 0
src/main/bench/index.js

@@ -0,0 +1,33 @@
+const axios = require("axios");
+const qs = require("qs");
+setInterval(() => {}, 1 << 30);
+
+axios.defaults.baseURL = "http://localhost:8080";
+axios
+    .post(
+        "/auth/phonePwdLogin",
+        qs.stringify({ phone: "15077886171", password: "123456" })
+    )
+    .then((res) => {
+        console.log(res.data);
+        axios.defaults.headers["Authorization"] = "Bearer " + res.data;
+        axios.get("/user/my").then((res) => {
+            console.log(res.data);
+        });
+        for (let i = 0; i < 10000; i++) {
+            axios
+                .post(
+                    "/order/create",
+                    qs.stringify({ collectionId: 7669, qty: 1 })
+                )
+                .then((res) => {
+                    console.log(res.data);
+                    setTimeout(() => {
+                        axios.post(
+                            "/order/testNotify",
+                            qs.stringify({ id: res.data.id })
+                        );
+                    }, Math.random() * 1000 + 100);
+                });
+        }
+    });

+ 6 - 0
src/main/bench/package.json

@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "axios": "^0.24.0",
+    "qs": "^6.10.2"
+  }
+}

+ 70 - 0
src/main/bench/yarn.lock

@@ -0,0 +1,70 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+axios@^0.24.0:
+  version "0.24.0"
+  resolved "https://registry.npmmirror.com/axios/download/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
+  integrity sha1-gE5voeS5xSiFAd2d/1anoJQNINY=
+  dependencies:
+    follow-redirects "^1.14.4"
+
+call-bind@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.nlark.com/call-bind/download/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+  integrity sha1-sdTonmiBGcPJqQOtMKuy9qkZvjw=
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.2"
+
+follow-redirects@^1.14.4:
+  version "1.14.6"
+  resolved "https://registry.npmmirror.com/follow-redirects/download/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
+  integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=
+
+get-intrinsic@^1.0.2:
+  version "1.1.1"
+  resolved "https://registry.nlark.com/get-intrinsic/download/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+  integrity sha1-FfWfN2+FXERpY5SPDSTNNje0q8Y=
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+
+has-symbols@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.nlark.com/has-symbols/download/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
+  integrity sha1-Fl0wcMADCXUqEjakeTMeOsVvFCM=
+
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.nlark.com/has/download/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=
+  dependencies:
+    function-bind "^1.1.1"
+
+object-inspect@^1.9.0:
+  version "1.11.1"
+  resolved "https://registry.npmmirror.com/object-inspect/download/object-inspect-1.11.1.tgz#d4bd7d7de54b9a75599f59a00bd698c1f1c6549b"
+  integrity sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==
+
+qs@^6.10.2:
+  version "6.10.2"
+  resolved "https://registry.npmmirror.com/qs/download/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe"
+  integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==
+  dependencies:
+    side-channel "^1.0.4"
+
+side-channel@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.nlark.com/side-channel/download/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+  integrity sha1-785cj9wQTudRslxY1CkAEfpeos8=
+  dependencies:
+    call-bind "^1.0.0"
+    get-intrinsic "^1.0.2"
+    object-inspect "^1.9.0"

+ 1 - 0
src/main/contract/.gitignore

@@ -0,0 +1 @@
+node_modules

+ 1080 - 0
src/main/contract/9th.sol

@@ -0,0 +1,1080 @@
+pragma solidity ^0.6.3;
+
+library Strings {
+    function toString(uint256 value) internal pure returns (string memory) {
+        // Inspired by OraclizeAPI's implementation - MIT licence
+        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
+
+        if (value == 0) {
+            return "0";
+        }
+        uint256 temp = value;
+        uint256 digits;
+        while (temp != 0) {
+            digits++;
+            temp /= 10;
+        }
+        bytes memory buffer = new bytes(digits);
+        uint256 index = digits - 1;
+        temp = value;
+        while (temp != 0) {
+            buffer[index--] = bytes1(uint8(48 + (temp % 10)));
+            temp /= 10;
+        }
+        return string(buffer);
+    }
+}
+
+library EnumerableMap {
+    struct MapEntry {
+        bytes32 _key;
+        bytes32 _value;
+    }
+
+    struct Map {
+        // Storage of map keys and values
+        MapEntry[] _entries;
+        // Position of the entry defined by a key in the `entries` array, plus 1
+        // because index 0 means a key is not in the map.
+        mapping(bytes32 => uint256) _indexes;
+    }
+
+    function _set(
+        Map storage map,
+        bytes32 key,
+        bytes32 value
+    ) private returns (bool) {
+        // We read and store the key's index to prevent multiple reads from the same storage slot
+        uint256 keyIndex = map._indexes[key];
+
+        if (keyIndex == 0) {
+            // Equivalent to !contains(map, key)
+            map._entries.push(MapEntry({_key: key, _value: value}));
+            // The entry is stored at length-1, but we add 1 to all indexes
+            // and use 0 as a sentinel value
+            map._indexes[key] = map._entries.length;
+            return true;
+        } else {
+            map._entries[keyIndex - 1]._value = value;
+            return false;
+        }
+    }
+
+    function _remove(Map storage map, bytes32 key) private returns (bool) {
+        // We read and store the key's index to prevent multiple reads from the same storage slot
+        uint256 keyIndex = map._indexes[key];
+
+        if (keyIndex != 0) {
+            // Equivalent to contains(map, key)
+            // To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one
+            // in the array, and then remove the last entry (sometimes called as 'swap and pop').
+            // This modifies the order of the array, as noted in {at}.
+
+            uint256 toDeleteIndex = keyIndex - 1;
+            uint256 lastIndex = map._entries.length - 1;
+
+            // When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs
+            // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
+
+            MapEntry storage lastEntry = map._entries[lastIndex];
+
+            // Move the last entry to the index where the entry to delete is
+            map._entries[toDeleteIndex] = lastEntry;
+            // Update the index for the moved entry
+            map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based
+
+            // Delete the slot where the moved entry was stored
+            map._entries.pop();
+
+            // Delete the index for the deleted slot
+            delete map._indexes[key];
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    function _contains(Map storage map, bytes32 key)
+        private
+        view
+        returns (bool)
+    {
+        return map._indexes[key] != 0;
+    }
+
+    function _length(Map storage map) private view returns (uint256) {
+        return map._entries.length;
+    }
+
+    function _at(Map storage map, uint256 index)
+        private
+        view
+        returns (bytes32, bytes32)
+    {
+        require(
+            map._entries.length > index,
+            "EnumerableMap: index out of bounds"
+        );
+
+        MapEntry storage entry = map._entries[index];
+        return (entry._key, entry._value);
+    }
+
+    function _tryGet(Map storage map, bytes32 key)
+        private
+        view
+        returns (bool, bytes32)
+    {
+        uint256 keyIndex = map._indexes[key];
+        if (keyIndex == 0) return (false, 0); // Equivalent to contains(map, key)
+        return (true, map._entries[keyIndex - 1]._value); // All indexes are 1-based
+    }
+
+    function _get(Map storage map, bytes32 key) private view returns (bytes32) {
+        uint256 keyIndex = map._indexes[key];
+        require(keyIndex != 0, "EnumerableMap: nonexistent key"); // Equivalent to contains(map, key)
+        return map._entries[keyIndex - 1]._value; // All indexes are 1-based
+    }
+
+    function _get(
+        Map storage map,
+        bytes32 key,
+        string memory errorMessage
+    ) private view returns (bytes32) {
+        uint256 keyIndex = map._indexes[key];
+        require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key)
+        return map._entries[keyIndex - 1]._value; // All indexes are 1-based
+    }
+
+    // UintToAddressMap
+
+    struct UintToAddressMap {
+        Map _inner;
+    }
+
+    function set(
+        UintToAddressMap storage map,
+        uint256 key,
+        identity value
+    ) internal returns (bool) {
+        return _set(map._inner, bytes32(key), bytes32(uint256(value)));
+    }
+
+    function remove(UintToAddressMap storage map, uint256 key)
+        internal
+        returns (bool)
+    {
+        return _remove(map._inner, bytes32(key));
+    }
+
+    function contains(UintToAddressMap storage map, uint256 key)
+        internal
+        view
+        returns (bool)
+    {
+        return _contains(map._inner, bytes32(key));
+    }
+
+    function length(UintToAddressMap storage map)
+        internal
+        view
+        returns (uint256)
+    {
+        return _length(map._inner);
+    }
+
+    function at(UintToAddressMap storage map, uint256 index)
+        internal
+        view
+        returns (uint256, identity)
+    {
+        (bytes32 key, bytes32 value) = _at(map._inner, index);
+        return (uint256(key), identity(uint256(value)));
+    }
+
+    function tryGet(UintToAddressMap storage map, uint256 key)
+        internal
+        view
+        returns (bool, identity)
+    {
+        (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key));
+        return (success, identity(uint256(value)));
+    }
+
+    function get(UintToAddressMap storage map, uint256 key)
+        internal
+        view
+        returns (identity)
+    {
+        return identity(uint256(_get(map._inner, bytes32(key))));
+    }
+
+    function get(
+        UintToAddressMap storage map,
+        uint256 key,
+        string memory errorMessage
+    ) internal view returns (identity) {
+        return
+            identity(
+                uint256(_get(map._inner, bytes32(key), errorMessage))
+            );
+    }
+}
+
+library EnumerableSet {
+
+    struct Set {
+        bytes32[] _values;
+
+        mapping (bytes32 => uint256) _indexes;
+    }
+
+    function _add(Set storage set, bytes32 value) private returns (bool) {
+        if (!_contains(set, value)) {
+            set._values.push(value);
+            set._indexes[value] = set._values.length;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    function _remove(Set storage set, bytes32 value) private returns (bool) {
+        uint256 valueIndex = set._indexes[value];
+
+        if (valueIndex != 0) {
+
+            uint256 toDeleteIndex = valueIndex - 1;
+            uint256 lastIndex = set._values.length - 1;
+
+            bytes32 lastvalue = set._values[lastIndex];
+
+            set._values[toDeleteIndex] = lastvalue;
+            set._indexes[lastvalue] = toDeleteIndex + 1;
+
+            set._values.pop();
+
+            delete set._indexes[value];
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    function _contains(Set storage set, bytes32 value) private view returns (bool) {
+        return set._indexes[value] != 0;
+    }
+
+    function _length(Set storage set) private view returns (uint256) {
+        return set._values.length;
+    }
+
+    function _at(Set storage set, uint256 index) private view returns (bytes32) {
+        require(set._values.length > index, "EnumerableSet: index out of bounds");
+        return set._values[index];
+    }
+
+
+    struct Bytes32Set {
+        Set _inner;
+    }
+
+    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
+        return _add(set._inner, value);
+    }
+
+    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
+        return _remove(set._inner, value);
+    }
+
+    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
+        return _contains(set._inner, value);
+    }
+
+    function length(Bytes32Set storage set) internal view returns (uint256) {
+        return _length(set._inner);
+    }
+
+    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
+        return _at(set._inner, index);
+    }
+
+    struct AddressSet {
+        Set _inner;
+    }
+
+    function add(AddressSet storage set, identity value) internal returns (bool) {
+        return _add(set._inner, bytes32(uint256(value)));
+    }
+
+    function remove(AddressSet storage set, identity value) internal returns (bool) {
+        return _remove(set._inner, bytes32(uint256(value)));
+    }
+
+    function contains(AddressSet storage set, identity value) internal view returns (bool) {
+        return _contains(set._inner, bytes32(uint256(value)));
+    }
+
+    function length(AddressSet storage set) internal view returns (uint256) {
+        return _length(set._inner);
+    }
+
+    function at(AddressSet storage set, uint256 index) internal view returns (identity) {
+        return identity(uint256(_at(set._inner, index)));
+    }
+
+    struct UintSet {
+        Set _inner;
+    }
+
+    function add(UintSet storage set, uint256 value) internal returns (bool) {
+        return _add(set._inner, bytes32(value));
+    }
+
+    function remove(UintSet storage set, uint256 value) internal returns (bool) {
+        return _remove(set._inner, bytes32(value));
+    }
+
+    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
+        return _contains(set._inner, bytes32(value));
+    }
+
+    function length(UintSet storage set) internal view returns (uint256) {
+        return _length(set._inner);
+    }
+
+    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
+        return uint256(_at(set._inner, index));
+    }
+}
+
+library Address {
+
+    function isContract(identity account) internal view returns (bool) {
+        // This method relies on extcodesize, which returns 0 for contracts in
+        // construction, since the code is only stored at the end of the
+        // constructor execution.
+
+        uint256 size;
+        // solhint-disable-next-line no-inline-assembly
+        assembly { size := extcodesize(account) }
+        return size > 0;
+    }
+
+    function sendValue(identity recipient, uint256 amount) internal {
+        require(identity(this).balance >= amount, "Address: insufficient balance");
+
+        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
+        (bool success, ) = recipient.call{ value: amount }("");
+        require(success, "Address: unable to send value, recipient may have reverted");
+    }
+
+    function functionCall(identity target, bytes memory data) internal returns (bytes memory) {
+      return functionCall(target, data, "Address: low-level call failed");
+    }
+
+    function functionCall(identity target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
+        return functionCallWithValue(target, data, 0, errorMessage);
+    }
+
+    function functionCallWithValue(identity target, bytes memory data, uint256 value) internal returns (bytes memory) {
+        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
+    }
+
+    function functionCallWithValue(identity target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
+        require(identity(this).balance >= value, "Address: insufficient balance for call");
+        require(isContract(target), "Address: call to non-contract");
+
+        // solhint-disable-next-line avoid-low-level-calls
+        (bool success, bytes memory returndata) = target.call{ value: value }(data);
+        return _verifyCallResult(success, returndata, errorMessage);
+    }
+
+    function functionStaticCall(identity target, bytes memory data) internal view returns (bytes memory) {
+        return functionStaticCall(target, data, "Address: low-level static call failed");
+    }
+
+    function functionStaticCall(identity target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
+        require(isContract(target), "Address: static call to non-contract");
+
+        // solhint-disable-next-line avoid-low-level-calls
+        (bool success, bytes memory returndata) = target.staticcall(data);
+        return _verifyCallResult(success, returndata, errorMessage);
+    }
+
+    function functionDelegateCall(identity target, bytes memory data) internal returns (bytes memory) {
+        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
+    }
+
+    function functionDelegateCall(identity target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
+        require(isContract(target), "Address: delegate call to non-contract");
+
+        // solhint-disable-next-line avoid-low-level-calls
+        (bool success, bytes memory returndata) = target.delegatecall(data);
+        return _verifyCallResult(success, returndata, errorMessage);
+    }
+
+    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
+        if (success) {
+            return returndata;
+        } else {
+            // Look for revert reason and bubble it up if present
+            if (returndata.length > 0) {
+                // The easiest way to bubble the revert reason is using memory via assembly
+
+                // solhint-disable-next-line no-inline-assembly
+                assembly {
+                    let returndata_size := mload(returndata)
+                    revert(add(32, returndata), returndata_size)
+                }
+            } else {
+                revert(errorMessage);
+            }
+        }
+    }
+}
+
+library SafeMath {
+
+    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
+        uint256 c = a + b;
+        if (c < a) return (false, 0);
+        return (true, c);
+    }
+
+    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
+        if (b > a) return (false, 0);
+        return (true, a - b);
+    }
+
+    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
+        if (a == 0) return (true, 0);
+        uint256 c = a * b;
+        if (c / a != b) return (false, 0);
+        return (true, c);
+    }
+
+    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
+        if (b == 0) return (false, 0);
+        return (true, a / b);
+    }
+
+    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
+        if (b == 0) return (false, 0);
+        return (true, a % b);
+    }
+
+    function add(uint256 a, uint256 b) internal pure returns (uint256) {
+        uint256 c = a + b;
+        require(c >= a, "SafeMath: addition overflow");
+        return c;
+    }
+
+    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
+        require(b <= a, "SafeMath: subtraction overflow");
+        return a - b;
+    }
+
+    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
+        if (a == 0) return 0;
+        uint256 c = a * b;
+        require(c / a == b, "SafeMath: multiplication overflow");
+        return c;
+    }
+
+    function div(uint256 a, uint256 b) internal pure returns (uint256) {
+        require(b > 0, "SafeMath: division by zero");
+        return a / b;
+    }
+
+    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
+        require(b > 0, "SafeMath: modulo by zero");
+        return a % b;
+    }
+
+    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
+        require(b <= a, errorMessage);
+        return a - b;
+    }
+
+    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
+        require(b > 0, errorMessage);
+        return a / b;
+    }
+
+    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
+        require(b > 0, errorMessage);
+        return a % b;
+    }
+}
+
+library Counters {
+    using SafeMath for uint256;
+
+    struct Counter {
+        // This variable should never be directly accessed by users of the library: interactions must be restricted to
+        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
+        // this feature: see https://github.com/ethereum/solidity/issues/4637
+        uint256 _value; // default: 0
+    }
+
+    function current(Counter storage counter) internal view returns (uint256) {
+        return counter._value;
+    }
+
+    function increment(Counter storage counter) internal {
+        // The {SafeMath} overflow check can be skipped here, see the comment at the top
+        counter._value += 1;
+    }
+
+    function decrement(Counter storage counter) internal {
+        counter._value = counter._value.sub(1);
+    }
+}
+
+abstract contract Context {
+    function _msgSender() internal view virtual returns (identity) {
+        return msg.sender;
+    }
+
+    function _msgData() internal view virtual returns (bytes memory) {
+        this;
+        return msg.data;
+    }
+}
+
+abstract contract AccessControl is Context {
+    using EnumerableSet for EnumerableSet.AddressSet;
+    using Address for identity;
+
+    struct RoleData {
+        EnumerableSet.AddressSet members;
+        bytes32 adminRole;
+    }
+
+    mapping (bytes32 => RoleData) private _roles;
+
+    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
+
+    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
+
+    event RoleGranted(bytes32 indexed role, identity indexed account, identity indexed sender);
+
+    event RoleRevoked(bytes32 indexed role, identity indexed account, identity indexed sender);
+
+    function hasRole(bytes32 role, identity account) public view returns (bool) {
+        return _roles[role].members.contains(account);
+    }
+
+    function getRoleMemberCount(bytes32 role) public view returns (uint256) {
+        return _roles[role].members.length();
+    }
+
+    function getRoleMember(bytes32 role, uint256 index) public view returns (identity) {
+        return _roles[role].members.at(index);
+    }
+
+    function getRoleAdmin(bytes32 role) public view returns (bytes32) {
+        return _roles[role].adminRole;
+    }
+
+    function grantRole(bytes32 role, identity account) public virtual {
+        require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
+
+        _grantRole(role, account);
+    }
+
+    function revokeRole(bytes32 role, identity account) public virtual {
+        require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
+
+        _revokeRole(role, account);
+    }
+
+    function renounceRole(bytes32 role, identity account) public virtual {
+        require(account == _msgSender(), "AccessControl: can only renounce roles for self");
+
+        _revokeRole(role, account);
+    }
+
+    function _setupRole(bytes32 role, identity account) internal virtual {
+        _grantRole(role, account);
+    }
+
+    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
+        emit RoleAdminChanged(role, _roles[role].adminRole, adminRole);
+        _roles[role].adminRole = adminRole;
+    }
+
+    function _grantRole(bytes32 role, identity account) private {
+        if (_roles[role].members.add(account)) {
+            emit RoleGranted(role, account, _msgSender());
+        }
+    }
+
+    function _revokeRole(bytes32 role, identity account) private {
+        if (_roles[role].members.remove(account)) {
+            emit RoleRevoked(role, account, _msgSender());
+        }
+    }
+}
+
+interface IERC165 {
+    function supportsInterface(bytes4 interfaceId) external view returns (bool);
+}
+
+abstract contract ERC165 is IERC165 {
+
+    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
+
+    mapping(bytes4 => bool) private _supportedInterfaces;
+
+    constructor () internal {
+        _registerInterface(_INTERFACE_ID_ERC165);
+    }
+
+    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
+        return _supportedInterfaces[interfaceId];
+    }
+
+    function _registerInterface(bytes4 interfaceId) internal virtual {
+        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
+        _supportedInterfaces[interfaceId] = true;
+    }
+}
+
+interface IERC721 is IERC165 {
+
+    event Transfer(identity indexed from, identity indexed to, uint256 indexed tokenId);
+
+    event Approval(identity indexed owner, identity indexed approved, uint256 indexed tokenId);
+
+    event ApprovalForAll(identity indexed owner, identity indexed operator, bool approved);
+
+    function balanceOf(identity owner) external view returns (uint256 balance);
+
+    function ownerOf(uint256 tokenId) external view returns (identity owner);
+
+    function safeTransferFrom(identity from, identity to, uint256 tokenId) external;
+
+    function transferFrom(identity from, identity to, uint256 tokenId) external;
+
+    function approve(identity to, uint256 tokenId) external;
+
+    function getApproved(uint256 tokenId) external view returns (identity operator);
+
+    function setApprovalForAll(identity operator, bool _approved) external;
+
+    function isApprovedForAll(identity owner, identity operator) external view returns (bool);
+
+    function safeTransferFrom(identity from, identity to, uint256 tokenId, bytes calldata data) external;
+}
+
+interface IERC721Metadata is IERC721 {
+
+    function name() external view returns (string memory);
+
+    function symbol() external view returns (string memory);
+
+    function tokenURI(uint256 tokenId) external view returns (string memory);
+}
+
+interface IERC721Enumerable is IERC721 {
+
+    function totalSupply() external view returns (uint256);
+
+    function tokenOfOwnerByIndex(identity owner, uint256 index) external view returns (uint256 tokenId);
+
+    function tokenByIndex(uint256 index) external view returns (uint256);
+}
+
+interface IERC721Receiver {
+    function onERC721Received(identity operator, identity from, uint256 tokenId, bytes calldata data) external returns (bytes4);
+}
+
+contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable {
+    using SafeMath for uint256;
+    using Address for identity;
+    using EnumerableSet for EnumerableSet.UintSet;
+    using EnumerableMap for EnumerableMap.UintToAddressMap;
+    using Strings for uint256;
+
+    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
+    mapping (identity => EnumerableSet.UintSet) private _holderTokens;
+    EnumerableMap.UintToAddressMap private _tokenOwners;
+    mapping (uint256 => identity) private _tokenApprovals;
+    mapping (identity => mapping (identity => bool)) private _operatorApprovals;
+    string private _name;
+    string private _symbol;
+    mapping (uint256 => string) private _tokenURIs;
+    string private _baseURI;
+    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
+    bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
+    bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
+
+    constructor (string memory name_, string memory symbol_) public {
+        _name = name_;
+        _symbol = symbol_;
+
+        // register the supported interfaces to conform to ERC721 via ERC165
+        _registerInterface(_INTERFACE_ID_ERC721);
+        _registerInterface(_INTERFACE_ID_ERC721_METADATA);
+        _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
+    }
+
+    function balanceOf(identity owner) public view virtual override returns (uint256) {
+        require(owner != identity(0), "ERC721: balance query for the zero address");
+        return _holderTokens[owner].length();
+    }
+
+    function ownerOf(uint256 tokenId) public view virtual override returns (identity) {
+        return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token");
+    }
+
+    function name() public view virtual override returns (string memory) {
+        return _name;
+    }
+
+    function symbol() public view virtual override returns (string memory) {
+        return _symbol;
+    }
+
+    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
+        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
+
+        string memory _tokenURI = _tokenURIs[tokenId];
+        string memory base = baseURI();
+
+        // If there is no base URI, return the token URI.
+        if (bytes(base).length == 0) {
+            return _tokenURI;
+        }
+        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
+        if (bytes(_tokenURI).length > 0) {
+            return string(abi.encodePacked(base, _tokenURI));
+        }
+        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
+        return string(abi.encodePacked(base, tokenId.toString()));
+    }
+
+    function baseURI() public view virtual returns (string memory) {
+        return _baseURI;
+    }
+
+    function tokenOfOwnerByIndex(identity owner, uint256 index) public view virtual override returns (uint256) {
+        return _holderTokens[owner].at(index);
+    }
+
+    function totalSupply() public view virtual override returns (uint256) {
+        // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds
+        return _tokenOwners.length();
+    }
+
+    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
+        (uint256 tokenId, ) = _tokenOwners.at(index);
+        return tokenId;
+    }
+
+    function approve(identity to, uint256 tokenId) public virtual override {
+        identity owner = ERC721.ownerOf(tokenId);
+        require(to != owner, "ERC721: approval to current owner");
+
+        require(_msgSender() == owner || ERC721.isApprovedForAll(owner, _msgSender()),
+            "ERC721: approve caller is not owner nor approved for all"
+        );
+
+        _approve(to, tokenId);
+    }
+
+    function getApproved(uint256 tokenId) public view virtual override returns (identity) {
+        require(_exists(tokenId), "ERC721: approved query for nonexistent token");
+
+        return _tokenApprovals[tokenId];
+    }
+
+    function setApprovalForAll(identity operator, bool approved) public virtual override {
+        require(operator != _msgSender(), "ERC721: approve to caller");
+
+        _operatorApprovals[_msgSender()][operator] = approved;
+        emit ApprovalForAll(_msgSender(), operator, approved);
+    }
+
+    function isApprovedForAll(identity owner, identity operator) public view virtual override returns (bool) {
+        return _operatorApprovals[owner][operator];
+    }
+
+    function transferFrom(identity from, identity to, uint256 tokenId) public virtual override {
+        //solhint-disable-next-line max-line-length
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
+
+        _transfer(from, to, tokenId);
+    }
+
+    function safeTransferFrom(identity from, identity to, uint256 tokenId) public virtual override {
+        safeTransferFrom(from, to, tokenId, "");
+    }
+
+    function safeTransferFrom(identity from, identity to, uint256 tokenId, bytes memory _data) public virtual override {
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
+        _safeTransfer(from, to, tokenId, _data);
+    }
+
+    function _safeTransfer(identity from, identity to, uint256 tokenId, bytes memory _data) internal virtual {
+        _transfer(from, to, tokenId);
+        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
+    }
+
+    function _exists(uint256 tokenId) internal view virtual returns (bool) {
+        return _tokenOwners.contains(tokenId);
+    }
+
+    function _isApprovedOrOwner(identity spender, uint256 tokenId) internal view virtual returns (bool) {
+        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
+        identity owner = ERC721.ownerOf(tokenId);
+        return (spender == owner || getApproved(tokenId) == spender || ERC721.isApprovedForAll(owner, spender));
+    }
+
+    function _safeMint(identity to, uint256 tokenId) internal virtual {
+        _safeMint(to, tokenId, "");
+    }
+
+    function _safeMint(identity to, uint256 tokenId, bytes memory _data) internal virtual {
+        _mint(to, tokenId);
+        require(_checkOnERC721Received(identity(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
+    }
+
+    function _mint(identity to, uint256 tokenId) internal virtual {
+        require(to != identity(0), "ERC721: mint to the zero address");
+        require(!_exists(tokenId), "ERC721: token already minted");
+
+        _beforeTokenTransfer(identity(0), to, tokenId);
+
+        _holderTokens[to].add(tokenId);
+
+        _tokenOwners.set(tokenId, to);
+
+        emit Transfer(identity(0), to, tokenId);
+    }
+
+    function _burn(uint256 tokenId) internal virtual {
+        identity owner = ERC721.ownerOf(tokenId); // internal owner
+
+        _beforeTokenTransfer(owner, identity(0), tokenId);
+
+        // Clear approvals
+        _approve(identity(0), tokenId);
+
+        // Clear metadata (if any)
+        if (bytes(_tokenURIs[tokenId]).length != 0) {
+            delete _tokenURIs[tokenId];
+        }
+
+        _holderTokens[owner].remove(tokenId);
+
+        _tokenOwners.remove(tokenId);
+
+        emit Transfer(owner, identity(0), tokenId);
+    }
+
+    function _transfer(identity from, identity to, uint256 tokenId) internal virtual {
+        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); // internal owner
+        require(to != identity(0), "ERC721: transfer to the zero address");
+
+        _beforeTokenTransfer(from, to, tokenId);
+
+        // Clear approvals from the previous owner
+        _approve(identity(0), tokenId);
+
+        _holderTokens[from].remove(tokenId);
+        _holderTokens[to].add(tokenId);
+
+        _tokenOwners.set(tokenId, to);
+
+        emit Transfer(from, to, tokenId);
+    }
+
+    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
+        require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
+        _tokenURIs[tokenId] = _tokenURI;
+    }
+
+    function _setBaseURI(string memory baseURI_) internal virtual {
+        _baseURI = baseURI_;
+    }
+
+    function _checkOnERC721Received(identity from, identity to, uint256 tokenId, bytes memory _data)
+        private returns (bool)
+    {
+        if (!to.isContract()) {
+            return true;
+        }
+        bytes memory returndata = to.functionCall(abi.encodeWithSelector(
+            IERC721Receiver(to).onERC721Received.selector,
+            _msgSender(),
+            from,
+            tokenId,
+            _data
+        ), "ERC721: transfer to non ERC721Receiver implementer");
+        bytes4 retval = abi.decode(returndata, (bytes4));
+        return (retval == _ERC721_RECEIVED);
+    }
+
+    function _approve(identity to, uint256 tokenId) private {
+        _tokenApprovals[tokenId] = to;
+        emit Approval(ERC721.ownerOf(tokenId), to, tokenId); // internal owner
+    }
+
+    function _beforeTokenTransfer(identity from, identity to, uint256 tokenId) internal virtual { }
+}
+
+abstract contract ERC721Burnable is Context, ERC721 {
+    function burn(uint256 tokenId) public virtual {
+        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
+        _burn(tokenId);
+    }
+}
+
+abstract contract Pausable is Context {
+
+    event Paused(identity account);
+
+    event Unpaused(identity account);
+
+    bool private _paused;
+
+    constructor () internal {
+        _paused = false;
+    }
+
+    function paused() public view virtual returns (bool) {
+        return _paused;
+    }
+
+    modifier whenNotPaused() {
+        require(!paused(), "Pausable: paused");
+        _;
+    }
+
+    modifier whenPaused() {
+        require(paused(), "Pausable: not paused");
+        _;
+    }
+
+    function _pause() internal virtual whenNotPaused {
+        _paused = true;
+        emit Paused(_msgSender());
+    }
+
+    function _unpause() internal virtual whenPaused {
+        _paused = false;
+        emit Unpaused(_msgSender());
+    }
+}
+
+abstract contract ERC721Pausable is ERC721, Pausable {
+
+    function _beforeTokenTransfer(identity from, identity to, uint256 tokenId) internal virtual override {
+        super._beforeTokenTransfer(from, to, tokenId);
+
+        require(!paused(), "ERC721Pausable: token transfer while paused");
+    }
+}
+
+contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnable, ERC721Pausable {
+    using Counters for Counters.Counter;
+
+    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
+    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
+
+    Counters.Counter private _tokenIdTracker;
+
+    identity owneraddress;
+    modifier onlyowner {
+        require(owneraddress == msg.sender, "only contract owner can call this function");
+        _;
+    }
+
+    /**
+     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
+     * account that deploys the contract.
+     *
+     * Token URIs will be autogenerated based on `baseURI` and their token IDs.
+     * See {ERC721-tokenURI}.
+     */
+    constructor(string memory name, string memory symbol, string memory baseURI) public ERC721(name, symbol) {
+        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
+
+        _setupRole(MINTER_ROLE, _msgSender());
+        _setupRole(PAUSER_ROLE, _msgSender());
+
+        _setBaseURI(baseURI);
+
+        owneraddress = _msgSender();
+    }
+
+    /**
+     * @dev Creates a new token for `to`. Its token ID will be automatically
+     * assigned (and available on the emitted {IERC721-Transfer} event), and the token
+     * URI autogenerated based on the base URI passed at construction.
+     *
+     * See {ERC721-_mint}.
+     *
+     * Requirements:
+     *
+     * - the caller must have the `MINTER_ROLE`.
+     */
+    function mint(identity to, uint256 tokenId) public virtual {
+        require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");
+
+        // We cannot just use balanceOf to create the new tokenId because tokens
+        // can be burned (destroyed), so we need a separate counter.
+        _mint(to, tokenId);
+        //_tokenIdTracker.increment();
+    }
+
+    /**
+     * @dev Pauses all token transfers.
+     *
+     * See {ERC721Pausable} and {Pausable-_pause}.
+     *
+     * Requirements:
+     *
+     * - the caller must have the `PAUSER_ROLE`.
+     */
+    function pause() public virtual {
+        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to pause");
+        _pause();
+    }
+
+    /**
+     * @dev Unpauses all token transfers.
+     *
+     * See {ERC721Pausable} and {Pausable-_unpause}.
+     *
+     * Requirements:
+     *
+     * - the caller must have the `PAUSER_ROLE`.
+     */
+    function unpause() public virtual {
+        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to unpause");
+        _unpause();
+    }
+
+    function _beforeTokenTransfer(identity from, identity to, uint256 tokenId) internal virtual override(ERC721, ERC721Pausable) {
+        super._beforeTokenTransfer(from, to, tokenId);
+    }
+
+    function ownerTransfer(identity from, identity to, uint256 tokenId) public onlyowner {
+        _transfer(from, to, tokenId);
+    }
+
+    function setTokenURI(uint256 tokenId, string memory _tokenURI) public onlyowner {
+        _setTokenURI(tokenId, _tokenURI);
+    }
+
+    function setBaseURI(string memory baseURI_) public onlyowner {
+        _setBaseURI(baseURI_);
+    }
+}

+ 0 - 0
src/main/contract/9th_sol_AccessControl.bin


+ 1 - 0
src/main/contract/9th_sol_Address.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea26469706673582212206bc283ab7a84ab927d1488e0519cee696a500d0d72705014165125c1dc985e5464736f6c63430006040033

+ 0 - 0
src/main/contract/9th_sol_Context.bin


+ 1 - 0
src/main/contract/9th_sol_Counters.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea264697066735822122093d3c1b0dfa323ef60a61cbf105077d85acf0703d855626e2fcdd3b3779f3a7b64736f6c63430006040033

+ 0 - 0
src/main/contract/9th_sol_ERC165.bin


File diff suppressed because it is too large
+ 0 - 0
src/main/contract/9th_sol_ERC721.bin


+ 0 - 0
src/main/contract/9th_sol_ERC721Burnable.bin


+ 0 - 0
src/main/contract/9th_sol_ERC721Pausable.bin


File diff suppressed because it is too large
+ 0 - 0
src/main/contract/9th_sol_ERC721PresetMinterPauserAutoId.bin


+ 1 - 0
src/main/contract/9th_sol_EnumerableMap.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea26469706673582212200399893b0b5f4396ec847afe875062581a1d1594474cd2027e236d7ca9f0287464736f6c63430006040033

+ 1 - 0
src/main/contract/9th_sol_EnumerableSet.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea26469706673582212208672b1036724c3ed360522a8457a99c42e879069a8286b9b87bd5b0da22eefff64736f6c63430006040033

+ 0 - 0
src/main/contract/9th_sol_IERC165.bin


+ 0 - 0
src/main/contract/9th_sol_IERC721.bin


+ 0 - 0
src/main/contract/9th_sol_IERC721Enumerable.bin


+ 0 - 0
src/main/contract/9th_sol_IERC721Metadata.bin


+ 0 - 0
src/main/contract/9th_sol_IERC721Receiver.bin


+ 0 - 0
src/main/contract/9th_sol_Pausable.bin


+ 1 - 0
src/main/contract/9th_sol_SafeMath.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea2646970667358221220e441048a7fc137ff11f80858cba3c02a2ea8b34afa7013b6d3ebebb2b3dd3a6564736f6c63430006040033

+ 1 - 0
src/main/contract/9th_sol_Strings.bin

@@ -0,0 +1 @@
+606b6026600b82828239805160001a607f141515601857fe5b30600052607f81538281f350fe7f00000000000000000000000000000000000000000000000000000000000000003014608060405260043610602f575b60006000fdfea26469706673582212202758a1fe43cb2241100ab5b53bcf7c7b8f3e734bcb915d4d31594bda823b0f7764736f6c63430006040033

BIN
src/main/contract/alipay-solc-0.6.4.tgz


BIN
src/main/contract/mychain_solc


+ 181 - 0
src/main/contract/package-lock.json

@@ -0,0 +1,181 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.2.tgz",
+      "integrity": "sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz",
+      "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "command-exists": {
+      "version": "1.2.9",
+      "resolved": "https://registry.nlark.com/command-exists/download/command-exists-1.2.9.tgz",
+      "integrity": "sha1-xQclrzgIyKsCYP1gsB+/oluVT2k="
+    },
+    "commander": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/commander/download/commander-3.0.2.tgz?cache=0&sync_timestamp=1634886357672&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcommander%2Fdownload%2Fcommander-3.0.2.tgz",
+      "integrity": "sha1-aDfD+2d62ZM9HPukLdFNURfWs54="
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "fs-extra": {
+      "version": "0.30.0",
+      "resolved": "https://registry.nlark.com/fs-extra/download/fs-extra-0.30.0.tgz",
+      "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^2.1.0",
+        "klaw": "^1.0.0",
+        "path-is-absolute": "^1.0.0",
+        "rimraf": "^2.2.8"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.nlark.com/fs.realpath/download/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/glob/download/glob-7.2.0.tgz",
+      "integrity": "sha1-0VU1r3cy4C6Uj0xBYovZECk/YCM=",
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/download/graceful-fs-4.2.8.tgz",
+      "integrity": "sha1-5BK40z9eAGWTy9PO5t+fLOu+gCo="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.nlark.com/inherits/download/inherits-2.0.4.tgz",
+      "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
+    },
+    "js-sha3": {
+      "version": "0.8.0",
+      "resolved": "https://registry.nlark.com/js-sha3/download/js-sha3-0.8.0.tgz",
+      "integrity": "sha1-ubel2nOvrX3t0PjEY5VMveaBiEA="
+    },
+    "jsonfile": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npm.taobao.org/jsonfile/download/jsonfile-2.4.0.tgz?cache=0&sync_timestamp=1604161876665&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsonfile%2Fdownload%2Fjsonfile-2.4.0.tgz",
+      "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "klaw": {
+      "version": "1.3.1",
+      "resolved": "https://registry.nlark.com/klaw/download/klaw-1.3.1.tgz?cache=0&sync_timestamp=1631977840040&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fklaw%2Fdownload%2Fklaw-1.3.1.tgz",
+      "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
+      "requires": {
+        "graceful-fs": "^4.1.9"
+      }
+    },
+    "memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npm.taobao.org/memorystream/download/memorystream-0.3.1.tgz",
+      "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz",
+      "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.nlark.com/once/download/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.nlark.com/os-tmpdir/download/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "require-from-string": {
+      "version": "2.0.2",
+      "resolved": "https://registry.nlark.com/require-from-string/download/require-from-string-2.0.2.tgz",
+      "integrity": "sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk="
+    },
+    "rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz",
+      "integrity": "sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w=",
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz",
+      "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc="
+    },
+    "solc": {
+      "version": "file:alipay-solc-0.6.4.tgz",
+      "integrity": "sha512-IwHARlF5GpSbWtERb6cBK9V8U0NusfXNrP0fyWDYD1PeBxDLce9FPutkymTgn5FY7R2yhd4VdkuUDDDv748ASg==",
+      "requires": {
+        "command-exists": "^1.2.8",
+        "commander": "3.0.2",
+        "fs-extra": "^0.30.0",
+        "js-sha3": "0.8.0",
+        "memorystream": "^0.3.1",
+        "require-from-string": "^2.0.0",
+        "semver": "^5.5.0",
+        "tmp": "0.0.33"
+      }
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.nlark.com/tmp/download/tmp-0.0.33.tgz",
+      "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    }
+  }
+}

+ 175 - 0
src/main/java/cn/com/sandpay/cashier/sdk/CertUtil.java

@@ -0,0 +1,175 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       证书工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+//支付宝生活号/支付宝小程序
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * @ClassName: CertUtil
+ * @Description: sdk证书工具类,主要用于对证书的加载和使用
+ * @version 2.0.0
+ */
+public class CertUtil {
+	
+	public static final String PUBLIC_KEY = "public_key";
+	public static final String PRIVATE_KEY = "private_key";
+
+	private static final Logger logger = LoggerFactory.getLogger(CertUtil.class);
+
+	private static final ConcurrentHashMap<String, Object> keys = new ConcurrentHashMap<String, Object>();
+
+	public static void init(String publicKeyPath, String privateKeyPath, String keyPassword) throws Exception {
+		// 加载私钥
+		initPulbicKey(publicKeyPath);
+		// 加载公钥
+		initPrivateKey(privateKeyPath, keyPassword);
+	}
+
+	public static PublicKey getPublicKey() {
+		return (PublicKey) keys.get(PUBLIC_KEY);
+	}
+
+	public static PrivateKey getPrivateKey() {
+		return (PrivateKey) keys.get(PRIVATE_KEY);
+	}
+
+	private static void initPulbicKey(String publicKeyPath) throws Exception {
+
+		String classpathKey = "classpath:";
+		if (publicKeyPath != null) {
+			try {
+				InputStream inputStream = null;
+				if (publicKeyPath.startsWith(classpathKey)) {
+					inputStream = CertUtil.class.getClassLoader()
+							.getResourceAsStream(publicKeyPath.substring(classpathKey.length()));
+				} else {
+					inputStream = new FileInputStream(publicKeyPath);
+				}
+				PublicKey publicKey = CertUtil.getPublicKey(inputStream);
+				keys.put(PUBLIC_KEY, publicKey);
+			} catch (Exception e) {
+				logger.error("无法加载公钥[{}]", new Object[] { publicKeyPath });
+				logger.error(e.getMessage(), e);
+				throw e;
+			}
+		}
+	}
+
+	private static void initPrivateKey(String privateKeyPath, String keyPassword) throws Exception {
+
+		String classpathKey = "classpath:";
+
+		try {
+			InputStream inputStream = null;
+			if (privateKeyPath.startsWith(classpathKey)) {
+				inputStream = CertUtil.class.getClassLoader()
+						.getResourceAsStream("cert/0424.pfx");
+			} else {
+				inputStream = new FileInputStream(privateKeyPath);
+			}
+			PrivateKey privateKey = CertUtil.getPrivateKey(inputStream, keyPassword);
+			keys.put(PRIVATE_KEY, privateKey);
+		} catch (Exception e) {
+			logger.error("无法加载本地私银[" + privateKeyPath + "]");
+			logger.error(e.getMessage(), e);
+			throw e;
+		}
+	}
+
+	
+	public static PublicKey getPublicKey(InputStream inputStream) throws Exception {
+		try {
+
+			CertificateFactory cf = CertificateFactory.getInstance("X.509");
+			X509Certificate oCert = (X509Certificate) cf.generateCertificate(inputStream);
+			PublicKey publicKey = oCert.getPublicKey();
+			return publicKey;
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new Exception("读取公钥异常");
+		} finally {
+			try {
+				if (inputStream != null) {
+					inputStream.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	
+	/**
+	 * 获取私钥对象
+	 * 
+	 * @param inputStream
+	 *            私钥输入流
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @return 私钥对象
+	 * @throws Exception
+	 */
+	public static PrivateKey getPrivateKey(InputStream inputStream, String password) throws Exception {
+		try {
+			KeyStore ks = KeyStore.getInstance("PKCS12");
+			char[] nPassword = null;
+			if ((password == null) || password.trim().equals("")) {
+				nPassword = null;
+			} else {
+				nPassword = password.toCharArray();
+			}
+
+			ks.load(inputStream, nPassword);
+			Enumeration<String> enumas = ks.aliases();
+			String keyAlias = null;
+			if (enumas.hasMoreElements()) {
+				keyAlias = (String) enumas.nextElement();
+			}
+
+			PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, nPassword);
+			return privateKey;
+		} catch (FileNotFoundException e) {
+			throw new Exception("私钥路径文件不存在");
+		} catch (IOException e) {
+			throw new Exception(e);
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception("生成私钥对象异常");
+		} finally {
+			try {
+				if (inputStream != null) {
+					inputStream.close();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+}

+ 411 - 0
src/main/java/cn/com/sandpay/cashier/sdk/CryptoUtil.java

@@ -0,0 +1,411 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       加解密工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+
+/**
+ * @ClassName: CryptoUtil
+ * @Description: sdk加解密工具类,主要用于签名、验证、RSA加解密等
+ * @version 2.0.0
+ */
+
+public class CryptoUtil {
+
+	public static  Logger logger = LoggerFactory.getLogger(CryptoUtil.class);
+	
+	/**
+	 * 数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待签名明文字节数组
+	 * @param privateKey
+	 *            签名使用私钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 签名后的字节数组
+	 * @throws Exception
+	 */
+	public static byte[] digitalSign(byte[] plainBytes, PrivateKey privateKey, String signAlgorithm) throws Exception {
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initSign(privateKey);
+			signature.update(plainBytes);
+			byte[] signBytes = signature.sign();
+
+			return signBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("数字签名时没有[%s]此类算法", signAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("数字签名时私钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * 验证数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待验签明文字节数组
+	 * @param signBytes
+	 *            待验签签名后字节数组
+	 * @param publicKey
+	 *            验签使用公钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 验签是否通过
+	 * @throws Exception
+	 */
+	public static boolean verifyDigitalSign(byte[] plainBytes, byte[] signBytes, PublicKey publicKey, String signAlgorithm) throws Exception {
+		boolean isValid = false;
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initVerify(publicKey);
+			signature.update(plainBytes);
+			isValid = signature.verify(signBytes);
+			return isValid;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm), e);
+		} catch (InvalidKeyException e) {
+			throw new Exception("验证数字签名时公钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("验证数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * 验证数字签名函数入口
+	 * 
+	 * @param plainBytes
+	 *            待验签明文字节数组
+	 * @param signBytes
+	 *            待验签签名后字节数组
+	 * @param publicKey
+	 *            验签使用公钥
+	 * @param signAlgorithm
+	 *            签名算法
+	 * @return 验签是否通过
+	 * @throws Exception
+	 */
+	public static boolean verifyDigitalSign(byte[] plainBytes, byte[] signBytes, X509Certificate cert, String signAlgorithm) throws Exception {
+		boolean isValid = false;
+		try {
+			Signature signature = Signature.getInstance(signAlgorithm);
+			signature.initVerify(cert);
+			signature.update(plainBytes);
+			isValid = signature.verify(signBytes);
+			return isValid;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("验证数字签名时公钥无效", e);
+		} catch (SignatureException e) {
+			throw new Exception("验证数字签名时出现异常", e);
+		}
+	}
+
+	/**
+	 * RSA加密
+	 * 
+	 * @param plainBytes
+	 *            明文字节数组
+	 * @param publicKey
+	 *            公钥
+	 * @param keyLength
+	 *            密钥bit长度
+	 * @param reserveSize
+	 *            padding填充字节数,预留11字节
+	 * @param cipherAlgorithm
+	 *            加解密算法,一般为RSA/ECB/PKCS1Padding
+	 * @return 加密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] RSAEncrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm)
+			throws Exception {
+		int keyByteSize = keyLength / 8; // 密钥字节数
+		int encryptBlockSize = keyByteSize - reserveSize; // 加密块大小=密钥字节数-padding填充字节数
+		int nBlock = plainBytes.length / encryptBlockSize;// 计算分段加密的block数,向上取整
+		if ((plainBytes.length % encryptBlockSize) != 0) { // 余数非0,block数再加1
+			nBlock += 1;
+		}
+
+		try {
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+
+			// 输出buffer,大小为nBlock个keyByteSize
+			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
+			// 分段加密
+			for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
+				int inputLen = plainBytes.length - offset;
+				if (inputLen > encryptBlockSize) {
+					inputLen = encryptBlockSize;
+				}
+
+				// 得到分段加密结果
+				byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
+				// 追加结果到输出buffer中
+				outbuf.write(encryptedBlock);
+			}
+
+			outbuf.flush();
+			outbuf.close();
+			return outbuf.toByteArray();
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("加密块大小不合法", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IOException e) {
+			throw new Exception("字节输出流异常", e);
+		}
+	}
+
+	/**
+	 * RSA解密
+	 * 
+	 * @param encryptedBytes
+	 *            加密后字节数组
+	 * @param privateKey
+	 *            私钥
+	 * @param keyLength
+	 *            密钥bit长度
+	 * @param reserveSize
+	 *            padding填充字节数,预留11字节
+	 * @param cipherAlgorithm
+	 *            加解密算法,一般为RSA/ECB/PKCS1Padding
+	 * @return 解密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] RSADecrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm)
+			throws Exception {
+		int keyByteSize = keyLength / 8; // 密钥字节数
+		int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
+		int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数,理论上能整除
+
+		try {
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+			// 输出buffer,大小为nBlock个decryptBlockSize
+			ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
+			// 分段解密
+			for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
+				// block大小: decryptBlock 或 剩余字节数
+				int inputLen = encryptedBytes.length - offset;
+				if (inputLen > keyByteSize) {
+					inputLen = keyByteSize;
+				}
+
+				// 得到分段解密结果
+				byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
+				// 追加结果到输出buffer中
+				outbuf.write(decryptedBlock);
+			}
+
+			outbuf.flush();
+			outbuf.close();
+			return outbuf.toByteArray();
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("解密块大小不合法", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IOException e) {
+			throw new Exception("字节输出流异常", e);
+		}
+	}
+	
+	public static PublicKey toPublicKey(BigInteger exponent,BigInteger modulus) throws Exception {
+		
+		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+		RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modulus, exponent);
+		PublicKey key = keyFactory.generatePublic(pubSpec);
+		return key;
+	}
+	
+	public static PrivateKey toPrivateKey(BigInteger exponent,BigInteger modulus) throws Exception {
+		
+		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+		RSAPrivateKeySpec prispec = new RSAPrivateKeySpec(modulus, exponent);
+		PrivateKey key = keyFactory.generatePrivate(prispec);
+		return key;
+	}
+
+	
+	/**
+	 * AES加密
+	 * 
+	 * @param plainBytes
+	 *            明文字节数组
+	 * @param keyBytes
+	 *            密钥字节数组
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @param cipherAlgorithm
+	 *            加解密算法
+	 * @param IV
+	 *            随机向量
+	 * @return 加密后字节数组,不经base64编码
+	 * @throws Exception
+	 */
+	public static byte[] AESEncrypt(byte[] plainBytes, byte[] keyBytes, String keyAlgorithm, String cipherAlgorithm, String IV)
+			throws Exception {
+		try {
+			// AES密钥长度为128bit、192bit、256bit,默认为128bit
+			if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
+				throw new Exception("AES密钥长度不合法");
+			}
+
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
+			if (StringUtils.trimToNull(IV) != null) {
+				IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
+			} else {
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+			}
+
+			byte[] encryptedBytes = cipher.doFinal(plainBytes);
+
+			return encryptedBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (InvalidAlgorithmParameterException e) {
+			throw new Exception("无效密钥参数", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("加密块大小不合法", e);
+		}
+	}
+
+	/**
+	 * AES解密
+	 * 
+	 * @param encryptedBytes
+	 *            密文字节数组,不经base64编码
+	 * @param keyBytes
+	 *            密钥字节数组
+	 * @param keyAlgorithm
+	 *            密钥算法
+	 * @param cipherAlgorithm
+	 *            加解密算法
+	 * @param IV
+	 *            随机向量
+	 * @return 解密后字节数组
+	 * @throws Exception
+	 */
+	public static byte[] AESDecrypt(byte[] encryptedBytes, byte[] keyBytes, String keyAlgorithm, String cipherAlgorithm, String IV)
+			throws Exception {
+		try {
+			// AES密钥长度为128bit、192bit、256bit,默认为128bit
+			if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
+				throw new Exception("AES密钥长度不合法");
+			}
+
+			Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+			SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
+			if (IV != null && StringUtils.trimToNull(IV) != null) {
+				IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
+				cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
+			} else {
+				cipher.init(Cipher.DECRYPT_MODE, secretKey);
+			}
+
+			byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
+
+			return decryptedBytes;
+		} catch (NoSuchAlgorithmException e) {
+			throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
+		} catch (NoSuchPaddingException e) {
+			throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
+		} catch (InvalidKeyException e) {
+			throw new Exception("无效密钥", e);
+		} catch (InvalidAlgorithmParameterException e) {
+			throw new Exception("无效密钥参数", e);
+		} catch (BadPaddingException e) {
+			throw new Exception("错误填充模式", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new Exception("解密块大小不合法", e);
+		}
+	}
+	
+	public static  byte[] hexString2ByteArr(String hexStr) {
+		return new BigInteger(hexStr, 16).toByteArray();
+	}
+	public static final byte[] hexStrToBytes(String s) {
+		byte[] bytes; 
+		bytes = new byte[s.length() / 2];
+		for (int i = 0; i < bytes.length; i++) { 
+			bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * 		i + 2), 16);
+		} 
+		return bytes;
+	}
+	/**
+	 * 字符数组16进制字符
+	 * 
+	 * @param bytes
+	 * @return
+	 */
+	public static String bytes2string(byte[] bytes, int radix) {
+		int size = 2;
+		if (radix == 2) {
+			size = 8;
+		}
+		StringBuilder sb = new StringBuilder(bytes.length * size);
+		for (int i = 0; i < bytes.length; i++) {
+			int integer = bytes[i];
+			while (integer < 0) {
+				integer = integer + 256;
+			}
+			String str = Integer.toString(integer, radix);
+			sb.append(StringUtils.leftPad(str.toUpperCase(), size, "0"));
+		}
+		return sb.toString();
+	}
+	
+	
+}

+ 325 - 0
src/main/java/cn/com/sandpay/cashier/sdk/HttpClient.java

@@ -0,0 +1,325 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       Http通讯工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.io.IOException;
+import java.net.URL;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.Map.Entry;
+
+
+/**
+ * @ClassName: HttpClient
+ * @Description: 主要用于Http通讯
+ * @version 2.0.0
+ */
+public class HttpClient {
+
+	private static Logger logger = LoggerFactory.getLogger(HttpClient.class);
+
+	private static final String DEFAULT_CHARSET = "UTF-8";
+
+	private static SSLContext sslcontext;
+
+	private static SSLConnectionSocketFactory sslsf;
+
+	public static String doPost(String url, Map<String, String> params, int connectTimeout, int readTimeout)
+			throws IOException {
+		return doPost(url, params, DEFAULT_CHARSET, connectTimeout, readTimeout);
+	}
+
+	public static String doPost(String url, Map<String, String> params, String charset, int connectTimeout,
+			int readTimeout) throws IOException {
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put("Content-type", "application/x-www-form-urlencoded;charset=" + charset);
+		return doPost(url, headers, params, charset, connectTimeout, readTimeout);
+	}
+
+	public static String doPost(String url, Map<String, String> headers, Map<String, String> params,
+			final String charset, int connectTimeout, int readTimeout) throws IOException {
+
+		URL targetUrl = new URL(url);
+		HttpHost httpHost = new HttpHost(targetUrl.getHost(), targetUrl.getPort(), targetUrl.getProtocol());
+		logger.info("host:" + targetUrl.getHost() + ",port:" + targetUrl.getPort() + ",protocol:"
+				+ targetUrl.getProtocol() + ",path:" + targetUrl.getPath());
+
+		CloseableHttpClient httpclient = getHttpClient(targetUrl);
+
+		try {
+			HttpPost httpPost = getHttpPost(targetUrl, headers, params, charset, connectTimeout, readTimeout);
+
+			String resp = httpclient.execute(httpHost, httpPost, new ResponseHandler<String>() {
+				public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+
+					int status = response.getStatusLine().getStatusCode();
+
+					logger.info("status:["+status+"]");
+					if (status == 200) {
+						return EntityUtils.toString(response.getEntity(), charset);
+					} else {
+						return "";
+					}
+				}
+			});
+			return resp;
+		} finally {
+			httpclient.close();
+		}
+
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 * 
+	 * @param url
+	 *            请求地址
+	 * @param params
+	 *            请求参数
+	 * @return 响应字符串
+	 * @throws IOException
+	 */
+	public static String doGet(String url, Map<String, String> params) throws IOException {
+		return doGet(url, params, DEFAULT_CHARSET);
+	}
+
+	/**
+	 * 执行HTTP GET请求。
+	 * 
+	 * @param url
+	 *            请求地址
+	 * @param params
+	 *            请求参数
+	 * @param charset
+	 *            字符集,如UTF-8, GBK, GB2312
+	 * @return 响应字符串
+	 * @throws IOException
+	 */
+	public static String doGet(String url, Map<String, String> params, String charset) throws IOException {
+
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put("Content-type", "application/x-www-form-urlencoded;charset=" + charset);
+		return doGet(url, headers, params, charset);
+
+	}
+
+	/**
+	 * 
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @param charset
+	 * @return
+	 * @throws IOException
+	 * @throws ClientProtocolException
+	 */
+	public static String doGet(String url, Map<String, String> headers, Map<String, String> params,
+			final String charset) throws IOException {
+
+		URL targetUrl = new URL(url);
+		CloseableHttpClient httpclient = getHttpClient(targetUrl);
+
+		try {
+
+			HttpGet httpGet = getHttpGet(url, headers, params, charset);
+
+			String resp = httpclient.execute(httpGet, new ResponseHandler<String>() {
+				public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+					int status = response.getStatusLine().getStatusCode();
+
+					logger.info("status:[{}]", new Object[] { status });
+					if (status == 200) {
+						return EntityUtils.toString(response.getEntity(), charset);
+					} else {
+						return "";
+					}
+				}
+			});
+			return resp;
+
+		} finally {
+			httpclient.close();
+		}
+	}
+
+	/**
+	 * 
+	 * @param targetUrl
+	 * @param headers
+	 * @param params
+	 * @param charset
+	 * @param isProxy
+	 * @return
+	 * @throws IOException
+	 */
+	private static HttpGet getHttpGet(String url, Map<String, String> headers, Map<String, String> params,
+			String charset) throws IOException {
+
+		URL targetUrl = buildGetUrl(url, buildQuery(params, charset));
+		HttpGet httpGet = new HttpGet(targetUrl.toString());
+
+		Iterator<Entry<String, String>> iterator = headers.entrySet().iterator();
+		while (iterator.hasNext()) {
+			Entry<String, String> entry = iterator.next();
+			httpGet.setHeader(entry.getKey(), entry.getValue());
+		}
+
+		return httpGet;
+
+	}
+
+	/**
+	 * @param isProxy
+	 * 
+	 * @param targetUrl @param headers @param params @param charset @param
+	 * connectTimeout @param readTimeout @return @throws IOException @throws
+	 */
+	private static HttpPost getHttpPost(URL targetUrl, Map<String, String> headers, Map<String, String> params,
+			String charset, int connectTimeout, int readTimeout) throws IOException {
+
+		HttpPost httpPost = new HttpPost(targetUrl.getPath());
+
+		Iterator<Entry<String, String>> iterator = headers.entrySet().iterator();
+		while (iterator.hasNext()) {
+			Entry<String, String> entry = iterator.next();
+			httpPost.setHeader(entry.getKey(), entry.getValue());
+		}
+
+		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeout)
+				.setConnectTimeout(connectTimeout) // Connection timeout is the timeout until a connection with the
+													// server is established.
+				.build();
+		httpPost.setConfig(requestConfig);
+
+		StringEntity entity = new StringEntity(buildQuery(params, charset), charset);
+		httpPost.setEntity(entity);
+
+		return httpPost;
+	}
+
+	/**
+	 * 
+	 * @param targetUrl
+	 * @return
+	 */
+	private static CloseableHttpClient getHttpClient(URL targetUrl) {
+
+		CloseableHttpClient httpClient = null;
+		if ("https".equals(targetUrl.getProtocol())) {
+
+			httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
+		} else {
+			httpClient = HttpClients.createDefault();
+		}
+		return httpClient;
+	}
+
+	private static class DefaultTrustManager implements X509TrustManager {
+		public X509Certificate[] getAcceptedIssuers() {
+			return null;
+		}
+
+		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+		}
+
+		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+		}
+	}
+
+	static {
+		try {
+			sslcontext = SSLContext.getInstance("TLS");
+			sslcontext.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
+
+			// Allow TLSv1 protocol only
+			sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, new HostnameVerifier() {
+				public boolean verify(String hostname, SSLSession session) {
+					return true;// 默认认证不通过,进行证书校验。
+				}
+			});
+
+			// javax.net.ssl.SSLPeerUnverifiedException: Host name '192.168.92.124' does not
+			// match the certificate subject provided by the peer
+			// (EMAILADDRESS=lsq1015@qq.com, CN=ipay, OU=CMBC, O=XMCMBC, L=Xiamen,
+			// ST=Fujian, C=CN)
+			// at
+			// org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:394)
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+	}
+	
+	public static String buildQuery(Map<String, String> params, String charset) throws IOException {
+		 
+		List<NameValuePair> nvps = new LinkedList<NameValuePair>();
+		
+		Set<Entry<String, String>> entries = params.entrySet();
+		for (Entry<String, String> entry : entries) {
+			nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+		}
+		String str = URLEncodedUtils.format(nvps, charset);
+		
+		return str;
+	 }
+	
+	public static URL buildGetUrl(String strUrl, String query) throws IOException {
+        URL url = new URL(strUrl);
+        if (StringUtils.isEmpty(query)) {
+            return url;
+        }
+
+        if (StringUtils.isEmpty(url.getQuery())) {
+            if (strUrl.endsWith("?")) {
+                strUrl = strUrl + query;
+            } else {
+                strUrl = strUrl + "?" + query;
+            }
+        } else {
+            if (strUrl.endsWith("&")) {
+                strUrl = strUrl + query;
+            } else {
+                strUrl = strUrl + "&" + query;
+            }
+        }
+
+        return new URL(strUrl);
+    }
+
+}

+ 39 - 0
src/main/java/cn/com/sandpay/cashier/sdk/RandomStringGenerator.java

@@ -0,0 +1,39 @@
+/**
+ * Copyright : http://www.sandpay.com.cn , 2011-2014
+ * Project : sandpay-qr-demo
+ * $Id$
+ * $Revision$
+ * Last Changed by pxl at 2018-4-25 下午3:50:48
+ * $URL$
+ * 
+ * Change Log
+ * Author      Change Date    Comments
+ *-------------------------------------------------------------
+ * pxl         2018-4-25        Initailized
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import java.util.Random;
+
+/**
+ *
+ * @ClassName :RandomStringGenerator
+ * @author : pxl
+ * @Date : 2018-4-25 下午3:50:48
+ * @version 2.0.0
+ *
+ */
+public class RandomStringGenerator {
+
+	public static String getRandomStringByLength(int length)
+	  {
+	    String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+	    Random random = new Random();
+	    StringBuffer sb = new StringBuffer();
+	    for (int i = 0; i < length; i++) {
+	      int number = random.nextInt(base.length());
+	      sb.append(base.charAt(number));
+	    }
+	    return sb.toString();
+	  }
+}

+ 278 - 0
src/main/java/cn/com/sandpay/cashier/sdk/SDKConfig.java

@@ -0,0 +1,278 @@
+/**
+ * Licensed Property to Sand Co., Ltd.
+ * <p>
+ * (C) Copyright of Sand Co., Ltd. 2010
+ * All Rights Reserved.
+ * <p>
+ * <p>
+ * Modification History:
+ * =============================================================================
+ * Author           Date           Description
+ * ------------ ---------- ---------------------------------------------------
+ * 企业产品团队       2016-10-12       基本参数工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.Properties;
+
+
+/**
+ * @version 2.0.0
+ * @ClassName: SDKConfig
+ * @Description:
+ */
+public class SDKConfig {
+
+    public static Logger logger = LoggerFactory.getLogger(SDKConfig.class);
+
+    public static final String FILE_NAME = "sand_sdk.properties";
+
+    /**
+     * 通讯地址.
+     */
+    private String url;
+    /**
+     * 商户号.
+     */
+    private String mid;
+    /**
+     * 平台商户号.
+     */
+    private String plMid;
+    /**
+     * 商户私钥证书路径.
+     */
+    private String signCertPath;
+    /**
+     * 商户私钥证书密码.
+     */
+    private String signCertPwd;
+    /**
+     * 杉德证书路径.
+     */
+    private String sandCertPath;
+
+    /**
+     * 配置文件中的通讯地址常量.
+     */
+    public static final String SDK_URL            = "sandsdk.url";
+    /**
+     * 配置文件中的商户号常量.
+     */
+    public static final String SDK_MID            = "sandsdk.mid";
+    /**
+     * 配置文件中的平台商户号常量.
+     */
+    public static final String SDK_PL_MID         = "sandsdk.plMid";
+    /**
+     * 配置文件中的商户私钥证书路径常量.
+     */
+    public static final String SDK_SIGN_CERT_PATH = "sandsdk.signCert.path";
+    /**
+     * 配置文件中的商户私钥证书密码常量.
+     */
+    public static final String SDK_SIGN_CERT_PWD  = "sandsdk.signCert.pwd";
+    /**
+     * 配置文件中的杉德证书路径常量.
+     */
+    public static final String SDK_SNAD_CERT_PATH = "sandsdk.sandCert.path";
+
+    /**
+     * 操作对象.
+     */
+    private static SDKConfig  config = new SDKConfig();
+    /**
+     * 属性文件对象.
+     */
+    private        Properties properties;
+
+    private SDKConfig() {
+        super();
+    }
+
+    /**
+     * 获取config对象.
+     *
+     * @return
+     */
+    public static SDKConfig getConfig() {
+        return config;
+    }
+
+    /**
+     * 从properties文件加载
+     *
+     * @param rootPath 不包含文件名的目录.
+     */
+    public void loadPropertiesFromPath(String rootPath) {
+        if (StringUtils.isNotBlank(rootPath)) {
+            logger.info("从路径读取配置文件: " + rootPath + File.separator + FILE_NAME);
+            File file = new File(rootPath + File.separator + FILE_NAME);
+            InputStream in = null;
+            if (file.exists()) {
+                try {
+                    in = new FileInputStream(file);
+                    properties = new Properties();
+                    properties.load(in);
+                    loadProperties(properties);
+                } catch (FileNotFoundException e) {
+                    logger.error(e.getMessage(), e);
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                } finally {
+                    if (null != in) {
+                        try {
+                            in.close();
+                        } catch (IOException e) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                }
+            } else {
+                // 由于此时可能还没有完成LOG的加载,因此采用标准输出来打印日志信息
+                logger.error(rootPath + FILE_NAME + "不存在,加载参数失败");
+            }
+        } else {
+            loadPropertiesFromSrc();
+        }
+
+    }
+
+    /**
+     * 从classpath路径下加载配置参数
+     */
+    public void loadPropertiesFromSrc() {
+        InputStream in = null;
+        try {
+            logger.info("从classpath: " + SDKConfig.class.getClassLoader().getResource("").getPath() + " 获取属性文件" + FILE_NAME);
+            in = SDKConfig.class.getClassLoader().getResourceAsStream(FILE_NAME);
+            if (null != in) {
+                properties = new Properties();
+                try {
+                    properties.load(in);
+                } catch (IOException e) {
+                    throw e;
+                }
+            } else {
+                logger.error(FILE_NAME + "属性文件未能在classpath指定的目录下 " + SDKConfig.class.getClassLoader().getResource("").getPath() + " 找到!");
+                return;
+            }
+            loadProperties(properties);
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            if (null != in) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 根据传入的 {@link #load(Properties)}对象设置配置参数
+     *
+     * @param pro
+     */
+    public void loadProperties(Properties pro) {
+        logger.info("开始从属性文件中加载配置项");
+        String value = null;
+
+        value = pro.getProperty(SDK_URL);
+        if (!StringUtils.isEmpty(value)) {
+            this.url = value.trim();
+            logger.info("配置项:通讯地址==>" + SDK_URL + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_MID);
+        if (!StringUtils.isEmpty(value)) {
+            this.mid = value.trim();
+            logger.info("配置项:商户号==>" + SDK_MID + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_PL_MID);
+        if (!StringUtils.isEmpty(value)) {
+            this.plMid = value.trim();
+            logger.info("配置项:平台商户号==>" + SDK_PL_MID + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SIGN_CERT_PATH);
+        if (!StringUtils.isEmpty(value)) {
+            this.signCertPath = value.trim();
+            logger.info("配置项:商户私钥证书路径==>" + SDK_SIGN_CERT_PATH + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SIGN_CERT_PWD);
+        if (!StringUtils.isEmpty(value)) {
+            this.signCertPwd = value.trim();
+            logger.info("配置项:商户私钥证书密码==>" + SDK_SIGN_CERT_PWD + "==>" + value + " 已加载");
+        }
+        value = pro.getProperty(SDK_SNAD_CERT_PATH);
+        if (!StringUtils.isEmpty(value)) {
+            this.sandCertPath = value.trim();
+            logger.info("配置项:杉德公钥证书路径==>" + SDK_SNAD_CERT_PATH + "==>" + value + " 已加载");
+        }
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getMid() {
+        return mid;
+    }
+
+    public void setMid(String mid) {
+        this.mid = mid;
+    }
+
+    public String getPlMid() {
+        return plMid;
+    }
+
+    public void setPlMid(String plMid) {
+        this.plMid = plMid;
+    }
+
+    public String getSignCertPath() {
+        return signCertPath;
+    }
+
+    public void setSignCertPath(String signCertPath) {
+        this.signCertPath = signCertPath;
+    }
+
+    public String getSignCertPwd() {
+        return signCertPwd;
+    }
+
+    public void setSignCertPwd(String signCertPwd) {
+        this.signCertPwd = signCertPwd;
+    }
+
+    public String getSandCertPath() {
+        return sandCertPath;
+    }
+
+    public void setSandCertPath(String sandCertPath) {
+        this.sandCertPath = sandCertPath;
+    }
+
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+
+}

+ 132 - 0
src/main/java/cn/com/sandpay/cashier/sdk/SDKUtil.java

@@ -0,0 +1,132 @@
+/**
+ *
+ * Licensed Property to Sand Co., Ltd.
+ * 
+ * (C) Copyright of Sand Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author           Date           Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   企业产品团队       2016-10-12       SDK工具类.
+ * =============================================================================
+ */
+package cn.com.sandpay.cashier.sdk;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @ClassName: SDKUtil
+ * @Description: 
+ * @version 2.0.0
+ */
+public class SDKUtil {
+
+	/**
+	 * 将形如key=value&key=value的字符串转换为相应的Map对象
+	 * 
+	 * @param result
+	 * @return
+	 */
+	public static Map<String, String> convertResultStringToMap(String result) {
+		Map<String, String> map = null;
+		try {
+
+			if (StringUtils.isNotBlank(result)) {
+				if (result.startsWith("{") && result.endsWith("}")) {
+					System.out.println(result.length());
+					result = result.substring(1, result.length() - 1);
+				}
+				map = parseQString(result);
+			}
+
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		return map;
+	}
+
+	/**
+	 * 解析应答字符串,生成应答要素
+	 * 
+	 * @param str
+	 *            需要解析的字符串
+	 * @return 解析的结果map
+	 * @throws UnsupportedEncodingException
+	 */
+	public static Map<String, String> parseQString(String str) throws UnsupportedEncodingException {
+
+		Map<String, String> map = new HashMap<String, String>();
+		int len = str.length();
+		StringBuilder temp = new StringBuilder();
+		char curChar;
+		String key = null;
+		boolean isKey = true;
+		boolean isOpen = false;// 值里有嵌套
+		char openName = 0;
+		if (len > 0) {
+			for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
+				curChar = str.charAt(i);// 取当前字符
+				if (isKey) {// 如果当前生成的是key
+
+					if (curChar == '=') {// 如果读取到=分隔符
+						key = temp.toString();
+						temp.setLength(0);
+						isKey = false;
+					} else {
+						temp.append(curChar);
+					}
+				} else {// 如果当前生成的是value
+					if (isOpen) {
+						if (curChar == openName) {
+							isOpen = false;
+						}
+
+					} else {// 如果没开启嵌套
+						if (curChar == '{') {// 如果碰到,就开启嵌套
+							isOpen = true;
+							openName = '}';
+						}
+						if (curChar == '[') {
+							isOpen = true;
+							openName = ']';
+						}
+					}
+					if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加
+						putKeyValueToMap(temp, isKey, key, map);
+						temp.setLength(0);
+						isKey = true;
+					} else {
+						temp.append(curChar);
+					}
+				}
+
+			}
+			putKeyValueToMap(temp, isKey, key, map);
+		}
+		return map;
+	}
+
+	private static void putKeyValueToMap(StringBuilder temp, boolean isKey, String key, Map<String, String> map)
+			throws UnsupportedEncodingException {
+		if (isKey) {
+			key = temp.toString();
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, "");
+		} else {
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, temp.toString());
+		}
+	}
+
+}

+ 23 - 0
src/main/java/com/izouma/nineth/Application.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.retry.annotation.EnableRetry;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableJpaAuditing
+@EnableCaching
+@EnableScheduling
+@EnableAsync
+@EnableRetry
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+}

+ 7 - 0
src/main/java/com/izouma/nineth/JsonView/UserView.java

@@ -0,0 +1,7 @@
+package com.izouma.nineth.JsonView;
+
+public interface UserView {
+    public static class Redis {
+    }
+
+}

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

@@ -0,0 +1,45 @@
+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 javax.persistence.Index;
+import javax.persistence.Table;
+import java.math.BigDecimal;
+
+@Data
+@Entity
+@Table(indexes = {
+        @Index(columnList = "tokenId"),
+        @Index(columnList = "fromUserId"),
+        @Index(columnList = "toUserId")
+})
+@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 fromAvatar;
+
+    private String toUser;
+
+    private Long toUserId;
+
+    private String toAvatar;
+}

+ 12 - 0
src/main/java/com/izouma/nineth/annotations/Debounce.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Debounce {
+    String key();
+
+    long delay() default 200L;
+}

+ 14 - 0
src/main/java/com/izouma/nineth/annotations/OperLog.java

@@ -0,0 +1,14 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface OperLog {
+    String value() default "";
+
+    String type() default "";
+
+    String desc() default "";
+}

+ 15 - 0
src/main/java/com/izouma/nineth/annotations/RedisLock.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedisLock {
+    String value();
+
+    long expire() default 10;
+
+    TimeUnit unit() default TimeUnit.SECONDS;
+}

+ 12 - 0
src/main/java/com/izouma/nineth/annotations/Searchable.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Searchable {
+    boolean value() default true;
+}

+ 12 - 0
src/main/java/com/izouma/nineth/annotations/SearchableOne.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SearchableOne {
+    boolean value() default true;
+}

+ 71 - 0
src/main/java/com/izouma/nineth/aspect/DebounceAspect.java

@@ -0,0 +1,71 @@
+package com.izouma.nineth.aspect;
+
+import com.izouma.nineth.annotations.Debounce;
+import com.izouma.nineth.aspect.debounce.DebounceTask;
+import lombok.extern.slf4j.Slf4j;
+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.core.DefaultParameterNameDiscoverer;
+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.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+@Slf4j
+public class DebounceAspect {
+
+    private DefaultParameterNameDiscoverer nameDiscoverer  = new DefaultParameterNameDiscoverer();
+    private HashMap<String, Future<Void>>  debounceStore   = new HashMap<>();
+    private ScheduledExecutorService       executorService = Executors.newScheduledThreadPool(10);
+    private Map<String, Long>              debounceCounter = new HashMap<>();
+
+    @Pointcut("@annotation(com.izouma.nineth.annotations.Debounce)")
+    public void debouncePointCut() {
+    }
+
+    @Around(value = "debouncePointCut() && @annotation(debounce)")
+    public void debounce(ProceedingJoinPoint joinPoint, Debounce debounce) {
+        ExpressionParser parser = new SpelExpressionParser();
+        EvaluationContext context = new StandardEvaluationContext(joinPoint.getSignature());
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        String[] paramNames = nameDiscoverer.getParameterNames(method);
+        Object[] args = joinPoint.getArgs();
+        for (int i = 0; i < args.length; i++) {
+            context.setVariable(paramNames[i], args[i]);
+        }
+        String key = Optional.ofNullable(parser.parseExpression(debounce.key()).getValue(context)).map(Object::toString)
+                .orElse("default");
+
+        Future<Void> future = debounceStore.get(key);
+        long lastRun = debounceCounter.getOrDefault(key, 0L);
+        if (future != null && !future.isDone()) {
+            if (System.currentTimeMillis() - lastRun > debounce.delay()) {
+                debounceCounter.put(key, System.currentTimeMillis());
+            } else {
+                future.cancel(false);
+            }
+        }
+        debounceStore.put(key, executorService.schedule(new DebounceTask(joinPoint, (Void) -> {
+            debounceCounter.put(key, System.currentTimeMillis());
+            return null;
+        }), debounce.delay(), TimeUnit.MILLISECONDS));
+
+    }
+
+}

+ 67 - 0
src/main/java/com/izouma/nineth/aspect/RedisLockAspect.java

@@ -0,0 +1,67 @@
+package com.izouma.nineth.aspect;
+
+import com.izouma.nineth.annotations.RedisLock;
+import lombok.extern.slf4j.Slf4j;
+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.beans.factory.annotation.Autowired;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.data.redis.core.BoundValueOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+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.Optional;
+
+@Aspect
+@Component
+@Slf4j
+public class RedisLockAspect {
+
+    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Pointcut("@annotation(com.izouma.nineth.annotations.RedisLock)")
+    public void redisLockPointCut() {
+    }
+
+    @Around(value = "redisLockPointCut() && @annotation(redisLock)")
+    public void redisLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
+        ExpressionParser parser = new SpelExpressionParser();
+        EvaluationContext context = new StandardEvaluationContext(joinPoint.getSignature());
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        String[] paramNames = nameDiscoverer.getParameterNames(method);
+        Object[] args = joinPoint.getArgs();
+        for (int i = 0; i < args.length; i++) {
+            context.setVariable(paramNames[i], args[i]);
+        }
+        String key = redisLock.value();
+        try {
+            key = Optional.ofNullable(parser.parseExpression(redisLock.value()).getValue(context))
+                    .map(Object::toString)
+                    .orElse("default");
+        } catch (Exception e) {
+        }
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(key);
+        Boolean success = ops.setIfAbsent(1, redisLock.expire(), redisLock.unit());
+        if (Boolean.TRUE.equals(success)) {
+            try {
+                joinPoint.proceed();
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+            redisTemplate.delete(key);
+        }
+    }
+
+}

+ 33 - 0
src/main/java/com/izouma/nineth/aspect/debounce/DebounceTask.java

@@ -0,0 +1,33 @@
+package com.izouma.nineth.aspect.debounce;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+@Slf4j
+public class DebounceTask implements Callable<Void> {
+    private final ProceedingJoinPoint  joinPoint;
+    private final Function<Void, Void> callback;
+
+    public DebounceTask(ProceedingJoinPoint joinPoint, Function<Void, Void> callback) {
+        this.joinPoint = joinPoint;
+        this.callback = callback;
+    }
+
+    @Override
+    public Void call() throws Exception {
+        try {
+            this.joinPoint.proceed();
+            if (this.callback != null) {
+                this.callback.apply(null);
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+
+        return null;
+
+    }
+}

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

@@ -0,0 +1,34 @@
+package com.izouma.nineth.config;
+
+import com.huifu.adapay.Adapay;
+import com.huifu.adapay.model.MerConfig;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@AllArgsConstructor
+@EnableConfigurationProperties({AdapayProperties.class})
+public class AdapayConfig {
+    private final AdapayProperties adapayProperties;
+
+    @PostConstruct
+    public void init() {
+        Adapay.debug = adapayProperties.isDebug();
+        Adapay.prodMode = adapayProperties.isProd();
+
+        MerConfig merConfig = new MerConfig();
+        merConfig.setApiKey(adapayProperties.getApiKey());
+        merConfig.setApiMockKey(adapayProperties.getMockKey());
+        merConfig.setRSAPrivateKey(adapayProperties.getPrivKey());
+        merConfig.setRSAPublicKey(adapayProperties.getPublicKey());
+        Adapay.publicKey = adapayProperties.getAppPublicKey();
+        try {
+            Adapay.initWithMerConfig(merConfig);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 20 - 0
src/main/java/com/izouma/nineth/config/AdapayProperties.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "adapay")
+public class AdapayProperties {
+    private String  merchant;
+    private String  appId;
+    private boolean debug;
+    private boolean prod;
+    private String  apiKey;
+    private String  mockKey;
+    private String  publicKey;
+    private String  privKey;
+    private String  appPublicKey;
+    private String  wxAppId;
+    private String  notifyUrl;
+}

+ 27 - 0
src/main/java/com/izouma/nineth/config/AddResponseHeaderFilter.java

@@ -0,0 +1,27 @@
+package com.izouma.nineth.config;
+
+import com.izouma.nineth.domain.User;
+import com.izouma.nineth.utils.SecurityUtils;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@Order(Ordered.LOWEST_PRECEDENCE)
+public class AddResponseHeaderFilter extends OncePerRequestFilter {
+    @Override
+    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+        User user = SecurityUtils.getAuthenticatedUser();
+        if (user != null) {
+            httpServletResponse.addHeader("X-UID", user.getId() + "");
+        }
+        filterChain.doFilter(httpServletRequest, httpServletResponse);
+    }
+}

+ 21 - 0
src/main/java/com/izouma/nineth/config/AlipayConfig.java

@@ -0,0 +1,21 @@
+package com.izouma.nineth.config;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties({AlipayProperties.class, AlipayProperties.class})
+public class AlipayConfig {
+    private final AlipayProperties properties;
+
+    @Bean
+    public AlipayClient alipayClient() {
+        return new DefaultAlipayClient(properties.getGateway(), properties.getAppId(), properties.getPrivateKey(),
+                "json", "UTF-8", properties.getAliPublicKey(), "RSA2");
+    }
+}

+ 20 - 0
src/main/java/com/izouma/nineth/config/AlipayProperties.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "alipay")
+public class AlipayProperties {
+    private String appId;
+    private String gateway;
+    private String privateKey;
+    private String appPublicKey;
+    private String aliPublicKey;
+    private String apiKey;
+    private String appCertPath;
+    private String aliPubCertPath;
+    private String rootCertPath;
+    private String notifyUrl;
+    private String returnUrl;
+}

+ 131 - 0
src/main/java/com/izouma/nineth/config/CacheConfig.java

@@ -0,0 +1,131 @@
+package com.izouma.nineth.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.izouma.nineth.JsonView.UserView;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+
+@Configuration
+@AutoConfigureAfter({RedisAutoConfiguration.class, CacheAutoConfiguration.class})
+@EnableRedisRepositories(basePackages = "com.izouma.nineth.repo.redis")
+public class CacheConfig {
+
+    @Bean
+    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(redisConnectionFactory);
+
+        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
+        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
+                ObjectMapper.DefaultTyping.NON_FINAL,
+                JsonTypeInfo.As.WRAPPER_ARRAY);
+        mapper.registerModule(new Hibernate5Module()
+                .enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING));
+        mapper.registerModule(new JavaTimeModule());
+        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+        mapper.setConfig(mapper.getSerializationConfig().withView(UserView.Redis.class));
+        SimpleModule simpleModule = new SimpleModule();
+        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
+        mapper.registerModule(simpleModule);
+
+        serializer.setObjectMapper(mapper);
+
+        template.setValueSerializer(serializer);
+        //使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    //    @Bean
+//    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
+//        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
+//        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+//                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
+//        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
+//    }
+    @Bean
+    public RedisCacheManager userRedisCacheManager(RedisTemplate redisTemplate) {
+        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
+                ObjectMapper.DefaultTyping.NON_FINAL,
+                JsonTypeInfo.As.WRAPPER_ARRAY);
+        mapper.registerModule(new Hibernate5Module()
+                .enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING));
+        mapper.registerModule(new JavaTimeModule());
+        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+        mapper.setConfig(mapper.getSerializationConfig().withView(UserView.Redis.class));
+        SimpleModule simpleModule = new SimpleModule();
+        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
+        mapper.registerModule(simpleModule);
+
+        serializer.setObjectMapper(mapper);
+
+        Map<String, RedisCacheConfiguration> cacheNamesConfigurationMap = new HashMap<>();
+        cacheNamesConfigurationMap.put("collectionList", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofSeconds(10))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+        cacheNamesConfigurationMap.put("myUserInfo", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+        cacheNamesConfigurationMap.put("userStat", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+        cacheNamesConfigurationMap.put("userList", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofMinutes(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofDays(7))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
+
+        RedisCacheManager redisCacheManager = RedisCacheManager.builder()
+                .cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory()))
+                .withInitialCacheConfigurations(cacheNamesConfigurationMap)
+                .cacheDefaults(cacheConfiguration)
+                .build();
+
+        return redisCacheManager;
+    }
+}

+ 19 - 0
src/main/java/com/izouma/nineth/config/Constants.java

@@ -0,0 +1,19 @@
+package com.izouma.nineth.config;
+
+public interface Constants {
+
+    public interface Regex {
+        String PHONE    = "^1[3-9]\\d{9}$";
+        String USERNAME = "^[_.@A-Za-z0-9-]*$";
+        String CHINESE  = "^[\\u4e00-\\u9fa5]+$";
+        String ID_NO    = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}[0-9xX]$";
+    }
+
+    String DEFAULT_AVATAR = "https://lasuo.oss-cn-hangzhou.aliyuncs.com/nft/2022-06-15-18-02-32JCxHwndD.png";
+
+    String bizId = "a00e36c5";
+
+    String kmsKey = "ydtg$@WZ9NH&EB2e";
+
+    String SMS_TOKEN_SECRET = "rjbcsj39s9mg9r";
+}

+ 148 - 0
src/main/java/com/izouma/nineth/config/DateConfig.java

@@ -0,0 +1,148 @@
+package com.izouma.nineth.config;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+@Configuration
+public class DateConfig {
+
+    /**
+     * 默认日期时间格式
+     */
+    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 默认日期格式
+     */
+    public static final String DEFAULT_DATE_FORMAT      = "yyyy-MM-dd";
+    /**
+     * 默认时间格式
+     */
+    public static final String DEFAULT_TIME_FORMAT      = "HH:mm:ss";
+
+    /**
+     * LocalDate转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalDate> localDateConverter() {
+        return new Converter<String, LocalDate>() {
+            @Override
+            public LocalDate convert(String source) {
+                return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * LocalDateTime转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalDateTime> localDateTimeConverter() {
+        return new Converter<String, LocalDateTime>() {
+            @Override
+            public LocalDateTime convert(String source) {
+                return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * LocalTime转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalTime> localTimeConverter() {
+        return new Converter<String, LocalTime>() {
+            @Override
+            public LocalTime convert(String source) {
+                return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * Date转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, Date> dateConverter() {
+        return new Converter<String, Date>() {
+            @Override
+            public Date convert(String source) {
+                SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                try {
+                    return format.parse(source);
+                } catch (ParseException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+    }
+
+
+    /**
+     * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
+     */
+    @Bean
+    public ObjectMapper objectMapper() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
+
+        //LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式
+        JavaTimeModule javaTimeModule = new JavaTimeModule();
+        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+
+
+        //Date序列化和反序列化
+        javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() {
+            @Override
+            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+                SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                String formattedDate = formatter.format(date);
+                jsonGenerator.writeString(formattedDate);
+            }
+        });
+        javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
+            @Override
+            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
+                SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                String date = jsonParser.getText();
+                try {
+                    return format.parse(date);
+                } catch (ParseException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+
+        objectMapper.registerModule(javaTimeModule);
+        return objectMapper;
+    }
+
+
+}
+

+ 6 - 0
src/main/java/com/izouma/nineth/config/ErrorCode.java

@@ -0,0 +1,6 @@
+package com.izouma.nineth.config;
+
+public class ErrorCode {
+    public static final int SOLD_OUT = 1001;
+    public static final int OFF_SHELF = 1002;
+}

+ 5 - 0
src/main/java/com/izouma/nineth/config/EventNames.java

@@ -0,0 +1,5 @@
+package com.izouma.nineth.config;
+
+public class EventNames {
+    public final static String SWITCH_ACCOUNT = "switchAccount";
+}

+ 35 - 0
src/main/java/com/izouma/nineth/config/GeneralProperties.java

@@ -0,0 +1,35 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "general")
+@Data
+public class GeneralProperties {
+    private String  host;
+    private String  contractName;
+    private String  name;
+    private String  org;
+    private String  shortName;
+    private String  createOrderGroup;
+    private String  createOrderTopic;
+    private String  updateStockGroup;
+    private String  updateStockTopic;
+    private String  updateSaleGroup;
+    private String  updateSaleTopic;
+    private String  orderNotifyGroup;
+    private String  orderNotifyTopic;
+    private String  mintGroup;
+    private String  mintTopic;
+    private boolean notifyServer;
+    private String  updateActivityStockGroup;
+    private String  updateActivityStockTopic;
+    private int     dataCenterId;
+    private int     workerId;
+    private String  updateQuotaGroup;
+    private String  updateQuotaTopic;
+    private String  broadcastEventGroup;
+    private String  broadcastEventTopic;
+    private String  registerGroup;
+    private String  registerTopic;
+}

+ 30 - 0
src/main/java/com/izouma/nineth/config/HibernateJsonConfig.java

@@ -0,0 +1,30 @@
+package com.izouma.nineth.config;
+
+
+import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.fasterxml.jackson.databind.Module;
+
+/**
+ * Hibernate 懒加载失效 因为json序列化
+ */
+@Configuration
+public class HibernateJsonConfig {
+
+    /**
+     * 注册一个额外的Jackson模块
+     *
+     * @return Module
+     */
+    @Bean
+    public Module hibernate5Module() {
+        Hibernate5Module module = new Hibernate5Module();
+        //禁用(表示要忽略@Transient字段属性,默认为true,设置为false禁用)
+        module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
+        //延时加载的对象不使用时设置为null
+        module.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
+        return module;
+    }
+
+}

+ 36 - 0
src/main/java/com/izouma/nineth/config/LocalDateTimeSerializerConfig.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.config;
+
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Configuration
+public class LocalDateTimeSerializerConfig {
+    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
+    private String pattern;
+
+    @Bean
+    public LocalDateTimeSerializer localDateTimeDeserializer() {
+        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+    }
+
+    @Bean
+    public LocalDateSerializer localDateSerializer() {
+        return new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+    }
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
+            builder.serializerByType(LocalDate.class, localDateSerializer());
+        };
+    }
+}

+ 37 - 0
src/main/java/com/izouma/nineth/config/RedisKeys.java

@@ -0,0 +1,37 @@
+package com.izouma.nineth.config;
+
+public class RedisKeys {
+    public static final String COLLECTION = "collection::";
+
+    public static final String CREATE_ORDER = "createOrder::";
+
+    public static final String COLLECTION_STOCK = "collectionStock::";
+
+    public static final String COLLECTION_SALE = "collectionSale::";
+
+    public static final String COLLECTION_QUOTA = "collectionQuota::";
+
+    public static final String PAY_RECORD = "payRecord::";
+
+    public static final String ORDER_LOCK = "orderLock::";
+
+    public static final String MINT_ACTIVITY_STOCK = "mintActivityStock::";
+
+    public static final String MINT_ORDER_LOCK = "mintOrderLock::";
+
+    public static final String ACTIVITY_PAY_RECORD = "activityPayRecord::";
+
+    public static final String UPDATE_SALE = "updateSale";
+
+    public static final String UPDATE_STOCK = "updateSale";
+
+    public static final String LIMIT_REQ = "limitReq::";
+
+    public static final String LIMIT_USER = "limitUser::";
+
+    public static final String BLACK_LIST = "blackList::";
+
+    public static final String VIP_COLLECTION_LIST = "vipCollectionList";
+
+    public static final String JWT_TOKEN = "jwtToken::";
+}

+ 25 - 0
src/main/java/com/izouma/nineth/config/SandPayConfig.java

@@ -0,0 +1,25 @@
+package com.izouma.nineth.config;
+
+import cn.com.sandpay.cashier.sdk.CertUtil;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@AllArgsConstructor
+@EnableConfigurationProperties({SandPayProperties.class})
+public class SandPayConfig {
+
+    private final SandPayProperties sandPayProperties;
+
+    @PostConstruct
+    public void init() {
+        try {
+            CertUtil.init(sandPayProperties.getSandCertPath(), sandPayProperties.getSignCertPath(), sandPayProperties.getSignCertPwd());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/config/SandPayProperties.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "sandpay")
+public class SandPayProperties {
+    private String url = "https://cashier.sandpay.com.cn/qr/api";
+    private String mid;
+    private String plMid;
+    private String signCertPath;
+    private String signCertPwd;
+    private String sandCertPath;
+    private String notifyUrl;
+}

+ 19 - 0
src/main/java/com/izouma/nineth/config/SchedulingConfig.java

@@ -0,0 +1,19 @@
+package com.izouma.nineth.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+public class SchedulingConfig {
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+        // 定时任务执行线程池核心线程数
+        taskScheduler.setPoolSize(50);
+        taskScheduler.setRemoveOnCancelPolicy(true);
+        taskScheduler.setThreadNamePrefix("Scheduler-");
+        return taskScheduler;
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/config/SnowflakeIdWorkerConfig.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.config;
+
+import com.izouma.nineth.utils.SnowflakeIdWorker;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Slf4j
+public class SnowflakeIdWorkerConfig {
+    @Bean
+    public SnowflakeIdWorker snowflakeIdWorker(GeneralProperties generalProperties) {
+        log.info("init snowflakeIdWorker worker={} dataCenter={}", generalProperties.getWorkerId(), generalProperties.getDataCenterId());
+        return new SnowflakeIdWorker(generalProperties.getWorkerId(), generalProperties.getDataCenterId());
+    }
+}

+ 24 - 0
src/main/java/com/izouma/nineth/config/SpringSecurityAuditorAware.java

@@ -0,0 +1,24 @@
+package com.izouma.nineth.config;
+
+import com.izouma.nineth.domain.User;
+import com.izouma.nineth.utils.SecurityUtils;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.util.Optional;
+
+@Component
+public class SpringSecurityAuditorAware implements AuditorAware<String> {
+
+    @Override
+    @Nonnull
+    public Optional<String> getCurrentAuditor() {
+        String auditor = "system";
+        User user = SecurityUtils.getAuthenticatedUser();
+        if (user != null) {
+            auditor = user.getNickname() + "(" + user.getId() + ")";
+        }
+        return Optional.of(auditor);
+    }
+}

+ 104 - 0
src/main/java/com/izouma/nineth/config/WebMvcConfig.java

@@ -0,0 +1,104 @@
+package com.izouma.nineth.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.List;
+
+@Configuration
+@EnableConfigurationProperties(GeneralProperties.class)
+public class WebMvcConfig implements WebMvcConfigurer {
+    @Value("${storage.local_path}")
+    private String localPath;
+
+    @Autowired
+    private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
+        // registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/admin/**").addResourceLocations("classpath:/static/admin/");
+        registry.addResourceHandler("/files/**").addResourceLocations("file:" + localPath);
+        registry.addResourceHandler("/MP_verify*").addResourceLocations("classpath:/");
+    }
+
+    @Bean
+    public Docket createApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(new ApiInfoBuilder()
+                        .title("接口文档")
+                        .version("1.0.0")
+                        .termsOfServiceUrl("#")
+                        .description("接口文档")
+                        .build())
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.izouma.nineth.web"))
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        converters.stream().filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
+                .forEach(converter -> {
+                    ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
+                    SimpleModule simpleModule = new SimpleModule();
+                    simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+                    simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
+                    objectMapper.registerModule(simpleModule);
+                    ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper);
+                });
+        System.out.println(converters);
+    }
+
+    // @Bean
+    // public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
+    //     MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
+    //     //设置日期格式
+    //     ObjectMapper objectMapper = new ObjectMapper();
+    //     objectMapper.setDateFormat(CustomDateFormat.instance);
+    //     objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+    //     mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
+    //     //设置中文编码格式
+    //     List<MediaType> list = new ArrayList<>();
+    //     list.add(MediaType.APPLICATION_JSON_UTF8);
+    //     mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
+    //     return mappingJackson2HttpMessageConverter;
+    // }
+
+//    @Override
+//    public void addFormatters(FormatterRegistry registry) {
+//        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
+//        registrar.setUseIsoFormat(true);
+//        registrar.registerFormatters(registry);
+//    }
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedHeaders("*")
+                .allowedOriginPatterns("*")
+                .allowCredentials(true)
+                .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH")
+                .exposedHeaders("Content-Disposition");
+    }
+
+}

+ 30 - 0
src/main/java/com/izouma/nineth/config/WxMaConfiguration.java

@@ -0,0 +1,30 @@
+package com.izouma.nineth.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties(WxMaProperties.class)
+public class WxMaConfiguration {
+    private final WxMaProperties properties;
+
+    @Bean
+    public WxMaService wxMaService() {
+        WxMaService service = new WxMaServiceImpl();
+        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+        config.setAppid(properties.getAppId());
+        config.setSecret(properties.getAppSecret());
+        config.setToken(properties.getMsgToken());
+        config.setAesKey(properties.getMsgAesKey());
+        config.setMsgDataFormat(properties.getMsgFormat());
+        service.setWxMaConfig(config);
+        return service;
+    }
+
+}

+ 14 - 0
src/main/java/com/izouma/nineth/config/WxMaProperties.java

@@ -0,0 +1,14 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "wx.ma")
+public class WxMaProperties {
+    private String appId;
+    private String appSecret;
+    private String msgToken;
+    private String msgAesKey;
+    private String msgFormat;
+}

+ 74 - 0
src/main/java/com/izouma/nineth/config/WxMpConfiguration.java

@@ -0,0 +1,74 @@
+package com.izouma.nineth.config;
+
+import com.izouma.nineth.mpHandler.LogHandler;
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties(WxMpProperties.class)
+public class WxMpConfiguration {
+    private final WxMpProperties properties;
+    private final LogHandler     logHandler;
+
+    @Bean
+    public WxMpService wxMpService() {
+        WxMpDefaultConfigImpl mpConfig = new WxMpDefaultConfigImpl();
+        mpConfig.setAppId(properties.getAppId());
+        mpConfig.setSecret(properties.getAppSecret());
+        mpConfig.setAccessToken(properties.getToken());
+        mpConfig.setAesKey(properties.getAesKey());
+        WxMpService service = new WxMpServiceImpl();
+        service.setWxMpConfigStorage(mpConfig);
+        return service;
+    }
+
+    @Bean
+    public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
+        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
+
+        // 记录所有事件的日志 (异步执行)
+        newRouter.rule().handler(this.logHandler).next();
+
+        /*// 接收客服会话管理事件
+        newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
+        newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
+        newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();
+
+        // 门店审核事件
+        newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
+
+        // 自定义菜单事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end();
+
+        // 点击菜单连接事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end();
+
+        // 关注事件
+        newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
+
+        // 取消关注事件
+        newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
+
+        // 上报地理位置事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end();
+
+        // 接收地理位置消息
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end();
+
+        // 扫码事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end();
+
+        // 默认
+        newRouter.rule().async(false).handler(this.msgHandler).end();*/
+
+        return newRouter;
+    }
+
+}

+ 13 - 0
src/main/java/com/izouma/nineth/config/WxMpProperties.java

@@ -0,0 +1,13 @@
+package com.izouma.nineth.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "wx.mp")
+public class WxMpProperties {
+    private String appId;
+    private String appSecret;
+    private String token;
+    private String aesKey;
+}

+ 47 - 0
src/main/java/com/izouma/nineth/config/WxPayConfiguration.java

@@ -0,0 +1,47 @@
+package com.izouma.nineth.config;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Binary Wang
+ */
+@Configuration
+@ConditionalOnClass(WxPayService.class)
+@EnableConfigurationProperties(WxPayProperties.class)
+public class WxPayConfiguration {
+    private WxPayProperties properties;
+
+    @Autowired
+    public WxPayConfiguration(WxPayProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public WxPayService wxService() {
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
+        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
+        payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
+        payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
+        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
+
+        // 可以指定是否使用沙箱环境
+        payConfig.setUseSandboxEnv(false);
+
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        return wxPayService;
+    }
+
+}

Some files were not shown because too many files changed in this diff