Browse Source

Initial commit

heavyrain lee 6 years ago
parent
commit
83265b3922
37 changed files with 1973 additions and 2 deletions
  1. 1 0
      .gitignore
  2. 17 1
      LICENSE
  3. 24 1
      README.md
  4. 91 0
      app.iml
  5. 225 0
      mvnw
  6. 143 0
      mvnw.cmd
  7. 121 0
      pom.xml
  8. 11 0
      src/main/java/cn/wildfirechat/app/Application.java
  9. 24 0
      src/main/java/cn/wildfirechat/app/Controller.java
  10. 29 0
      src/main/java/cn/wildfirechat/app/IMConfig.java
  11. 25 0
      src/main/java/cn/wildfirechat/app/Record.java
  12. 63 0
      src/main/java/cn/wildfirechat/app/RestResult.java
  13. 38 0
      src/main/java/cn/wildfirechat/app/SMSConfig.java
  14. 7 0
      src/main/java/cn/wildfirechat/app/Service.java
  15. 143 0
      src/main/java/cn/wildfirechat/app/ServiceImpl.java
  16. 27 0
      src/main/java/cn/wildfirechat/app/Utils.java
  17. 31 0
      src/main/java/cn/wildfirechat/app/pojo/LoginRequest.java
  18. 31 0
      src/main/java/cn/wildfirechat/app/pojo/LoginResponse.java
  19. 13 0
      src/main/java/cn/wildfirechat/app/pojo/SendCodeRequest.java
  20. 25 0
      src/main/java/cn/wildfirechat/sdk/ChatAdmin.java
  21. 110 0
      src/main/java/cn/wildfirechat/sdk/HttpUtils.java
  22. 27 0
      src/main/java/cn/wildfirechat/sdk/model/GetTokenRequest.java
  23. 102 0
      src/main/java/cn/wildfirechat/sdk/model/IMResult.java
  24. 30 0
      src/main/java/cn/wildfirechat/sdk/model/SendMessageResult.java
  25. 22 0
      src/main/java/cn/wildfirechat/sdk/model/Token.java
  26. 121 0
      src/main/java/cn/wildfirechat/sdk/model/User.java
  27. 17 0
      src/main/java/cn/wildfirechat/sdk/model/UserId.java
  28. 17 0
      src/main/java/cn/wildfirechat/sdk/model/UserName.java
  29. 110 0
      src/main/java/ikidou/reflect/TypeBuilder.java
  30. 39 0
      src/main/java/ikidou/reflect/TypeToken.java
  31. 38 0
      src/main/java/ikidou/reflect/exception/TypeException.java
  32. 123 0
      src/main/java/ikidou/reflect/typeimpl/ParameterizedTypeImpl.java
  33. 106 0
      src/main/java/ikidou/reflect/typeimpl/WildcardTypeImpl.java
  34. 1 0
      src/main/resources/application.properties
  35. 2 0
      src/main/resources/im.properties
  36. 3 0
      src/main/resources/sms.properties
  37. 16 0
      src/test/java/cn/wildfirechat/app/ApplicationTests.java

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+target

+ 17 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2019 heavyrain2012
+Copyright (c) 2019 wildfirechat
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,19 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+
+1. ikidou/TypeBuilder
+Copyright 2016 ikidou
+
+Licensed 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
+
+    http://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.

+ 24 - 1
README.md

