Browse Source

first commit

ZhuLei886 2 years ago
commit
d29c2823f9
30 changed files with 2541 additions and 0 deletions
  1. 33 0
      .gitignore
  2. 121 0
      LICENSE
  3. 316 0
      mvnw
  4. 188 0
      mvnw.cmd
  5. 68 0
      pom.xml
  6. 13 0
      src/main/java/com/example/demo/Application.java
  7. 69 0
      src/main/java/com/example/demo/configuration/LogClientHttpRequestInterceptor.java
  8. 150 0
      src/main/java/com/example/demo/configuration/RestTemplateConfiguration.java
  9. 112 0
      src/main/java/com/example/demo/controller/CustomNFTController.java
  10. 32 0
      src/main/java/com/example/demo/model/dto/CreateAccountDTO.java
  11. 58 0
      src/main/java/com/example/demo/model/dto/DealResultDTO.java
  12. 23 0
      src/main/java/com/example/demo/model/dto/ErrorDTO.java
  13. 21 0
      src/main/java/com/example/demo/model/dto/Message.java
  14. 57 0
      src/main/java/com/example/demo/model/dto/NFTClassDTO.java
  15. 36 0
      src/main/java/com/example/demo/model/dto/NFTClassListDTO.java
  16. 27 0
      src/main/java/com/example/demo/model/dto/NFTOperateDTO.java
  17. 49 0
      src/main/java/com/example/demo/model/dto/SNFTDTO.java
  18. 55 0
      src/main/java/com/example/demo/model/req/AccReq.java
  19. 61 0
      src/main/java/com/example/demo/model/req/NFTClassListReq.java
  20. 69 0
      src/main/java/com/example/demo/model/req/NFTCreateReq.java
  21. 61 0
      src/main/java/com/example/demo/model/req/NFTPublishReq.java
  22. 52 0
      src/main/java/com/example/demo/model/req/NFTTransferReq.java
  23. 60 0
      src/main/java/com/example/demo/proxy/AvataProxy.java
  24. 458 0
      src/main/java/com/example/demo/proxy/impl/AvataProxyImpl.java
  25. 24 0
      src/main/java/com/example/demo/util/AvataProperties.java
  26. 83 0
      src/main/java/com/example/demo/util/AvataUtils.java
  27. 62 0
      src/main/java/com/example/demo/util/UUIDUtils.java
  28. 15 0
      src/main/resources/application.yml
  29. 71 0
      src/main/resources/logback.xml
  30. 97 0
      src/test/java/com/example/demo/AvataProxyTest.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### 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/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 121 - 0
LICENSE

@@ -0,0 +1,121 @@
+                     木兰宽松许可证, 第1版
+
+   木兰宽松许可证, 第1版
+   2019年8月 http://license.coscl.org.cn/MulanPSL
+
+   您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束:
+
+   0. 定义
+
+      “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
+
+      “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
+
+      “法人实体”是指提交贡献的机构及其“关联实体”。
+
+      “关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
+
+      “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
+
+   1. 授予版权许可
+
+      每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
+
+   2. 授予专利许可
+
+      每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
+
+   3. 无商标许可
+
+      “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
+
+   4. 分发限制
+
+      您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
+
+   5. 免责声明与责任限制
+
+      “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
+
+   条款结束。
+
+   如何将木兰宽松许可证,第1版,应用到您的软件
+
+   如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
+
+      1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
+
+      2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
+
+      3, 请将如下声明文本放入每个源文件的头部注释中。
+
+   Copyright (c) [2019] [name of copyright holder]
+   [Software Name] is licensed under the Mulan PSL v1.
+   You can use this software according to the terms and conditions of the Mulan PSL v1.
+   You may obtain a copy of Mulan PSL v1 at:
+      http://license.coscl.org.cn/MulanPSL
+   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+   PURPOSE.
+   See the Mulan PSL v1 for more details.
+
+
+                     Mulan Permissive Software License,Version 1
+
+   Mulan Permissive Software License,Version 1 (Mulan PSL v1)
+   August 2019 http://license.coscl.org.cn/MulanPSL
+
+   Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions:
+
+   0. Definition
+
+      Software means the program and related documents which are comprised of those Contribution and licensed under this License.
+
+      Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
+
+      Legal Entity means the entity making a Contribution and all its Affiliates.
+
+      Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
+
+   Contribution means the copyrightable work licensed by a particular Contributor under this License.
+
+   1. Grant of Copyright License
+
+      Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
+
+   2. Grant of Patent License
+
+      Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
+
+   3. No Trademark License
+
+      No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4.
+
+   4. Distribution Restriction
+
+      You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
+
+   5. Disclaimer of Warranty and Limitation of Liability
+
+      The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages.
+
+   End of the Terms and Conditions
+
+   How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software
+
+      To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps:
+
+      i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
+      ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
+      iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file.
+   
+   Copyright (c) [2019] [name of copyright holder]
+   [Software Name] is licensed under the Mulan PSL v1.
+   You can use this software according to the terms and conditions of the Mulan PSL v1.
+   You may obtain a copy of Mulan PSL v1 at:
+      http://license.coscl.org.cn/MulanPSL
+   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+   PURPOSE.
+   
+   See the Mulan PSL v1 for more details.

+ 316 - 0
mvnw

@@ -0,0 +1,316 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven 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 /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  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)`"
+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="`\\unset -f command; \\command -v 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
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    fi
+    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 $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        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
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 188 - 0
mvnw.cmd

@@ -0,0 +1,188 @@
+@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 Maven 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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq 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% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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%
+
+cmd /C exit /B %ERROR_CODE%

+ 68 - 0
pom.xml

@@ -0,0 +1,68 @@
+<?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.7.0</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.example</groupId>
+    <artifactId>demo</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>demo</name>
+    <description>Demo project for Spring Boot</description>
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.10</version>
+        </dependency>
+        <!--mybatis plus extension,包含了mybatis plus core-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>3.4.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.16</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
src/main/java/com/example/demo/Application.java

@@ -0,0 +1,13 @@
+package com.example.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+}

+ 69 - 0
src/main/java/com/example/demo/configuration/LogClientHttpRequestInterceptor.java