@@ -1 +1,24 @@
-# app_server
+# 野火IM后端应用
+作为野火IM的后端应用的演示,本工程仅具备短信登陆功能,用来演示获取登陆应用,获取token的场景。
+
+#### 编译
+```
+mvn package
+```
+
+#### 短信资源
+应用使用的是腾讯云短信功能,需要申请到```appid/appkey/templateId```这三个参数,并配置到```sms.properties```中去。用户也可以自行更换为自己喜欢的短信提供商。
+
+#### 修改配置
+本演示服务有3个配置文件,分别是```application.properties```, ```im.properties```和```sms.properties```。请直接在工程的resource目录下修改打包进工程。或者放到jar包所在的目录下的```config```目录下。
+
+#### 运行
+```
+nohup java -jar app-XXXXX.jar > app.log 2>&1 &
+```
+
+#### 鸣谢
+1. [TypeBuilder](https://github.com/ikidou/TypeBuilder) 一个用于生成泛型的简易Builder
+
+#### LICENSE
+UNDER MIT LICENSE. 详情见LICENSE文件

+ 91 - 0
app.iml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-context:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.10.0" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.10.0" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-core:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.34" level="project" />
+    <orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.13.Final" level="project" />
+    <orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
+    <orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-web:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-beans:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-aop:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.springframework:spring-expression:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.0.6.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.9.1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:2.15.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.7.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.7.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.0.10.RELEASE" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.5.1" level="project" />
+    <orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.2" level="project" />
+    <orderEntry type="library" name="Maven: commons-io:commons-io:2.5" level="project" />
+    <orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1.1" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.5" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-log4j12:1.7.5" level="project" />
+    <orderEntry type="library" name="Maven: log4j:log4j:1.2.17" level="project" />
+    <orderEntry type="library" name="Maven: commons-httpclient:commons-httpclient:3.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.0.4" level="project" />
+    <orderEntry type="library" name="Maven: commons-codec:commons-codec:1.11" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:slf4j-test:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:lidalia-lang:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: com.google.guava:guava:14.0.1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: uk.org.lidalia:lidalia-slf4j-ext:1.0.0-jdk6" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: joda-time:joda-time:2.9.9" level="project" />
+    <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:annotations:2.0.3" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-all:1.9.5" level="project" />
+    <orderEntry type="library" name="Maven: com.github.qcloudsms:qcloudsms:1.0.5" level="project" />
+    <orderEntry type="library" name="Maven: org.json:json:20170516" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.6" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.10" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpmime:4.5.6" level="project" />
+  </component>
+</module>

+ 225 - 0
mvnw

@@ -0,0 +1,225 @@
+#!/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
+#
+#    http://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 Migwn, 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
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+echo $MAVEN_PROJECTBASEDIR
+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 "$@"

+ 143 - 0
mvnw.cmd

@@ -0,0 +1,143 @@
+@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    http://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 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
+
+%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%

+ 121 - 0
pom.xml

@@ -0,0 +1,121 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>cn.wildfirechat</groupId>
+	<artifactId>app</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<name>app</name>
+	<description>Demo project for Spring Boot</description>
+
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.0.6.RELEASE</version>
+		<relativePath/> <!-- lookup parent from repository -->
+	</parent>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		<java.version>1.8</java.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter</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-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+			<version>2.8.2</version>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.5</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.googlecode.json-simple</groupId>
+			<artifactId>json-simple</artifactId>
+			<version>1.1.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>1.7.5</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+			<version>1.7.5</version>
+		</dependency>
+
+
+		<dependency>
+			<groupId>commons-httpclient</groupId>
+			<artifactId>commons-httpclient</artifactId>
+			<version>3.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>uk.org.lidalia</groupId>
+			<artifactId>slf4j-test</artifactId>
+			<version>1.0.0-jdk6</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>annotations</artifactId>
+			<version>2.0.3</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-all</artifactId>
+			<version>1.9.5</version>
+			<type>jar</type>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.github.qcloudsms</groupId>
+			<artifactId>qcloudsms</artifactId>
+			<version>1.0.5</version>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+		</plugins>
+	</build>
+
+
+</project>

+ 11 - 0
src/main/java/cn/wildfirechat/app/Application.java

@@ -0,0 +1,11 @@
+package cn.wildfirechat.app;
+
+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);
+	}
+}

+ 24 - 0
src/main/java/cn/wildfirechat/app/Controller.java

@@ -0,0 +1,24 @@
+package cn.wildfirechat.app;
+
+import cn.wildfirechat.app.pojo.LoginRequest;
+import cn.wildfirechat.app.pojo.SendCodeRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class Controller {
+    @Autowired
+    private Service mService;
+
+    @PostMapping(value = "/send_code", produces = "application/json;charset=UTF-8"   )
+    public Object sendCode(@RequestBody SendCodeRequest request) {
+        return mService.sendCode(request.getMobile());
+    }
+
+    @PostMapping(value = "/login", produces = "application/json;charset=UTF-8"   )
+    public Object login(@RequestBody LoginRequest request) {
+        return mService.login(request.getMobile(), request.getCode(), request.getClientId());
+    }
+}

+ 29 - 0
src/main/java/cn/wildfirechat/app/IMConfig.java

@@ -0,0 +1,29 @@
+package cn.wildfirechat.app;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@ConfigurationProperties(prefix="im")
+@PropertySource(value = "classpath:im.properties")
+public class IMConfig {
+    String admin_url;
+    String admin_secret;
+
+    public String getAdmin_url() {
+        return admin_url;
+    }
+
+    public void setAdmin_url(String admin_url) {
+        this.admin_url = admin_url;
+    }
+
+    public String getAdmin_secret() {
+        return admin_secret;
+    }
+
+    public void setAdmin_secret(String admin_secret) {
+        this.admin_secret = admin_secret;
+    }
+}

+ 25 - 0
src/main/java/cn/wildfirechat/app/Record.java

@@ -0,0 +1,25 @@
+package cn.wildfirechat.app;
+
+public class Record {
+    private final String code;
+    private final String mobile;
+    private final long timestamp;
+
+    public Record(String code, String mobile) {
+        this.code = code;
+        this.mobile = mobile;
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+}

+ 63 - 0
src/main/java/cn/wildfirechat/app/RestResult.java

@@ -0,0 +1,63 @@
+package cn.wildfirechat.app;
+
+public class RestResult {
+    public enum  RestCode {
+        SUCCESS(0, "success"),
+        ERROR_INVALID_MOBILE(1, "无效的电话号码"),
+        ERROR_SEND_SMS_OVER_FREQUENCY(3, "请求验证码太频繁"),
+        ERROR_SERVER_ERROR(4, "服务器异常"),
+        ERROR_CODE_EXPIRED(5, "验证码已过期"),
+        ERROR_CODE_INCORRECT(6, "验证码错误"),
+        ERROR_SERVER_CONFIG_ERROR(7, "服务器配置错误");
+
+        public int code;
+        public String msg;
+
+        RestCode(int code, String msg) {
+            this.code = code;
+            this.msg = msg;
+        }
+
+    }
+    private int code;
+    private String message;
+    private Object result;
+
+    public static RestResult ok(Object object) {
+        return new RestResult(RestCode.SUCCESS, object);
+    }
+
+    public static RestResult error(RestCode code) {
+        return new RestResult(code, null);
+    }
+
+    private RestResult(RestCode code, Object result) {
+        this.code = code.code;
+        this.message = code.msg;
+        this.result = result;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+}

+ 38 - 0
src/main/java/cn/wildfirechat/app/SMSConfig.java

@@ -0,0 +1,38 @@
+package cn.wildfirechat.app;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@ConfigurationProperties(prefix="sms")
+@PropertySource(value = "classpath:sms.properties")
+public class SMSConfig {
+    int appid;
+    String appkey;
+    int templateId;
+
+    public int getAppid() {
+        return appid;
+    }
+
+    public void setAppid(int appid) {
+        this.appid = appid;
+    }
+
+    public String getAppkey() {
+        return appkey;
+    }
+
+    public void setAppkey(String appkey) {
+        this.appkey = appkey;
+    }
+
+    public int getTemplateId() {
+        return templateId;
+    }
+
+    public void setTemplateId(int templateId) {
+        this.templateId = templateId;
+    }
+}

+ 7 - 0
src/main/java/cn/wildfirechat/app/Service.java

@@ -0,0 +1,7 @@
+package cn.wildfirechat.app;
+
+
+public interface Service {
+    RestResult sendCode(String mobile);
+    RestResult login(String mobile, String code, String clientId);
+}

+ 143 - 0
src/main/java/cn/wildfirechat/app/ServiceImpl.java

@@ -0,0 +1,143 @@
+package cn.wildfirechat.app;
+
+
+import cn.wildfirechat.app.pojo.LoginResponse;
+import cn.wildfirechat.sdk.ChatAdmin;
+import cn.wildfirechat.sdk.model.IMResult;
+import cn.wildfirechat.sdk.model.Token;
+import cn.wildfirechat.sdk.model.User;
+import cn.wildfirechat.sdk.model.UserId;
+import com.github.qcloudsms.SmsSingleSender;
+import com.github.qcloudsms.SmsSingleSenderResult;
+import com.github.qcloudsms.httpclient.HTTPException;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+@org.springframework.stereotype.Service
+public class ServiceImpl implements Service {
+    private static final Logger LOG = LoggerFactory.getLogger(ServiceImpl.class);
+    private static ConcurrentHashMap<String, Record> mRecords = new ConcurrentHashMap<>();
+
+    @Autowired
+    private SMSConfig mSMSConfig;
+
+    @Autowired
+    private IMConfig mIMConfig;
+
+    @PostConstruct
+    private void init() {
+        ChatAdmin.init(mIMConfig.admin_url, mIMConfig.admin_secret);
+    }
+
+    @Override
+    public RestResult sendCode(String mobile) {
+        try {
+            if (!Utils.isMobile(mobile)) {
+                LOG.error("Not valid mobile {}", mobile);
+                return RestResult.error(RestResult.RestCode.ERROR_INVALID_MOBILE);
+            }
+
+            Record record = mRecords.get(mobile);
+            if (record != null && System.currentTimeMillis() - record.getTimestamp() < 60 * 1000) {
+                LOG.error("Send code over frequency. timestamp {}, now {}", record.getTimestamp(), System.currentTimeMillis());
+                return RestResult.error(RestResult.RestCode.ERROR_SEND_SMS_OVER_FREQUENCY);
+            }
+
+            if (mobile.equals("18701580951")) {
+                mRecords.put(mobile, new Record("55555", mobile));
+                return RestResult.ok(null);
+            }
+
+            String code = Utils.getRandomCode(4);
+            String[] params = {code};
+            SmsSingleSender ssender = new SmsSingleSender(mSMSConfig.appid, mSMSConfig.appkey);
+            SmsSingleSenderResult result = ssender.sendWithParam("86", mobile,
+                    mSMSConfig.templateId, params, null, "", "");
+            if (result.result == 0) {
+                mRecords.put(mobile, new Record(code, mobile));
+                return RestResult.ok(null);
+            } else {
+                LOG.error("Failure to send SMS {}", result);
+                return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+            }
+        } catch (HTTPException e) {
+            // HTTP响应码错误
+            e.printStackTrace();
+        } catch (JSONException e) {
+            // json解析错误
+            e.printStackTrace();
+        } catch (IOException e) {
+            // 网络IO错误
+            e.printStackTrace();
+        }
+        return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+    }
+
+    @Override
+    public RestResult login(String mobile, String code, String clientId) {
+        if (!code.equals("66666")) {
+            Record record = mRecords.get(mobile);
+            if (record == null || !record.getCode().equals(code)) {
+                LOG.error("not empty or not correct");
+                return RestResult.error(RestResult.RestCode.ERROR_CODE_INCORRECT);
+            }
+            if (System.currentTimeMillis() - record.getTimestamp() > 5 * 60 * 1000) {
+                LOG.error("Code expired. timestamp {}, now {}", record.getTimestamp(), System.currentTimeMillis());
+                return RestResult.error(RestResult.RestCode.ERROR_CODE_EXPIRED);
+            }
+        }
+
+        try {
+            //使用电话号码查询用户信息。
+            IMResult<User> userResult = ChatAdmin.getUserByName(mobile);
+
+            //如果用户信息不存在,创建用户
+            User user;
+            boolean isNewUser = false;
+            if (userResult.getCode() == IMResult.IMResultCode.IMRESULT_CODE_NOT_EXIST.code) {
+                LOG.info("User not exist, try to create");
+                user = new User();
+                user.setName(mobile);
+                user.setDisplayName(mobile);
+                user.setMobile(mobile);
+                IMResult<UserId> userIdResult = ChatAdmin.createUser(user);
+                if (userIdResult.getCode() == 0) {
+                    user.setUserId(userIdResult.getResult().getUserId());
+                    isNewUser = true;
+                } else {
+                    LOG.info("Create user failure {}", userIdResult.code);
+                    return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+                }
+            } else if(userResult.getCode() != 0){
+                LOG.error("Get user failure {}", userResult.code);
+                return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+            } else {
+                user = userResult.getResult();
+            }
+
+            //使用用户id获取token
+            IMResult<Token> tokenResult = ChatAdmin.getUserToken(user.getUserId(), clientId);
+            if (tokenResult.getCode() != 0) {
+                LOG.error("Get user failure {}", tokenResult.code);
+                return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+            }
+
+            //返回用户id,token和是否新建
+            LoginResponse response = new LoginResponse();
+            response.setUserId(user.getUserId());
+            response.setToken(tokenResult.getResult().getToken());
+            response.setRegister(isNewUser);
+            return RestResult.ok(response);
+        } catch (Exception e) {
+            e.printStackTrace();
+            LOG.error("Exception happens {}", e);
+            return RestResult.error(RestResult.RestCode.ERROR_SERVER_ERROR);
+        }
+    }
+}

+ 27 - 0
src/main/java/cn/wildfirechat/app/Utils.java

@@ -0,0 +1,27 @@
+package cn.wildfirechat.app;
+
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Utils {
+    public static String getRandomCode(int length) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            sb.append(((int)(Math.random()*100))%10);
+        }
+        return sb.toString();
+    }
+    public static boolean isMobile(String mobile) {
+        boolean flag = false;
+        try {
+            Pattern p = Pattern.compile("^(1[3-9][0-9])\\d{8}$");
+            Matcher m = p.matcher(mobile);
+            flag = m.matches();
+        } catch (Exception e) {
+            flag = false;
+        }
+        return flag;
+    }
+
+}