@@ -0,0 +1,69 @@
+package com.example.demo.configuration;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StopWatch;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+
+@Component
+public class LogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
+    @Override
+    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+
+        StopWatch stopWatch = new StopWatch();
+        stopWatch.start();
+        ClientHttpResponse response = execution.execute(request, body);
+
+        stopWatch.stop();
+        StringBuilder resBody = new StringBuilder();
+        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(),
+                Charset.forName("UTF-8")))) {
+            String line = bufferedReader.readLine();
+            while (line != null) {
+                resBody.append(line);
+                line = bufferedReader.readLine();
+            }
+        }
+        //当然图片、文件一类的就可以省了,打出日志没啥用处,此处的业务逻辑随意撸了,比如header头信息类似于  Accept 、Accept-Encoding 、Accept-Language、Connection 等等
+        if (request.getHeaders().getContentType() != null && request.getHeaders().getContentType().includes(MediaType.MULTIPART_FORM_DATA)) {
+            body = new byte[]{};
+        }
+
+        RestLog restLog = RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(request.getHeaders()).method(request.getMethodValue())
+                .reqUrl(request.getURI().toString()).reqBody(new String(body, Charset.forName("UTF-8"))).resBody(resBody.toString()).resStatus(response.getRawStatusCode()).build();
+
+        System.out.println(restLog.toString());
+        return response;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @SuppressWarnings("rawtypes")
+    private static class RestLog {
+        private String reqUrl;
+        private String method;
+        private HttpHeaders headers;
+        private String reqBody;
+        private String resBody;
+        private long costTime;
+        private int resStatus;
+
+
+    }
+}
+

+ 150 - 0
src/main/java/com/example/demo/configuration/RestTemplateConfiguration.java

@@ -0,0 +1,150 @@
+package com.example.demo.configuration;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.BufferingClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+@Configuration
+public class RestTemplateConfiguration {
+    @Value("${http.maxTotal}")
+    private Integer maxTotal;
+    @Value("${http.maxPerRouter}")
+    private Integer defaultMaxPerRoute;
+
+    /**
+     * 连接上服务器(握手成功)的时间,超出抛出connect timeout
+     */
+    @Value("${http.connectionTimeout}")
+    private Integer connectTimeout;
+
+    @Value("${http.connectionRequestTimeout}")
+    private Integer connectionRequestTimeout;
+
+    @Value("${http.readTime}")
+    private Integer readTime;
+
+    @Autowired
+    private LogClientHttpRequestInterceptor logClientHttpRequestInterceptor;
+
+
+    @Bean(name = "restTemplate")
+    public RestTemplate restTemplate() {
+        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
+        List<ClientHttpRequestInterceptor> interceptorList = new ArrayList<>();
+        interceptorList.add(logClientHttpRequestInterceptor);
+        restTemplate.setInterceptors(interceptorList);
+
+        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
+
+
+        Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
+        while (iterator.hasNext()) {
+            HttpMessageConverter<?> converter = iterator.next();
+            if (converter instanceof StringHttpMessageConverter) {
+                ((StringHttpMessageConverter) converter).setDefaultCharset(Charset.forName("UTF-8"));
+            }
+
+            if(converter instanceof MappingJackson2HttpMessageConverter){
+                ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper());
+
+            }
+
+        }
+
+        return restTemplate;
+    }
+
+    @Bean
+    public ClientHttpRequestFactory httpRequestFactory() {
+        return new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient()));
+    }
+
+    @Bean
+    public HttpClient httpClient() {
+        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
+                .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                .build();
+        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
+        connectionManager.setMaxTotal(maxTotal);
+        connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
+
+        RequestConfig requestConfig = RequestConfig.custom()
+                //服务器返回数据(response)的时间,超过抛出read timeout
+                .setSocketTimeout(readTime)
+                //连接上服务器(握手成功)的时间,超出抛出connect timeout
+                .setConnectTimeout(connectTimeout)
+                //从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
+                .setConnectionRequestTimeout(connectionRequestTimeout)
+                .build();
+        return HttpClientBuilder.create()
+                .setDefaultRequestConfig(requestConfig)
+                .setConnectionManager(connectionManager)
+                .build();
+    }
+
+
+
+
+    @Bean(name = "objectMapper")
+    public ObjectMapper objectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
+            @Override
+            public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+                String result = StringDeserializer.instance.deserialize(p, ctxt);
+                if (StringUtils.isEmpty(result)) {
+                    return null;
+                }
+                return result;
+            }
+        });
+        mapper.registerModule(module);
+        mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
+        mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+//		mapper.setPropertyNamingStrategy(new CustomNameStrategy());
+
+        // 其他配置省略,可根据需要配置
+        return mapper;
+    }
+
+
+
+
+}

+ 112 - 0
src/main/java/com/example/demo/controller/CustomNFTController.java