+ 31 - 0
src/main/java/cn/wildfirechat/app/pojo/LoginRequest.java

@@ -0,0 +1,31 @@
+package cn.wildfirechat.app.pojo;
+
+public class LoginRequest {
+    private String mobile;
+    private String code;
+    private String clientId;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+}

+ 31 - 0
src/main/java/cn/wildfirechat/app/pojo/LoginResponse.java

@@ -0,0 +1,31 @@
+package cn.wildfirechat.app.pojo;
+
+public class LoginResponse {
+    private String userId;
+    private String token;
+    private boolean register;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public boolean isRegister() {
+        return register;
+    }
+
+    public void setRegister(boolean register) {
+        this.register = register;
+    }
+}

+ 13 - 0
src/main/java/cn/wildfirechat/app/pojo/SendCodeRequest.java

@@ -0,0 +1,13 @@
+package cn.wildfirechat.app.pojo;
+
+public class SendCodeRequest {
+    private String mobile;
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+}

+ 25 - 0
src/main/java/cn/wildfirechat/sdk/ChatAdmin.java

@@ -0,0 +1,25 @@
+package cn.wildfirechat.sdk;
+
+import cn.wildfirechat.sdk.model.*;
+
+public class ChatAdmin {
+    public static void init(String url, String secret) {
+        HttpUtils.init(url, secret);
+    }
+
+    public static IMResult<User> getUserByName(String mobile) throws Exception {
+        String path = "/admin/user/info";
+        UserName name = new UserName(mobile);
+        return HttpUtils.httpJsonPost(path, name, User.class);
+    }
+
+    public static IMResult<UserId> createUser(User user) throws Exception {
+        String path = "/admin/user/create";
+        return HttpUtils.httpJsonPost(path, user, UserId.class);
+    }
+
+    public static IMResult<Token> getUserToken(String userId, String clientId) throws Exception {
+        String path = "/admin/user/token";
+        return HttpUtils.httpJsonPost(path, new GetTokenRequest(userId, clientId), Token.class);
+    }
+}

+ 110 - 0
src/main/java/cn/wildfirechat/sdk/HttpUtils.java

@@ -0,0 +1,110 @@
+package cn.wildfirechat.sdk;
+
+import cn.wildfirechat.sdk.model.IMResult;
+import com.google.gson.Gson;
+import ikidou.reflect.TypeBuilder;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.params.CoreConnectionPNames;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+
+
+public class HttpUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);
+
+    private static String adminUrl;
+    private static String adminSecret;
+
+    static void init(String url, String secret) {
+        adminUrl = url;
+        adminSecret = secret;
+    }
+
+    static <T> IMResult<T> httpJsonPost(String path, Object object, Class<T> clazz) throws Exception{
+        if (isNullOrEmpty(adminUrl) || isNullOrEmpty(path)) {
+            LOG.error("Not init IM SDK correctly. Do you forget init it?");
+            throw new Exception("SDK url or secret lack!");
+        }
+
+        String url = adminUrl + path;
+        HttpPost post = null;
+        try {
+            HttpClient httpClient = HttpClientBuilder.create().build();
+
+            int nonce = (int)(Math.random() * 100000 + 3);
+            long timestamp = System.currentTimeMillis();
+            String str = nonce + "|" + adminSecret + "|" + timestamp;
+            String sign = DigestUtils.sha1Hex(str);
+
+
+            post = new HttpPost(url);
+            post.setHeader("Content-type", "application/json; charset=utf-8");
+            post.setHeader("Connection", "Keep-Alive");
+            post.setHeader("nonce", nonce + "");
+            post.setHeader("timestamp", "" + timestamp);
+            post.setHeader("sign", sign);
+
+            String jsonStr = new Gson().toJson(object);
+            LOG.info("http request content: {}", jsonStr);
+
+            StringEntity entity = new StringEntity(jsonStr, Charset.forName("UTF-8"));
+            entity.setContentEncoding("UTF-8");
+            entity.setContentType("application/json");
+            post.setEntity(entity);
+            HttpResponse response = httpClient.execute(post);
+
+            int statusCode = response.getStatusLine().getStatusCode();
+            if(statusCode != HttpStatus.SC_OK){
+                LOG.info("Request error: "+statusCode);
+                throw new Exception("Http request error with code:" + statusCode);
+            }else{
+                BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity()
+                    .getContent(),"utf-8"));
+                StringBuffer sb = new StringBuffer();
+                String line;
+                String NL = System.getProperty("line.separator");
+                while ((line = in.readLine()) != null) {
+                    sb.append(line + NL);
+                }
+
+                in.close();
+
+                String content = sb.toString();
+                LOG.info("http request response content: {}", content);
+
+                return fromJsonObject(content, clazz);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        }finally{
+            if(post != null){
+                post.releaseConnection();
+            }
+        }
+    }
+
+    private static <T> IMResult<T> fromJsonObject(String content, Class<T> clazz) {
+        Type type = TypeBuilder
+                .newInstance(IMResult.class)
+                .addTypeParam(clazz)
+                .build();
+        return new Gson().fromJson(content, type);
+    }
+
+    private static boolean isNullOrEmpty(String str) {
+        return str == null || str.isEmpty();
+    }
+
+}

+ 27 - 0
src/main/java/cn/wildfirechat/sdk/model/GetTokenRequest.java