@@ -0,0 +1,112 @@
+package com.example.demo.controller;
+
+import com.example.demo.model.dto.*;
+import com.example.demo.model.req.*;
+import com.example.demo.proxy.AvataProxy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @Created by:   zhulei
+ * 2022/6/1 13:55
+ * code is far away from bug with the animal protecting
+ * <p>
+ * <p>
+ * █████▒█    ██  ▄████▄   ██ ▄█▀       ██████╗ ██╗   ██╗ ██████╗
+ * ▓██   ▒ ██  ▓██▒▒██▀ ▀█   ██▄█▒        ██╔══██╗██║   ██║██╔════╝
+ * ▒████ ░▓██  ▒██░▒▓█    ▄ ▓███▄░        ██████╔╝██║   ██║██║  ███╗
+ * ░▓█▒  ░▓▓█  ░██░▒▓▓▄ ▄██▒▓██ █▄        ██╔══██╗██║   ██║██║   ██║
+ * ░▒█░   ▒▒█████▓ ▒ ▓███▀ ░▒██▒ █▄       ██████╔╝╚██████╔╝╚██████╔╝
+ * ▒ ░   ░▒▓▒ ▒ ▒ ░ ░▒ ▒  ░▒ ▒▒ ▓▒       ╚═════╝  ╚═════╝  ╚═════╝
+ * ░     ░░▒░ ░ ░   ░  ▒   ░ ░▒ ▒░
+ * ░ ░    ░░░ ░ ░ ░        ░ ░░ ░
+ * ░     ░ ░      ░  ░
+ * ░
+ */
+@Slf4j
+@RestController
+@RequestMapping("customNFT")
+public class CustomNFTController {
+    @Autowired
+    AvataProxy avataProxy;
+
+    @RequestMapping("/createAccount")
+    public CreateAccountDTO createAccount(String name, String operationId) throws Exception {
+        CreateAccountDTO account = avataProxy.createAccount( name, operationId );
+
+        return account;
+    }
+//    @RequestMapping("/selectAccount")
+//    public void selectAccount(String acc,String operationId) throws Exception {
+//        AccReq accReq = new AccReq(  );
+//        accReq.setAccount( acc );
+//        accReq.setOperation_id( operationId );
+//         avataProxy.SELECT_ACCOUNTS( accReq);
+//
+////        return account;
+//    }
+
+    //发行
+    @RequestMapping("/createNft")
+    public NFTOperateDTO createNft(String classId, String name, String operationId,String recipient) throws Exception {
+        NFTPublishReq nftPublishReq = new NFTPublishReq();
+
+        nftPublishReq.setClass_id( classId );
+        nftPublishReq.setName( name );
+        nftPublishReq.setOperation_id( operationId );
+        nftPublishReq.setRecipient( recipient );
+
+        NFTOperateDTO nftOperateDTO = avataProxy.publishNFT( nftPublishReq );
+        return nftOperateDTO;
+    }
+
+    //发行
+    @RequestMapping("/sNFT")
+    public SNFTDTO sNFT(String classId, String NFTId) throws Exception {
+        NFTTransferReq nftTransferReq  = new NFTTransferReq(  );
+
+        nftTransferReq.setClass_id( classId );
+        nftTransferReq.setNft_id( NFTId );//nftId
+
+
+        SNFTDTO snftdto = avataProxy.sNFT( nftTransferReq );
+        return snftdto;
+    }
+
+    //上架
+    @RequestMapping("/setNftTypes")
+    public NFTOperateDTO setNftTypes(String name, String owner, String operationId) {
+        NFTCreateReq nftClassListReq = new NFTCreateReq();
+        nftClassListReq.setName( name );
+//        nftClassListReq.setClass_id( classId );
+        nftClassListReq.setOwner( owner );
+        nftClassListReq.setOperation_id( operationId );
+//        nftClassListReq.setUri( tokeUri );
+        NFTOperateDTO nftClassListDTO = avataProxy.createNFT( nftClassListReq );
+        return nftClassListDTO;
+    }
+
+    //查询回执
+    @RequestMapping("/queryDealResult")
+    public DealResultDTO queryDealResult(String operationId) {
+        DealResultDTO dealResultDTO = avataProxy.queryDealResult( operationId );
+        return dealResultDTO;
+    }
+
+    //
+    //转让NFT
+    @RequestMapping("/transfer")
+    public NFTOperateDTO transfer(String classId,String NFTId,String operationId,String owner,
+                                  String recipient) {
+        NFTTransferReq nftTransferReq  = new NFTTransferReq(  );
+        nftTransferReq.setClass_id( classId );//NFT 类别 ID
+        nftTransferReq.setNft_id( NFTId );//NFT ID
+        nftTransferReq.setOperation_id( operationId );//操作 ID
+        nftTransferReq.setOwner( owner );//NFT 持有者地址
+        nftTransferReq.setRecipient( recipient );//NFT 接收者地址
+        NFTOperateDTO nftOperateDTO = avataProxy.transferNFT( nftTransferReq );
+        return nftOperateDTO;
+    }
+}

+ 32 - 0
src/main/java/com/example/demo/model/dto/CreateAccountDTO.java

@@ -0,0 +1,32 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateAccountDTO {
+
+    /**
+     * 链账户地址
+     */
+    private String account;
+
+    /**
+     * 链账户名称
+     */
+    private String name;
+
+    /**
+     * 操作id
+     */
+    private String operation_id;
+}

+ 58 - 0
src/main/java/com/example/demo/model/dto/DealResultDTO.java

@@ -0,0 +1,58 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DealResultDTO {
+    /**
+     * 用户操作类型
+     */
+    private String type;
+
+    /**
+     * 交易hash
+     */
+    private String tx_hash;
+
+    /**
+     * 交易状态, 0 处理中; 1 成功; 2 失败; 3 未处理
+     */
+    private Integer status;
+
+    /**
+     * 类别id
+     */
+    private String class_id;
+
+    /**
+     * NFT ID
+     */
+    private String nft_id;
+
+    /**
+     * 错误描述
+     */
+    private String message;
+
+    /**
+     * 交易上链的区块高度
+     */
+    private String block_height;
+
+    /**
+     * 交易标签, 自定义 key:支持大小写英文字母和汉字和数字,长度6-12位,自定义 value:长度限制在64位字符,支持大小写字母和数字
+     */
+    private Map<String, Object> tag;
+}

+ 23 - 0
src/main/java/com/example/demo/model/dto/ErrorDTO.java

@@ -0,0 +1,23 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErrorDTO {
+
+    private String code;
+
+    private String code_space;
+
+    private String message;
+}

+ 21 - 0
src/main/java/com/example/demo/model/dto/Message.java

@@ -0,0 +1,21 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Message<T> implements Serializable {
+    private T data;
+
+}

+ 57 - 0
src/main/java/com/example/demo/model/dto/NFTClassDTO.java

@@ -0,0 +1,57 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTClassDTO {
+    /**
+     * NFT类别ID
+     */
+    private String id;
+
+    /**
+     * NFT类别名称
+     */
+    private String name;
+
+    /**
+     * NFT类别标识
+     */
+    private String symbol;
+
+    /**
+     * NFT 类别包含的 NFT 总量
+     */
+    private Integer nft_count;
+
+    /**
+     * 链外数据连接
+     */
+    private String uri;
+
+    /**
+     * NFT类别权属者地址
+     */
+    private String owner;
+
+    /**
+     * 创建 NFT 类别的 Tx Hash
+     */
+    private String tx_hash;
+
+    /**
+     * 创建 NFT 类别的时间戳(UTC 时间)
+     */
+    private String timestamp;
+
+}

+ 36 - 0
src/main/java/com/example/demo/model/dto/NFTClassListDTO.java

@@ -0,0 +1,36 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTClassListDTO {
+
+    /**
+     * 游标
+     */
+    private Integer offset;
+
+    /**
+     * 每页数量
+     */
+    private Integer limit;
+
+    /**
+     * 总记录数
+     */
+    private Integer total_count;
+
+    private List<NFTClassDTO> classes;
+}

+ 27 - 0
src/main/java/com/example/demo/model/dto/NFTOperateDTO.java

@@ -0,0 +1,27 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTOperateDTO {
+
+    /**
+     * TASK ID
+     */
+    private String task_id;
+
+    /**
+     * 操作ID
+     */
+    private String operation_id;
+}

+ 49 - 0
src/main/java/com/example/demo/model/dto/SNFTDTO.java

@@ -0,0 +1,49 @@
+package com.example.demo.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SNFTDTO {
+
+    private String id;
+
+
+    private String name;
+
+
+    private String class_id;
+
+
+    private String class_name;
+
+
+    private String class_symbol;
+
+
+    private String uri;
+
+    private String uri_hash;
+
+    private String data;
+
+    private String owner;
+
+    private String status;
+
+    private String tx_hash;
+
+    private String timestamp;
+
+}

+ 55 - 0
src/main/java/com/example/demo/model/req/AccReq.java

@@ -0,0 +1,55 @@
+package com.example.demo.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AccReq {
+    /**
+     * 游标,默认0
+     */
+    private String offset;
+
+    /**
+     * 每页数量
+     */
+    private String limit;
+
+    //链账户地址
+    private String account;
+
+    /**
+     *创建日期范围 - 开始,yyyy-MM-dd(UTC 时间)
+     */
+    private String start_date;
+
+    /**
+     * 创建日期范围 - 结束,yyyy-MM-dd(UTC 时间)
+     */
+    private String end_date;
+
+    /**
+     * 排序规则:DATE_ASC / DATE_DESC
+     */
+    private String sort_by;
+
+    /**
+     * 操作 ID
+     */
+    private String operation_id;
+
+    /**
+     * 链账户名称,支持模糊查询
+     */
+    private String name;
+
+}

+ 61 - 0
src/main/java/com/example/demo/model/req/NFTClassListReq.java

@@ -0,0 +1,61 @@
+package com.example.demo.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTClassListReq {
+    /**
+     * 游标,默认0
+     */
+    private String offset;
+
+    /**
+     * 每页数量
+     */
+    private String limit;
+
+    /**
+     * NFT类别ID
+     */
+    private String id;
+
+    /**
+     * NFT类别名称
+     */
+    private String name;
+
+    /**
+     * NFT类别权属者地址
+     */
+    private String owner;
+
+    /**
+     * 创建 NFT 类别的 Tx Hash
+     */
+    private String tx_hash;
+
+    /**
+     * NFT 类别创建日期范围 - 开始,yyyy-MM-dd(UTC 时间)
+     */
+    private String start_date;
+
+    /**
+     * NFT 类别创建日期范围 - 结束,yyyy-MM-dd(UTC 时间)
+     */
+    private String end_date;
+
+    /**
+     * 排序规则:DATE_ASC / DATE_DESC
+     */
+    private String sort_by;
+}

+ 69 - 0
src/main/java/com/example/demo/model/req/NFTCreateReq.java

@@ -0,0 +1,69 @@
+package com.example.demo.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTCreateReq {
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * NFT 类别 ID
+     */
+    private String class_id;
+
+    /**
+     * 标识
+     */
+    private String symbol;
+
+    /**
+     * 描述
+     */
+    private String description;
+
+    /**
+     * 链外数据链接
+     */
+    private String uri;
+
+    /**
+     * 链外数据hash
+     */
+    private String uri_hash;
+
+    /**
+     * 自定义链上元数据
+     */
+    private String data;
+
+    /**
+     * NFT 类别权属者地址,支持任一文昌链合法链账户地址
+     */
+    private String owner;
+
+    /**
+     * 交易标签, 自定义 key:支持大小写英文字母和汉字和数字,长度6-12位,自定义 value:长度限制在64位字符,支持大小写字母和数字
+     */
+    private Map<String, Object> tag;
+
+    /**
+     * 操作 ID,保证幂等性,避免重复请求,保证对于同一操作发起的一次请求或者多次请求的结果是一致的;由接入方生成的、针对每个 Project ID 唯一的、不超过 64 个大小写字母、数字、-、下划线的字符串
+     */
+    private String operation_id;
+}

+ 61 - 0
src/main/java/com/example/demo/model/req/NFTPublishReq.java

@@ -0,0 +1,61 @@
+package com.example.demo.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTPublishReq {
+
+    /**
+     * NFT类别id
+     * 必传
+     */
+    private String class_id;
+
+    /**
+     * NFT名称
+     * 必传
+     */
+    private String name;
+
+    /**
+     * 链外数据链接
+     */
+    private String uri;
+
+    /**
+     * 链外数据hash
+     */
+    private String uri_hash;
+
+    /**
+     * 自定义链上元数据
+     */
+    private String data;
+
+    /**
+     * NFT 接收者地址,支持任一文昌链合法链账户地址,默认为 NFT 类别的权属者地址
+     */
+    private String recipient;
+
+    /**
+     * 交易标签, 自定义 key:支持大小写英文字母和汉字和数字,长度6-12位,自定义 value:长度限制在64位字符,支持大小写字母和数字
+     */
+    private Map<String, Object> tag;
+
+    /**
+     * 操作 ID,保证幂等性,避免重复请求,保证对于同一操作发起的一次请求或者多次请求的结果是一致的;由接入方生成的、针对每个 Project ID 唯一的、不超过 64 个大小写字母、数字、-、下划线的字符串
+     */
+    private String operation_id;
+}

+ 52 - 0
src/main/java/com/example/demo/model/req/NFTTransferReq.java

@@ -0,0 +1,52 @@
+package com.example.demo.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NFTTransferReq {
+
+    /**
+     * NFT类别ID
+     * 必填
+     */
+    private String class_id;
+
+    /**
+     * NFT持有者地址
+     * 必填
+     */
+    private String owner;
+
+    /**
+     * NFT ID
+     * 必填
+     */
+    private String nft_id;
+
+    /**
+     * NFT 接收者地址,支持任一文昌链合法链账户地址,默认为 NFT 类别的权属者地址
+     */
+    private String recipient;
+
+    /**
+     * 交易标签, 自定义 key:支持大小写英文字母和汉字和数字,长度6-12位,自定义 value:长度限制在64位字符,支持大小写字母和数字
+     */
+    private Map<String, Object> tag;
+
+    /**
+     * 操作 ID,保证幂等性,避免重复请求,保证对于同一操作发起的一次请求或者多次请求的结果是一致的;由接入方生成的、针对每个 Project ID 唯一的、不超过 64 个大小写字母、数字、-、下划线的字符串
+     */
+    private String operation_id;
+}

+ 60 - 0
src/main/java/com/example/demo/proxy/AvataProxy.java

@@ -0,0 +1,60 @@
+package com.example.demo.proxy;
+
+
+import com.example.demo.model.dto.*;
+import com.example.demo.model.req.*;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+public interface AvataProxy {
+
+    /**
+     * 创建离岸账户
+     * @param name 账户名称
+     * @param operationId 操作id,需幂等
+     * @return
+     */
+    CreateAccountDTO createAccount(String name, String operationId);
+
+    void SELECT_ACCOUNTS(AccReq accReq);
+
+    /**
+     * 查询NFT类别
+     * @param nftClassListReq
+     * @return
+     */
+    NFTClassListDTO queryNFTClassList(NFTClassListReq nftClassListReq);
+
+    /**
+     * 发行NFT
+     * @param nftPublishReq
+     * @return
+     */
+    NFTOperateDTO publishNFT(NFTPublishReq nftPublishReq);
+
+    /**
+     * 上链交易结果查询
+     * @param taskId TASK ID
+     * @return
+     */
+    DealResultDTO queryDealResult(String taskId);
+
+    /**
+     * 转让NFT
+     * @param nftTransferReq
+     * @return
+     */
+    NFTOperateDTO transferNFT(NFTTransferReq nftTransferReq);
+
+    SNFTDTO sNFT(NFTTransferReq nftTransferReq);
+
+
+    /**
+     * 创建NFT类别
+     * @param nftCreateReq
+     * @return
+     */
+    NFTOperateDTO createNFT(NFTCreateReq nftCreateReq);
+}