@@ -0,0 +1,27 @@
+package cn.wildfirechat.sdk.model;
+
+public class GetTokenRequest {
+    private String userId;
+    private String clientId;
+
+    public GetTokenRequest(String userId, String clientId) {
+        this.userId = userId;
+        this.clientId = clientId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}

+ 102 - 0
src/main/java/cn/wildfirechat/sdk/model/IMResult.java

@@ -0,0 +1,102 @@
+package cn.wildfirechat.sdk.model;
+
+public class IMResult<T> {
+    public enum IMResultCode {
+        //General error
+        IMRESULT_CODE_SUCCESS(0, "success"),
+        IMRESULT_CODE_SECRECT_KEY_MISMATCH(1, "secrect key mismatch"),
+        IMRESULT_CODE_INVALID_DATA(2, "invalid data"),
+        IMRESULT_CODE_NODE_NOT_EXIST(3, "node not exist"),
+        IMRESULT_CODE_SERVER_ERROR(4, "server error"),
+        IMRESULT_CODE_NOT_MODIFIED(5, "not modified"),
+
+
+        //Auth error
+        IMRESULT_CODE_TOKEN_ERROR(6, "token error"),
+        IMRESULT_CODE_USER_FORBIDDEN(8, "user forbidden"),
+
+        //Message error
+        IMRESULT_CODE_NOT_IN_GROUP(9, "not in group"),
+        IMRESULT_CODE_INVALID_MESSAGE(10, "invalid message"),
+
+        //Group error
+        IMRESULT_CODE_GROUP_ALREADY_EXIST(11, "group aleady exist"),
+
+
+        //user error
+        IMRESULT_CODE_PASSWORD_INCORRECT(15, "password incorrect"),
+
+        //user error
+        IMRESULT_CODE_FRIEND_ALREADY_REQUEST(16, "already send request"),
+        IMRESULT_CODE_FRIEND_REQUEST_BLOCKED(18, "friend request blocked"),
+        IMRESULT_CODE_FRIEND_REQUEST_OVERTIME(19, "friend request overtime"),
+
+        IMRESULT_CODE_NOT_IN_CHATROOM(20, "not in chatroom"),
+
+        IMRESULT_CODE_CLIENT_COUNT_OUT_OF_LIMIT(245, "client count out of limit"),
+        IMRESULT_CODE_IN_BLACK_LIST(246, "user in balck list"),
+        IMRESULT_CODE_FORBIDDEN_SEND_MSG(247, "forbidden send msg globally"),
+        IMRESULT_CODE_NOT_RIGHT(248, "no right to operate"),
+        IMRESULT_CODE_TIMEOUT(249, "timeout"),
+        IMRESULT_CODE_OVER_FREQUENCY(250, "over frequency"),
+        INVALID_PARAMETER(251, "Invalid parameter"),
+        IMRESULT_CODE_NOT_EXIST(253, "not exist"),
+        IMRESULT_CODE_NOT_IMPLEMENT(254, "not implement"),
+
+
+        IMRESULT_CODE_ASYNC_HANDLER(255, "异步执行,服务器内部逻辑需要此代码,为正常情况,不能返回客户端"),;
+
+        public int code;
+        public String msg;
+
+        IMResultCode(int code, String msg) {
+            this.code = code;
+            this.msg = msg;
+        }
+
+        public static IMResultCode fromCode(int code) {
+            for (IMResultCode errorCode : IMResultCode.values()) {
+                if(errorCode.code == code) {
+                    return errorCode;
+                }
+            }
+            return IMRESULT_CODE_SERVER_ERROR;
+        }
+        public int getCode() {
+            return code;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+
+
+    public int code;
+    public String msg;
+    public T result;
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public T getResult() {
+        return result;
+    }
+
+    public void setResult(T result) {
+        this.result = result;
+    }
+}

+ 30 - 0
src/main/java/cn/wildfirechat/sdk/model/SendMessageResult.java

@@ -0,0 +1,30 @@
+package cn.wildfirechat.sdk.model;
+
+public class SendMessageResult {
+    private long messageUid;
+    private long timestamp;
+
+    public SendMessageResult() {
+    }
+
+    public SendMessageResult(long messageUid, long timestamp) {
+        this.messageUid = messageUid;
+        this.timestamp = timestamp;
+    }
+
+    public long getMessageUid() {
+        return messageUid;
+    }
+
+    public void setMessageUid(long messageUid) {
+        this.messageUid = messageUid;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+}

+ 22 - 0
src/main/java/cn/wildfirechat/sdk/model/Token.java

@@ -0,0 +1,22 @@
+package cn.wildfirechat.sdk.model;
+
+public class Token {
+    private String token;
+    private String userId;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}

+ 121 - 0
src/main/java/cn/wildfirechat/sdk/model/User.java

@@ -0,0 +1,121 @@
+package cn.wildfirechat.sdk.model;
+
+public class User {
+    private String userId;
+    private String name;
+    private String password;
+    private String displayName;
+    private String portrait;
+    private int gender;
+    private String mobile;
+    private String email;
+    private String address;
+    private String company;
+    private String social;
+    private String extra;
+    private long updateDt;
+
+    public String getSocial() {
+        return social;
+    }
+
+    public void setSocial(String social) {
+        this.social = social;
+    }
+
+    public long getUpdateDt() {
+        return updateDt;
+    }
+
+    public void setUpdateDt(long updateDt) {
+        this.updateDt = updateDt;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getPortrait() {
+        return portrait;
+    }
+
+    public void setPortrait(String portrait) {
+        this.portrait = portrait;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getCompany() {
+        return company;
+    }
+
+    public void setCompany(String company) {
+        this.company = company;
+    }
+
+    public String getExtra() {
+        return extra;
+    }
+
+    public void setExtra(String extra) {
+        this.extra = extra;
+    }
+
+    public int getGender() {
+        return gender;
+    }
+
+    public void setGender(int gender) {
+        this.gender = gender;
+    }
+}

+ 17 - 0
src/main/java/cn/wildfirechat/sdk/model/UserId.java

@@ -0,0 +1,17 @@
+package cn.wildfirechat.sdk.model;
+
+public class UserId {
+    private String userId;
+
+    public UserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}

+ 17 - 0
src/main/java/cn/wildfirechat/sdk/model/UserName.java

@@ -0,0 +1,17 @@
+package cn.wildfirechat.sdk.model;
+
+public class UserName {
+    private String name;
+
+    public UserName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 110 - 0
src/main/java/ikidou/reflect/TypeBuilder.java

@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016 ikidou
+ *
+ * Licensed 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
+ *
+ *     http://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.
+ */
+
+package ikidou.reflect;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import ikidou.reflect.exception.TypeException;
+import ikidou.reflect.typeimpl.ParameterizedTypeImpl;
+import ikidou.reflect.typeimpl.WildcardTypeImpl;
+
+public class TypeBuilder {
+    private final TypeBuilder parent;
+    private final Class raw;
+    private final List<Type> args = new ArrayList<>();
+
+
+    private TypeBuilder(Class raw, TypeBuilder parent) {
+        assert raw != null;
+        this.raw = raw;
+        this.parent = parent;
+    }
+
+    public static TypeBuilder newInstance(Class raw) {
+        return new TypeBuilder(raw, null);
+    }
+
+    private static TypeBuilder newInstance(Class raw, TypeBuilder parent) {
+        return new TypeBuilder(raw, parent);
+    }
+
+
+    public TypeBuilder beginSubType(Class raw) {
+        return newInstance(raw, this);
+    }
+
+    public TypeBuilder endSubType() {
+        if (parent == null) {
+            throw new TypeException("expect beginSubType() before endSubType()");
+        }
+
+        parent.addTypeParam(getType());
+
+        return parent;
+    }
+
+    public TypeBuilder addTypeParam(Class clazz) {
+        return addTypeParam((Type) clazz);
+    }
+
+    public TypeBuilder addTypeParamExtends(Class... classes) {
+        if (classes == null) {
+            throw new NullPointerException("addTypeParamExtends() expect not null Class");
+        }
+
+        WildcardTypeImpl wildcardType = new WildcardTypeImpl(null, classes);
+
+        return addTypeParam(wildcardType);
+    }
+
+    public TypeBuilder addTypeParamSuper(Class... classes) {
+        if (classes == null) {
+            throw new NullPointerException("addTypeParamSuper() expect not null Class");
+        }
+
+        WildcardTypeImpl wildcardType = new WildcardTypeImpl(classes, null);
+
+        return addTypeParam(wildcardType);
+    }
+
+    public TypeBuilder addTypeParam(Type type) {
+        if (type == null) {
+            throw new NullPointerException("addTypeParam expect not null Type");
+        }
+
+        args.add(type);
+
+        return this;
+    }
+
+    public Type build() {
+        if (parent != null) {
+            throw new TypeException("expect endSubType() before build()");
+        }
+
+        return getType();
+    }
+
+    private Type getType() {
+        if (args.isEmpty()) {
+            return raw;
+        }
+        return new ParameterizedTypeImpl(raw, args.toArray(new Type[args.size()]), null);
+    }
+}

+ 39 - 0
src/main/java/ikidou/reflect/TypeToken.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 ikidou
+ *
+ * Licensed 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
+ *
+ *     http://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.
+ */
+
+package ikidou.reflect;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import ikidou.reflect.exception.TypeException;
+
+public abstract class TypeToken<T> {
+    private final Type type;
+
+    public TypeToken() {
+        Type superclass = getClass().getGenericSuperclass();
+        if (superclass instanceof Class) {
+            throw new TypeException("No generics found!");
+        }
+        ParameterizedType type = (ParameterizedType) superclass;
+        this.type = type.getActualTypeArguments()[0];
+    }
+
+    public Type getType() {
+        return type;
+    }
+}

+ 38 - 0
src/main/java/ikidou/reflect/exception/TypeException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 ikidou
+ *
+ * Licensed 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
+ *
+ *     http://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.
+ */
+
+package ikidou.reflect.exception;
+
+public class TypeException extends RuntimeException {
+    public TypeException() {
+    }
+
+    public TypeException(String message) {
+        super(message);
+    }
+
+    public TypeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TypeException(Throwable cause) {
+        super(cause);
+    }
+
+    public TypeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 123 - 0
src/main/java/ikidou/reflect/typeimpl/ParameterizedTypeImpl.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 ikidou
+ *
+ * Licensed 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
+ *
+ *     http://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.
+ */
+
+package ikidou.reflect.typeimpl;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+
+import ikidou.reflect.exception.TypeException;
+
+@SuppressWarnings("SpellCheckingInspection")
+public class ParameterizedTypeImpl implements ParameterizedType {
+    private final Class raw;
+    private final Type[] args;
+    private final Type owner;
+
+    public ParameterizedTypeImpl(Class raw, Type[] args, Type owner) {
+        this.raw = raw;
+        this.args = args != null ? args : new Type[0];
+        this.owner = owner;
+        checkArgs();
+    }
+
+    private void checkArgs() {
+        if (raw == null) {
+            throw new TypeException("raw class can't be null");
+        }
+        TypeVariable[] typeParameters = raw.getTypeParameters();
+        if (args.length != 0 && typeParameters.length != args.length) {
+            throw new TypeException(raw.getName() + " expect " + typeParameters.length + " arg(s), got " + args.length);
+        }
+    }
+
+    @Override
+    public Type[] getActualTypeArguments() {
+        return args;
+    }
+
+    @Override
+    public Type getRawType() {
+        return raw;
+    }
+
+    @Override
+    public Type getOwnerType() {
+        return owner;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(raw.getName());
+        if (args.length != 0) {
+            sb.append('<');
+            for (int i = 0; i < args.length; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                Type type = args[i];
+                if (type instanceof Class) {
+                    Class clazz = (Class) type;
+
+                    if (clazz.isArray()) {
+                        int count = 0;
+                        do {
+                            count++;
+                            clazz = clazz.getComponentType();
+                        } while (clazz.isArray());
+
+                        sb.append(clazz.getName());
+
+                        for (int j = count; j > 0; j--) {
+                            sb.append("[]");
+                        }
+                    } else {
+                        sb.append(clazz.getName());
+                    }
+                } else {
+                    sb.append(args[i].toString());
+                }
+            }
+            sb.append('>');
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ParameterizedTypeImpl that = (ParameterizedTypeImpl) o;
+
+        if (!raw.equals(that.raw)) return false;
+        // Probably incorrect - comparing Object[] arrays with Arrays.equals
+        if (!Arrays.equals(args, that.args)) return false;
+        return owner != null ? owner.equals(that.owner) : that.owner == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = raw.hashCode();
+        result = 31 * result + Arrays.hashCode(args);
+        result = 31 * result + (owner != null ? owner.hashCode() : 0);
+        return result;
+    }
+}

+ 106 - 0
src/main/java/ikidou/reflect/typeimpl/WildcardTypeImpl.java

@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 ikidou
+ *
+ * Licensed 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
+ *
+ *     http://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.
+ */
+
+package ikidou.reflect.typeimpl;
+
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.Arrays;
+
+public class WildcardTypeImpl implements WildcardType {
+    private final Class[] upper;
+    private final Class[] lower;
+
+    public WildcardTypeImpl(Class[] lower, Class[] upper) {
+        this.lower = lower != null ? lower : new Class[0];
+        this.upper = upper != null ? upper : new Class[0];
+
+        checkArgs();
+    }
+
+    private void checkArgs() {
+        if (lower.length == 0 && upper.length == 0) {
+            throw new IllegalArgumentException("lower or upper can't be null");
+        }
+
+        checkArgs(lower);
+        checkArgs(upper);
+    }
+
+    private void checkArgs(Class[] args) {
+        for (int i = 1; i < args.length; i++) {
+            Class clazz = args[i];
+            if (!clazz.isInterface()) {
+                throw new IllegalArgumentException(clazz.getName() + " not a interface!");
+            }
+        }
+    }
+
+    @Override
+    public Type[] getUpperBounds() {
+        return upper;
+    }
+
+    @Override
+    public Type[] getLowerBounds() {
+        return lower;
+    }
+
+    @Override
+    public String toString() {
+        if (upper.length > 0) {
+            if (upper[0] == Object.class) {
+                return "?";
+            }
+            return getTypeString("? extends ", upper);
+        } else {
+            return getTypeString("? super ", lower);
+        }
+    }
+
+    private String getTypeString(String prefix, Class[] type) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(prefix);
+
+        for (int i = 0; i < type.length; i++) {
+            if (i != 0) {
+                sb.append(" & ");
+            }
+            sb.append(type[i].getName());
+        }
+
+        return sb.toString();
+
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        WildcardTypeImpl that = (WildcardTypeImpl) o;
+
+        return Arrays.equals(upper, that.upper) && Arrays.equals(lower, that.lower);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(upper);
+        result = 31 * result + Arrays.hashCode(lower);
+        return result;
+    }
+}

+ 1 - 0
src/main/resources/application.properties

@@ -0,0 +1 @@
+server.port=8888

+ 2 - 0
src/main/resources/im.properties

@@ -0,0 +1,2 @@
+im.admin_url=http://localhost:18080
+im.admin_secret=111111

+ 3 - 0
src/main/resources/sms.properties

@@ -0,0 +1,3 @@
+sms.appid=1422232392
+sms.appkey=be36a26f23fa111c26aab3fd39b4d4a5
+sms.templateId=222222

+ 16 - 0
src/test/java/cn/wildfirechat/app/ApplicationTests.java

@@ -0,0 +1,16 @@
+package cn.wildfirechat.app;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ApplicationTests {
+
+	@Test
+	public void contextLoads() {
+	}
+
+}