+ 458 - 0
src/main/java/com/example/demo/proxy/impl/AvataProxyImpl.java

@@ -0,0 +1,458 @@
+package com.example.demo.proxy.impl;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.example.demo.model.dto.*;
+import com.example.demo.model.req.*;
+import com.example.demo.proxy.AvataProxy;
+import com.example.demo.util.AvataProperties;
+import com.example.demo.util.AvataUtils;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.annotation.Resource;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class AvataProxyImpl implements AvataProxy {
+
+    @Autowired
+    private AvataProperties avataProperties;
+    @Resource
+    private RestTemplate restTemplate;
+
+    private static final String CREATE_ACCOUNT_URL = "/v1beta1/account";
+
+    private static final String NFT_CLASS_LIST_QUERY_LIST = "/v1beta1/nft/classes";
+
+    private static final String NFT_PUBLISH_URL_PRE = "/v1beta1/nft/nfts/";
+
+    private static final String DEAL_RESULT_QUERY_URL_PRE = "/v1beta1/tx/";
+
+    private static final String NFT_TRANSFER_URL_PRE = "/v1beta1/nft/nft-transfers/";
+
+    private static final String NFT_CREATE_URL = "/v1beta1/nft/classes";
+
+    private static final String NFT_SELECT_ACCOUNTS = "/v1beta1/accounts";
+
+//    private static final String S_NFT_CREATE_URL = "/v1beta1/nft/classes";
+
+    //创建链账户
+    @Override
+    public CreateAccountDTO createAccount(String name, String operationId) {
+        if (StringUtils.isBlank(name)) {
+            throw new RuntimeException("缺少必传参数name");
+        }
+        if (StringUtils.isBlank(operationId)) {
+            throw new RuntimeException("缺少必传参数operationId");
+        }
+        Long currentTime  = System.currentTimeMillis();
+        // 请求body
+        Map<String, Object> body = new HashMap<>();
+        body.put("name", name);
+        body.put("operation_id", operationId);
+        // 验签
+        String signature = AvataUtils.signRequest(CREATE_ACCOUNT_URL, null, body, currentTime, avataProperties.getApiSecret());
+        // 请求体
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+        headers.setContentType(MediaType.parseMediaType("application/json"));
+
+        HttpEntity<String> httpEntity = new HttpEntity<>(JSONObject.toJSONString(body), headers);
+
+        String url = avataProperties.getAvataUrl() + CREATE_ACCOUNT_URL;
+        log.info("文昌链接口请求地址"+url);
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<CreateAccountDTO>> typeReference = new ParameterizedTypeReference<Message<CreateAccountDTO>>() {
+            };
+            ResponseEntity<Message<CreateAccountDTO>> responseEntity;
+
+            responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, typeReference);
+
+            Message<CreateAccountDTO> message = responseEntity.getBody();
+
+            CreateAccountDTO createAccountDTO = message.getData();
+            // 成功
+            log.info("创建链账户成功:" + createAccountDTO);
+
+            return createAccountDTO;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("非200处理链账户创建失败:"+name+":"+operationId);
+            e.printStackTrace();
+            throw new RuntimeException("链账户创建失败");
+        }
+    }
+    //查询链账户
+    public void SELECT_ACCOUNTS(AccReq accReq) {
+
+
+        String path = NFT_SELECT_ACCOUNTS;
+
+        Map<String, Object> body = JSON.parseObject(JSON.toJSONString(accReq), new TypeReference<Map<String, Object>>() {
+        });
+
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(path, null, body, currentTime, avataProperties.getApiSecret());
+
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<SNFTDTO>> typeReference = new ParameterizedTypeReference<Message<SNFTDTO>>() {
+            };
+            ResponseEntity<Message<SNFTDTO>> responseEntity = restTemplate.exchange(avataProperties.getAvataUrl() + path, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
+
+
+            log.info("查询结果:" + responseEntity.getBody());
+//            return responseEntity.getBody();;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            e.printStackTrace();
+            throw new RuntimeException("查询失败");
+        }
+    }
+
+    @Override
+    public NFTClassListDTO queryNFTClassList(NFTClassListReq nftClassListReq) {
+        Map<String, Object> query = null == nftClassListReq ? new HashMap<>() :
+                JSON.parseObject(JSON.toJSONString(nftClassListReq), new TypeReference<Map<String, Object>>() {
+                });
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(NFT_CLASS_LIST_QUERY_LIST, query, null, currentTime, avataProperties.getApiSecret());
+
+        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(avataProperties.getAvataUrl() + NFT_CLASS_LIST_QUERY_LIST);
+        for (Map.Entry<String, Object> entry : query.entrySet()) {
+            builder.queryParam(entry.getKey(), entry.getValue());
+        }
+
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<NFTClassListDTO>> typeReference = new ParameterizedTypeReference<Message<NFTClassListDTO>>() {
+            };
+            ResponseEntity<Message<NFTClassListDTO>> responseEntity = restTemplate.exchange(new URI(builder.toUriString()), HttpMethod.GET, new HttpEntity<>(headers), typeReference);
+
+            Message<NFTClassListDTO> message = responseEntity.getBody();
+            NFTClassListDTO nftClassListDTO = message.getData();
+
+            if (null != nftClassListDTO && null != nftClassListDTO.getClasses() && nftClassListDTO.getClasses().size() != 0) {
+                // 遍历处理类别列表
+                for (NFTClassDTO nftClassDTO : nftClassListDTO.getClasses()) {
+
+                }
+            }
+
+            log.info("NFT类别查询结果:" + nftClassListDTO);
+
+            return nftClassListDTO;
+
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("非200查询NFT类别失败:"+nftClassListReq);
+            e.printStackTrace();
+            throw new RuntimeException("查询NFT类别失败");
+        }
+    }
+
+    //发行NFT
+    @Override
+    public NFTOperateDTO publishNFT(NFTPublishReq nftPublishReq) {
+        if (null == nftPublishReq) {
+            log.info("发行NFT失败,缺少参数:"+ null);
+            throw new RuntimeException("发行NFT失败,缺少参数");
+        }
+        if (null == nftPublishReq.getClass_id()) {
+            log.info("发行NFT失败,缺少参数class_id:"+nftPublishReq);
+            throw new RuntimeException("发行NFT失败,缺少参数class_id");
+        }
+        if (null == nftPublishReq.getName()) {
+            log.info("发行NFT失败,缺少参数name:"+nftPublishReq);
+            throw new RuntimeException("发行NFT失败,缺少参数name");
+        }
+        if (null == nftPublishReq.getOperation_id()) {
+            log.info("发行NFT失败,缺少参数operation_id:"+nftPublishReq);
+//            throw new RuntimeException("发行NFT失败,缺少参数operation_id");
+        }
+
+        String path = NFT_PUBLISH_URL_PRE + nftPublishReq.getClass_id();
+
+        Map<String, Object> body = JSON.parseObject(JSON.toJSONString(nftPublishReq), new TypeReference<Map<String, Object>>() {
+        });
+
+        body.remove("class_id");
+
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(path, null, body, currentTime, avataProperties.getApiSecret());
+        // 请求体
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+        headers.setContentType(MediaType.parseMediaType("application/json"));
+
+        HttpEntity<String> httpEntity = new HttpEntity<>(JSONObject.toJSONString(body), headers);
+
+        String url = avataProperties.getAvataUrl() + path;
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<NFTOperateDTO>> typeReference = new ParameterizedTypeReference<Message<NFTOperateDTO>>() {
+            };
+            ResponseEntity<Message<NFTOperateDTO>> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, typeReference);
+
+            Message<NFTOperateDTO> message = responseEntity.getBody();
+
+            NFTOperateDTO nftOperateDTO = message.getData();
+            // 成功
+            log.info("发行NFT成功:" + nftOperateDTO);
+
+            return nftOperateDTO;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("非200发行NFT失败:参数为:"+nftPublishReq);
+            e.printStackTrace();
+            throw new RuntimeException("发行NFT失败");
+        }
+    }
+
+    //上链交易结果查询
+    @Override
+    public DealResultDTO queryDealResult(String taskId) {
+        if (StringUtils.isBlank(taskId)) {
+            log.info("上链交易结果查询失败,缺少参数taskId:"+taskId);
+            throw new RuntimeException("上链交易结果查询失败,缺少参数taskId");
+        }
+
+        String path = DEAL_RESULT_QUERY_URL_PRE + taskId;
+
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(path, null, null, currentTime, avataProperties.getApiSecret());
+
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<DealResultDTO>> typeReference = new ParameterizedTypeReference<Message<DealResultDTO>>() {
+            };
+            ResponseEntity<Message<DealResultDTO>> responseEntity = restTemplate.exchange(avataProperties.getAvataUrl() + path, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
+
+            Message<DealResultDTO> message = responseEntity.getBody();
+            DealResultDTO dealResultDTO = message.getData();
+            log.info("上链交易结果查询:" + dealResultDTO);
+            return dealResultDTO;
+
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("非200上链交易结果查询失败");
+            e.printStackTrace();
+            throw new RuntimeException("上链交易结果查询失败");
+        }
+    }
+
+    //转让NFT
+    @Override
+    public NFTOperateDTO transferNFT(NFTTransferReq nftTransferReq) {
+        if (null == nftTransferReq) {
+            log.info("转让NFT失败,缺少参数:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数");
+        }
+        if (null == nftTransferReq.getClass_id()) {
+            log.info("转让NFT失败,缺少参数class_id:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数class_id");
+        }
+        if (null == nftTransferReq.getOwner()) {
+            log.info("转让NFT失败,缺少参数owner:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数owner");
+        }
+        if (null == nftTransferReq.getNft_id()) {
+            log.info("转让NFT失败,缺少参数nft_id:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数nft_id");
+        }
+        if (null == nftTransferReq.getRecipient()) {
+            log.info("转让NFT失败,缺少参数recipient:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数recipient");
+        }
+        if (null == nftTransferReq.getOperation_id()) {
+            log.info("转让NFT失败,缺少参数operation_id:"+nftTransferReq);
+            throw new RuntimeException("转让NFT失败,缺少参数operation_id");
+        }
+
+        String path = NFT_TRANSFER_URL_PRE + nftTransferReq.getClass_id() + "/" + nftTransferReq.getOwner() + "/" + nftTransferReq.getNft_id();
+
+        Map<String, Object> body = JSON.parseObject(JSON.toJSONString(nftTransferReq), new TypeReference<Map<String, Object>>() {
+        });
+
+        body.remove("class_id");
+        body.remove("owner");
+        body.remove("nft_id");
+
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(path, null, body, currentTime, avataProperties.getApiSecret());
+        // 请求体
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+        headers.setContentType(MediaType.parseMediaType("application/json"));
+
+        HttpEntity<String> httpEntity = new HttpEntity<>(JSONObject.toJSONString(body), headers);
+
+        String url = avataProperties.getAvataUrl() + path;
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<NFTOperateDTO>> typeReference = new ParameterizedTypeReference<Message<NFTOperateDTO>>() {
+            };
+            ResponseEntity<Message<NFTOperateDTO>> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, typeReference);
+
+            Message<NFTOperateDTO> message = responseEntity.getBody();
+
+            NFTOperateDTO nftOperateDTO = message.getData();
+            // 成功
+            log.info("转让NFT成功:" + nftOperateDTO);
+
+            return nftOperateDTO;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("转让NFT失败"+nftTransferReq);
+            e.printStackTrace();
+            throw new RuntimeException("转让NFT失败");
+        }
+    }
+
+    //上架NFT
+    @Override
+    public NFTOperateDTO createNFT(NFTCreateReq nftCreateReq) {
+        if (null == nftCreateReq) {
+            log.info("创建NFT失败,缺少参数:"+ null);
+            throw new RuntimeException("创建NFT失败,缺少参数");
+        }
+        if (null == nftCreateReq.getName()) {
+            log.info("创建NFT失败,缺少参数name:"+nftCreateReq);
+            throw new RuntimeException("创建NFT失败,缺少参数name");
+        }
+        if (null == nftCreateReq.getOwner()) {
+            log.info("创建NFT失败,缺少参数owner:"+nftCreateReq);
+            throw new RuntimeException("创建NFT失败,缺少参数owner");
+        }
+        if (null == nftCreateReq.getOperation_id()) {
+            log.info("创建NFT失败,缺少参数operation_id:"+nftCreateReq);
+            throw new RuntimeException("创建NFT失败,缺少参数operation_id");
+        }
+
+        Map<String, Object> body = JSON.parseObject(JSON.toJSONString(nftCreateReq), new TypeReference<Map<String, Object>>() {
+        });
+
+
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(NFT_CREATE_URL, null, body, currentTime, avataProperties.getApiSecret());
+        // 请求体
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+        headers.setContentType(MediaType.parseMediaType("application/json"));
+
+        HttpEntity<String> httpEntity = new HttpEntity<>(JSONObject.toJSONString(body), headers);
+
+        String url = avataProperties.getAvataUrl() + NFT_CREATE_URL;
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<NFTOperateDTO>> typeReference = new ParameterizedTypeReference<Message<NFTOperateDTO>>() {
+            };
+            ResponseEntity<Message<NFTOperateDTO>> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, typeReference);
+
+            Message<NFTOperateDTO> message = responseEntity.getBody();
+
+            NFTOperateDTO nftOperateDTO = message.getData();
+            // 成功
+            log.info("创建NFT成功:" + nftOperateDTO);
+
+            return nftOperateDTO;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("创建NFT失败"+nftCreateReq);
+            e.printStackTrace();
+            throw new RuntimeException("创建NFT失败");
+        }
+    }
+
+    //查询NFT
+    @Override
+    public SNFTDTO sNFT(NFTTransferReq nftTransferReq) {
+        if (null == nftTransferReq) {
+            log.info("查询NFT失败,缺少参数:"+nftTransferReq);
+            throw new RuntimeException("查询NFT失败,缺少参数");
+        }
+        if (null == nftTransferReq.getClass_id()) {
+            log.info("查询NFT失败,缺少参数class_id:"+nftTransferReq);
+            throw new RuntimeException("查询NFT失败,缺少参数class_id");
+        }
+
+        if (null == nftTransferReq.getNft_id()) {
+            log.info("查询NFT失败,缺少参数nft_id:"+nftTransferReq);
+            throw new RuntimeException("查询NFT失败,缺少参数nft_id");
+        }
+
+
+        String path = NFT_PUBLISH_URL_PRE + nftTransferReq.getClass_id() + "/" +  nftTransferReq.getNft_id();
+
+//        Map<String, Object> body = JSON.parseObject(JSON.toJSONString(nftTransferReq), new TypeReference<Map<String, Object>>() {
+//        });
+//
+//        body.remove("class_id");
+//        body.remove("owner");
+//        body.remove("nft_id");
+        Long currentTime = System.currentTimeMillis();
+        // 验签
+        String signature = AvataUtils.signRequest(path, null, null, currentTime, avataProperties.getApiSecret());
+
+        HttpHeaders headers = getHttpHeader(signature, currentTime);
+
+        try {
+            // 接口调用
+            ParameterizedTypeReference<Message<SNFTDTO>> typeReference = new ParameterizedTypeReference<Message<SNFTDTO>>() {
+            };
+            ResponseEntity<Message<SNFTDTO>> responseEntity = restTemplate.exchange(avataProperties.getAvataUrl() + path, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
+
+            Message<SNFTDTO> message = responseEntity.getBody();
+            SNFTDTO snftdto = message.getData();
+            log.info("查询NFT结果:" + snftdto);
+            return snftdto;
+        } catch (Exception e) {
+            // 非200都会抛异常
+            // 处理异常
+            log.info("查询NFT失败"+nftTransferReq);
+            e.printStackTrace();
+            throw new RuntimeException("查询NFT失败");
+        }
+    }
+
+    HttpHeaders getHttpHeader(String signature, Long timestamp) {
+        HttpHeaders headers = new HttpHeaders();
+        headers.set("X-Api-Key", avataProperties.getApiKey());
+        headers.set("X-Timestamp", timestamp.toString());
+        headers.set("X-Signature", signature);
+
+        return headers;
+    }
+}

+ 24 - 0
src/main/java/com/example/demo/util/AvataProperties.java

@@ -0,0 +1,24 @@
+
+package com.example.demo.util;
+
+import lombok.Data;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 文昌链配置信息
+ *
+ * @author
+ */
+@Data
+@Configuration
+//方便nacos配置可以开放下面,也可以在配置文件中配置,
+//@ConfigurationProperties(prefix = "avata")
+public class AvataProperties {
+
+	private String avataUrl="https://apis.avata.bianjie.ai";
+
+	private String apiKey="M2n2W0j7i1A5d0k2J5D1S236P9n3h2g";
+
+	private String apiSecret="E2H2E0o7M1F5A0R235r1F2K6w9K3m25";
+
+}

+ 83 - 0
src/main/java/com/example/demo/util/AvataUtils.java

@@ -0,0 +1,83 @@
+package com.example.demo.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AvataUtils {
+
+    /**
+     * 对请求参数进行签名处理
+     *
+     * @param path      请求路径,仅截取域名后及 Query 参数前部分,例:"/v1beta1/accounts";
+     * @param query     Query 参数,例:"key1=value1&key2=value2",需转为 Map 格式
+     * @param body      Body 参数,例:"{\"count\": 1, \"operation_id\": \"random_string\"}",需转为 Map 格式
+     * @param timestamp 当前时间戳(毫秒),例:1647751123703
+     * @param apiSecret 应用方的 API Secret,例:"AKIDz8krbsJ5yKBZQpn74WFkmLPc5ab"
+     * @return 返回签名结果
+     */
+    public static String signRequest(String path, Map<String, Object> query, Map<String, Object> body, long timestamp, String apiSecret) {
+        Map<String, Object> paramsMap = new HashMap();
+
+        paramsMap.put("path_url", path);
+
+        if (query != null && !query.isEmpty()) {
+            query.forEach((key, value) -> paramsMap.put("query_" + key, value));
+        }
+
+        if (body != null && !body.isEmpty()) {
+            body.forEach((key, value) -> paramsMap.put("body_" + key, value));
+        }
+
+        // 重要提示:下载相应的依赖,请使用上方Java代码前的版本号
+
+        // 将请求参数序列化为排序后的 JSON 字符串
+        String jsonStr = JSON.toJSONString(paramsMap, SerializerFeature.MapSortField);
+
+        // 执行签名
+        String signature = sha256Sum(jsonStr + String.valueOf(timestamp) + apiSecret);
+
+        return signature;
+    }
+
+    /**
+     * SHA256 摘要
+     *
+     * @param str
+     * @return
+     */
+    private static String sha256Sum(String str) {
+        MessageDigest digest = null;
+        try {
+            digest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            // Should not happen
+            e.printStackTrace();
+        }
+        byte[] encodedHash = digest.digest(str.getBytes(StandardCharsets.UTF_8));
+        return bytesToHex(encodedHash);
+    }
+
+    /**
+     * 将 bytes 转为 Hex
+     *
+     * @param hash
+     * @return
+     */
+    private static String bytesToHex(byte[] hash) {
+        StringBuilder hexString = new StringBuilder(2 * hash.length);
+        for (int i = 0; i < hash.length; i++) {
+            String hex = Integer.toHexString(0xff & hash[i]);
+            if (hex.length() == 1) {
+                hexString.append('0');
+            }
+            hexString.append(hex);
+        }
+        return hexString.toString();
+    }
+}

+ 62 - 0
src/main/java/com/example/demo/util/UUIDUtils.java

@@ -0,0 +1,62 @@
+package com.example.demo.util;
+
+import java.util.UUID;
+
+public class UUIDUtils {
+public static String[] chars = new String[] {
+		"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
+
+		"v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
+
+		"G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
+
+		};
+
+	/**
+	 * 獲取8位UUID
+	 * @return
+	 */
+	public static String generateShortUuid() {
+		return getUUID(8);
+	}
+	/**
+	 * 獲取16位UUID
+	 * @return
+	 */
+	public static String generateLongUuid() {
+		return getUUID(16);
+	}
+
+	/**
+	 *32位默认长度的uuid
+	 * (获取32位uuid)
+	 *
+	 * @return
+	 */
+	public static  String getUUID()
+	{
+		return UUID.randomUUID().toString().replace("-", "");
+	}
+
+	/**
+	 *
+	 * (获取指定长度uuid)
+	 *
+	 * @return
+	 */
+	public static  String getUUID(int len)
+	{
+		if(0 >= len)
+		{
+			return null;
+		}
+
+		String uuid = getUUID();
+		StringBuffer str = new StringBuffer();
+		for (int i = 0; i < len; i++)
+		{
+			str.append(uuid.charAt(i));
+		}
+		return str.toString();
+	}
+}

+ 15 - 0
src/main/resources/application.yml

@@ -0,0 +1,15 @@
+
+avata:
+  url: https://apis.avata.bianjie.ai/
+  apiKey: M2n2W0j7i1A5d0k2J5D1S236P9n3h2g
+  apiSecret: E2H2E0o7M1F5A0R235r1F2K6w9K3m25
+
+# http请求参数设置
+http:
+  maxTotal: 200
+  maxPerRouter: 100
+  connectionTimeout: 3000
+  connectionRequestTimeout: 2000
+  readTime: 15000
+server:
+  port: 8083

+ 71 - 0
src/main/resources/logback.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+    <property name="log.path" value="logs" />
+    <!-- 日志输出格式 -->
+    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 系统模块日志级别控制  -->
+    <logger name="com.easy" level="info" />
+    <!-- Spring日志级别控制  -->
+    <logger name="org.springframework" level="warn" />
+
+    <root level="info">
+        <appender-ref ref="console" />
+    </root>
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+</configuration>

+ 97 - 0
src/test/java/com/example/demo/AvataProxyTest.java

@@ -0,0 +1,97 @@
+package com.example.demo;
+
+import com.example.demo.model.dto.CreateAccountDTO;
+import com.example.demo.model.dto.DealResultDTO;
+import com.example.demo.model.dto.NFTClassListDTO;
+import com.example.demo.model.dto.NFTOperateDTO;
+import com.example.demo.model.req.NFTClassListReq;
+import com.example.demo.model.req.NFTCreateReq;
+import com.example.demo.model.req.NFTPublishReq;
+import com.example.demo.model.req.NFTTransferReq;
+import com.example.demo.proxy.AvataProxy;
+import com.example.demo.util.UUIDUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+
+/**
+ * @author enlai.wang 16621134798
+ * @date: 2022/5/26
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public class AvataProxyTest {
+    @Resource
+    private AvataProxy avataProxy;
+
+    @Test
+    public void testCreateAvataAccountFail() {
+        System.out.println("创建账户错误示例");
+        CreateAccountDTO createAccountDTO = avataProxy.createAccount("测试", "test1653568056132");
+    }
+    @Test
+    public void testCreateAvataAccountSuccess() {
+        System.out.println("创建账户正确示例");
+        CreateAccountDTO createAccountDTO = avataProxy.createAccount("测试", "test" + System.currentTimeMillis());
+    }
+
+    @Test
+    public void testQueryAvataAccountSuccess() {
+        System.out.println("创建账户正确示例");
+        CreateAccountDTO createAccountDTO = avataProxy.createAccount("测试", "test" + System.currentTimeMillis());
+    }
+    @Test
+    public void testQueryNFTClassList() {
+        System.out.println("测试NFT类别查询");
+        // 请求参数
+        NFTClassListReq nftClassListReq = new NFTClassListReq();
+        nftClassListReq.setName("test123456");
+        nftClassListReq.setLimit("10");
+        NFTClassListDTO nftClassListDTO = avataProxy.queryNFTClassList(nftClassListReq);
+    }
+
+    @Test
+    public void testPublishNFT() {
+        System.out.println("测试NFT发行");
+
+        NFTPublishReq nftPublishReq = new NFTPublishReq();
+        nftPublishReq.setClass_id("");
+        nftPublishReq.setName("test123456");
+        nftPublishReq.setOperation_id("test123456");
+        nftPublishReq.setRecipient("");
+        NFTOperateDTO nftPublishDTO = avataProxy.publishNFT(nftPublishReq);
+    }
+
+    @Test
+    public void testQueryDealResult() {
+        System.out.println("测试上链交易结果查询");
+//        DealResultDTO dealResultDTO = avataProxy.queryDealResult("");
+        DealResultDTO dealResultDTO2 = avataProxy.queryDealResult("");
+
+    }
+
+    @Test
+    public void testTransferNFT() {
+        System.out.println("测试NFT转让");
+
+        NFTTransferReq nftTransferReq = new NFTTransferReq();
+        nftTransferReq.setClass_id("");
+        nftTransferReq.setNft_id("");
+        nftTransferReq.setOwner("");
+        nftTransferReq.setOperation_id("test123456");
+        nftTransferReq.setRecipient("");
+        NFTOperateDTO nftPublishDTO = avataProxy.transferNFT(nftTransferReq);
+    }
+
+    @Test
+    public void testCreateNFT() {
+        NFTCreateReq nftCreateReq = new NFTCreateReq();
+        nftCreateReq.setName("");
+        nftCreateReq.setOwner("");
+        nftCreateReq.setOperation_id("test123456");
+        avataProxy.createNFT(nftCreateReq);
+    }
+}