chengqiang 5 yıl önce
işleme
69dbcfcc40
100 değiştirilmiş dosya ile 13332 ekleme ve 0 silme
  1. 43 0
      .gitignore
  2. 2 0
      README.md
  3. 957 0
      pom.xml
  4. 197 0
      src/main/java/com/easemob/server/example/api/ChatGroupAPI.java
  5. 26 0
      src/main/java/com/easemob/server/example/api/ChatMessageAPI.java
  6. 110 0
      src/main/java/com/easemob/server/example/api/ChatRoomAPI.java
  7. 35 0
      src/main/java/com/easemob/server/example/api/FileAPI.java
  8. 251 0
      src/main/java/com/easemob/server/example/api/IMUserAPI.java
  9. 14 0
      src/main/java/com/easemob/server/example/api/SendMessageAPI.java
  10. 15 0
      src/main/java/com/easemob/server/example/api/impl/EasemobAuthToken.java
  11. 183 0
      src/main/java/com/easemob/server/example/api/impl/EasemobChatGroup.java
  12. 38 0
      src/main/java/com/easemob/server/example/api/impl/EasemobChatMessage.java
  13. 113 0
      src/main/java/com/easemob/server/example/api/impl/EasemobChatRoom.java
  14. 243 0
      src/main/java/com/easemob/server/example/api/impl/EasemobIMUsers.java
  15. 28 0
      src/main/java/com/easemob/server/example/api/impl/EasemobSendMessage.java
  16. 10 0
      src/main/java/com/easemob/server/example/comm/EasemobAPI.java
  17. 30 0
      src/main/java/com/easemob/server/example/comm/OrgInfo.java
  18. 70 0
      src/main/java/com/easemob/server/example/comm/ResponseHandler.java
  19. 75 0
      src/main/java/com/easemob/server/example/comm/TokenUtil.java
  20. 20 0
      src/main/java/com/jeeplus/common/annotation/FieldName.java
  21. 13 0
      src/main/java/com/jeeplus/common/beanvalidator/AddGroup.java
  22. 116 0
      src/main/java/com/jeeplus/common/beanvalidator/BeanValidators.java
  23. 12 0
      src/main/java/com/jeeplus/common/beanvalidator/DefaultGroup.java
  24. 12 0
      src/main/java/com/jeeplus/common/beanvalidator/EditGroup.java
  25. 523 0
      src/main/java/com/jeeplus/common/config/Global.java
  26. 35 0
      src/main/java/com/jeeplus/common/filter/JeeplusMultipartResolver.java
  27. 23 0
      src/main/java/com/jeeplus/common/filter/PageCachingFilter.java
  28. 73 0
      src/main/java/com/jeeplus/common/json/AjaxJson.java
  29. 90 0
      src/main/java/com/jeeplus/common/json/ComNameJson.java
  30. 82 0
      src/main/java/com/jeeplus/common/json/IMAjaxJson.java
  31. 28 0
      src/main/java/com/jeeplus/common/json/PrintJSON.java
  32. 20 0
      src/main/java/com/jeeplus/common/mail/MailAuthenticator.java
  33. 96 0
      src/main/java/com/jeeplus/common/mail/MailBody.java
  34. 154 0
      src/main/java/com/jeeplus/common/mail/MailSendUtils.java
  35. 57 0
      src/main/java/com/jeeplus/common/mapper/BeanMapper.java
  36. 78 0
      src/main/java/com/jeeplus/common/mapper/ConvertBlobTypeHandler.java
  37. 169 0
      src/main/java/com/jeeplus/common/mapper/JaxbMapper.java
  38. 261 0
      src/main/java/com/jeeplus/common/mapper/JsonMapper.java
  39. 66 0
      src/main/java/com/jeeplus/common/mapper/adapters/MapConvertor.java
  40. 48 0
      src/main/java/com/jeeplus/common/oss/GetObjectProgressListener.java
  41. 541 0
      src/main/java/com/jeeplus/common/oss/MultipartUpload.java
  42. 247 0
      src/main/java/com/jeeplus/common/oss/MultipartUploadSample.java
  43. 742 0
      src/main/java/com/jeeplus/common/oss/OSSClientUtil.java
  44. 180 0
      src/main/java/com/jeeplus/common/oss/OssPostObject.java
  45. 159 0
      src/main/java/com/jeeplus/common/oss/OssUploadPart.java
  46. 49 0
      src/main/java/com/jeeplus/common/oss/PutObjectProgressListener.java
  47. 125 0
      src/main/java/com/jeeplus/common/oss/UploadSample.java
  48. 57 0
      src/main/java/com/jeeplus/common/persistence/ActEntity.java
  49. 13 0
      src/main/java/com/jeeplus/common/persistence/BaseDao.java
  50. 186 0
      src/main/java/com/jeeplus/common/persistence/BaseEntity.java
  51. 118 0
      src/main/java/com/jeeplus/common/persistence/CrudDao.java
  52. 127 0
      src/main/java/com/jeeplus/common/persistence/DataEntity.java
  53. 212 0
      src/main/java/com/jeeplus/common/persistence/MapperLoader.java
  54. 623 0
      src/main/java/com/jeeplus/common/persistence/Page.java
  55. 43 0
      src/main/java/com/jeeplus/common/persistence/Parameter.java
  56. 32 0
      src/main/java/com/jeeplus/common/persistence/TreeDao.java
  57. 85 0
      src/main/java/com/jeeplus/common/persistence/TreeEntity.java
  58. 32 0
      src/main/java/com/jeeplus/common/persistence/annotation/MyBatisDao.java
  59. 33 0
      src/main/java/com/jeeplus/common/persistence/dialect/Dialect.java
  60. 89 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/DB2Dialect.java
  61. 44 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/DerbyDialect.java
  62. 45 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/H2Dialect.java
  63. 53 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/MySQLDialect.java
  64. 68 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/OracleDialect.java
  65. 48 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/PostgreSQLDialect.java
  66. 95 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/SQLServer2005Dialect.java
  67. 55 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/SQLServerDialect.java
  68. 47 0
      src/main/java/com/jeeplus/common/persistence/dialect/db/SybaseDialect.java
  69. 110 0
      src/main/java/com/jeeplus/common/persistence/interceptor/BaseInterceptor.java
  70. 128 0
      src/main/java/com/jeeplus/common/persistence/interceptor/PaginationInterceptor.java
  71. 89 0
      src/main/java/com/jeeplus/common/persistence/interceptor/PreparePaginationInterceptor.java
  72. 188 0
      src/main/java/com/jeeplus/common/persistence/interceptor/SQLHelper.java
  73. 37 0
      src/main/java/com/jeeplus/common/persistence/proxy/PageConfiguration.java
  74. 191 0
      src/main/java/com/jeeplus/common/persistence/proxy/PaginationMapperMethod.java
  75. 36 0
      src/main/java/com/jeeplus/common/persistence/proxy/PaginationMapperRegistry.java
  76. 260 0
      src/main/java/com/jeeplus/common/security/Cryptos.java
  77. 148 0
      src/main/java/com/jeeplus/common/security/Digests.java
  78. 40 0
      src/main/java/com/jeeplus/common/security/shiro/HasAnyPermissionsTag.java
  79. 211 0
      src/main/java/com/jeeplus/common/security/shiro/cache/JedisCacheManager.java
  80. 144 0
      src/main/java/com/jeeplus/common/security/shiro/cache/SessionCacheManager.java
  81. 176 0
      src/main/java/com/jeeplus/common/security/shiro/session/CacheSessionDAO.java
  82. 275 0
      src/main/java/com/jeeplus/common/security/shiro/session/JedisSessionDAO.java
  83. 25 0
      src/main/java/com/jeeplus/common/security/shiro/session/SessionDAO.java
  84. 207 0
      src/main/java/com/jeeplus/common/security/shiro/session/SessionManager.java
  85. 517 0
      src/main/java/com/jeeplus/common/service/BaseService.java
  86. 115 0
      src/main/java/com/jeeplus/common/service/CrudService.java
  87. 29 0
      src/main/java/com/jeeplus/common/service/ServiceException.java
  88. 83 0
      src/main/java/com/jeeplus/common/service/TreeService.java
  89. 150 0
      src/main/java/com/jeeplus/common/servlet/ValidateCodeServlet.java
  90. 192 0
      src/main/java/com/jeeplus/common/sms/SMSUtils.java
  91. 128 0
      src/main/java/com/jeeplus/common/tag/AceMenuTag.java
  92. 145 0
      src/main/java/com/jeeplus/common/tag/MenuTag.java
  93. 181 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsBarTag.java
  94. 184 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsLineDoubleNumTag.java
  95. 181 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsLineTag.java
  96. 287 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsLineTimeLineTag.java
  97. 113 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsPieTag.java
  98. 171 0
      src/main/java/com/jeeplus/common/tag/echarts/EChartsRadarTag.java
  99. 27 0
      src/main/java/com/jeeplus/common/test/SpringTransactionalContextTests.java
  100. 0 0
      src/main/java/com/jeeplus/common/test/UUIDTest.java

+ 43 - 0
.gitignore

@@ -0,0 +1,43 @@
+/target/ 
+target
+.idea
+*.iml
+*.ipr
+*.iws
+/.idea/
+.classpath
+.project
+.settings      
+ ##filter databfile、sln file##
+*.mdb  
+*.ldb  
+*.sln    
+##class file##
+*.com  
+*.class  
+*.dll  
+*.exe  
+*.o  
+*.so  
+# compression file
+*.7z  
+*.dmg  
+*.gz  
+*.iso  
+*.jar  
+*.rar  
+*.tar  
+*.zip  
+*.via
+*.tmp
+*.err 
+# OS generated files #  
+.DS_Store  
+.DS_Store?  
+._*  
+.Spotlight-V100  
+.Trashes  
+Icon?  
+ehthumbs.db  
+Thumbs.db  
+.idea/workspace.xml

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+# jeeplus
+

+ 957 - 0
pom.xml

@@ -0,0 +1,957 @@
+<?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>jeeplus</groupId>
+    <artifactId>jeeplus</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>war</packaging>
+    <name>jeeplus</name>
+
+    <!-- 项目属性 -->
+    <properties>
+        <lib.path>${basedir}/src/main/webapp/WEB-INF/lib</lib.path>
+        <!-- main version setting -->
+        <spring.version>4.0.8.RELEASE</spring.version>
+        <validator.version>5.1.1.Final</validator.version>
+        <mybatis.version>3.2.8</mybatis.version>
+        <mybatis-spring.version>1.2.2</mybatis-spring.version>
+        <druid.version>1.1.3</druid.version>
+        <ehcache.version>2.6.9</ehcache.version>
+        <ehcache-web.version>2.0.4</ehcache-web.version>
+        <shiro.version>1.2.3</shiro.version>
+        <sitemesh.version>2.4.2</sitemesh.version>
+        <activation.version>5.19.0</activation.version>
+        <jpush.version>3.2.17</jpush.version>
+        <jiguang.version>1.0.3</jiguang.version>
+        <netty.version>4.1.6.Final</netty.version>
+        <gson.version>2.3</gson.version>
+
+        <!-- tools version setting -->
+        <slf4j.version>1.7.22</slf4j.version>
+        <commons-lang.version>2.5</commons-lang.version>
+        <commons-lang3.version>3.3.2</commons-lang3.version>
+        <commons-io.version>2.4</commons-io.version>
+        <commons-codec.version>1.9</commons-codec.version>
+        <commons-fileupload.version>1.3.1</commons-fileupload.version>
+        <commons-beanutils.version>1.9.1</commons-beanutils.version>
+        <jackson.version>2.2.3</jackson.version>
+        <fastjson.version>1.1.40</fastjson.version>
+        <xstream.version>1.4.7</xstream.version>
+        <guava.version>17.0</guava.version>
+        <dozer.version>5.5.1</dozer.version>
+        <poi.version>3.9</poi.version>
+        <freemarker.version>2.3.20</freemarker.version>
+        <ckfinder.version>2.3</ckfinder.version>
+        <websocket.version>1.3.0</websocket.version>
+        <!--<ckfinder.version>2.6.1</ckfinder.version>-->
+
+        <!-- jdbc driver setting -->
+        <mysql.driver.version>5.1.30</mysql.driver.version>
+        <oracle.driver.version>10.2.0.4.0</oracle.driver.version>
+        <mssql.driver.version>1.3.1</mssql.driver.version>
+        <mongodb.data.version>1.8.4.RELEASE</mongodb.data.version>
+        <mongodb.driver.version>3.2.2</mongodb.driver.version>
+
+        <!-- environment setting -->
+        <jdk.version>1.7</jdk.version>
+        <tomcat.version>2.2</tomcat.version>
+        <jetty.version>7.6.14.v20131031</jetty.version>
+        <webserver.port>8181</webserver.port>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <downloadSources>true</downloadSources>
+
+        <jodconverter.version>2.2.1</jodconverter.version>
+        <pdfbox.version>2.0.9</pdfbox.version>
+
+    </properties>
+    <build>
+        <finalName>jeeplus</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.3</version>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                    <compilerArguments>
+                        <bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath>
+                    </compilerArguments>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <configuration>
+                    <connectors>
+                        <connector
+                                implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
+                            <port>${webserver.port}</port>
+                        </connector>
+                    </connectors>
+                    <webAppConfig>
+                        <contextPath>/${project.artifactId}</contextPath>
+                    </webAppConfig>
+                    <systemProperties>
+                        <systemProperty>
+                            <name>org.mortbay.util.URI.charset</name>
+                            <value>${project.build.sourceEncoding}</value>
+                        </systemProperty>
+                    </systemProperties>
+                    <stopKey/>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!-- 依赖项定义 -->
+    <dependencies>
+
+        <!-- SPRING begin -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.jpush.api</groupId>
+            <artifactId>jpush-client</artifactId>
+            <version>${jpush.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.jpush.api</groupId>
+            <artifactId>jiguang-common</artifactId>
+            <version>${jiguang.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>${netty.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>${gson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+            <version>${spring.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-tx</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <!-- spring orm -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-orm</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-jdbc</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <!-- bean validate -->
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+            <version>${validator.version}</version>
+        </dependency>
+        <!-- SPRING end -->
+
+        <!-- AOP begin -->
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+            <version>1.7.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.7.4</version>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib</artifactId>
+            <version>3.1</version>
+        </dependency>
+        <!-- AOP end -->
+
+        <!-- PERSISTENCE begin -->
+
+        <!-- MyBatis -->
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis</artifactId>
+            <version>${mybatis.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis</groupId>
+            <artifactId>mybatis-spring</artifactId>
+            <version>${mybatis-spring.version}</version>
+        </dependency>
+
+        <!-- connection pool -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <!-- jdbc driver -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql.driver.version}</version>
+        </dependency>
+
+        <!-- mongoDB支持jar -->
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-mongodb</artifactId>
+            <version>${mongodb.data.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <version>${mongodb.driver.version}</version>
+        </dependency>
+        <!-- <dependency>
+            <groupId>com.oracle</groupId>
+            <artifactId>ojdbc14</artifactId>
+            <version>${oracle.driver.version}</version>
+            <scope>runtime</scope>
+        </dependency> -->
+        <dependency>
+            <groupId>net.sourceforge.jtds</groupId>
+            <artifactId>jtds</artifactId>
+            <version>${mssql.driver.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <!-- PERSISTENCE end -->
+
+        <!-- WEB begin -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-oxm</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>opensymphony</groupId>
+            <artifactId>sitemesh</artifactId>
+            <version>${sitemesh.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>taglibs</groupId>
+            <artifactId>standard</artifactId>
+            <version>1.1.2</version>
+            <type>jar</type>
+        </dependency>
+
+
+        <!--<dependency>-->
+            <!--<groupId>javax</groupId>-->
+            <!--<artifactId>javaee-api</artifactId>-->
+            <!--<version>7.0</version>-->
+        <!--</dependency>-->
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+            <version>1.2</version>
+            <type>jar</type>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>jsp-api</artifactId>
+            <version>2.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>jsp-api</artifactId>
+            <version>2.2</version>
+            <scope>provided</scope>
+        </dependency>
+
+
+
+        <!-- CACHE begin -->
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache-core</artifactId>
+            <version>${ehcache.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache-web</artifactId>
+            <version>${ehcache-web.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>2.5.1</version>
+        </dependency>
+        <!-- CACHE end -->
+
+        <!-- SECURITY begin -->
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+            <version>${shiro.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+            <version>${shiro.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-cas</artifactId>
+            <version>${shiro.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+            <version>${shiro.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-ehcache</artifactId>
+            <version>${shiro.version}</version>
+        </dependency>
+        <!-- SECURITY end -->
+
+
+        <!-- LOGGING begin -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <!-- common-logging 实际调用slf4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <!-- java.util.logging 实际调用slf4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jul-to-slf4j</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <!-- LOGGING end -->
+
+        <!-- GENERAL UTILS begin -->
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${commons-lang.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>${commons-lang3.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons-io.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>${commons-codec.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>${commons-fileupload.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>${commons-beanutils.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- google java lib -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${guava.version}</version>
+        </dependency>
+
+        <!-- jackson json -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.module</groupId>
+            <artifactId>jackson-module-jaxb-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+            <version>1.9.13</version>
+        </dependency>
+
+        <!-- fastjson json
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency> -->
+
+        <!-- xstream xml -->
+        <dependency>
+            <groupId>com.thoughtworks.xstream</groupId>
+            <artifactId>xstream</artifactId>
+            <version>${xstream.version}</version>
+        </dependency>
+
+        <!-- pojo copy -->
+        <dependency>
+            <groupId>net.sf.dozer</groupId>
+            <artifactId>dozer</artifactId>
+            <version>${dozer.version}</version>
+        </dependency>
+
+        <!-- freemarker engine -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>${freemarker.version}</version>
+        </dependency>
+
+        <!-- email -->
+        <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+            <version>1.4.7</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
+        <!-- poi office -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+
+        <!-- image util -->
+        <dependency>
+            <groupId>com.drewnoakes</groupId>
+            <artifactId>metadata-extractor</artifactId>
+            <version>2.6.2</version>
+        </dependency>
+
+        <!-- 条形码、二维码生成  -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>2.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>batik</groupId>
+            <artifactId>batik-util</artifactId>
+            <version>1.6-1</version>
+        </dependency>
+
+
+        <!-- 中文分词 -->
+        <dependency>
+            <groupId>org.wltea</groupId>
+            <artifactId>analyzer</artifactId>
+            <version>2012_u6</version>
+        </dependency>
+        <!-- GENERAL UTILS end -->
+
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+
+
+        <!-- CKFinder begin -->
+        <dependency>
+            <groupId>net.coobird</groupId>
+            <artifactId>thumbnailator</artifactId>
+            <version>0.4.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ckfinder</groupId>
+            <artifactId>apache-ant-zip</artifactId>
+            <version>${ckfinder.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ckfinder</groupId>
+            <artifactId>ckfinder</artifactId>
+            <version>${ckfinder.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ckfinder</groupId>
+            <artifactId>ckfinderplugin-fileeditor</artifactId>
+            <version>${ckfinder.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ckfinder</groupId>
+            <artifactId>ckfinderplugin-imageresize</artifactId>
+            <version>${ckfinder.version}</version>
+        </dependency>
+        <!-- CKFinder end -->
+
+        <dependency>
+            <groupId>org.jeeframework</groupId>
+            <artifactId>gencode</artifactId>
+            <version>1.5</version>
+        </dependency>
+
+
+        <!-- TEST begin -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <!-- TEST end -->
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+            <version>1.20</version>
+        </dependency>
+
+        <!-- Java-WebSocket -->
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>${websocket.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.fusesource</groupId>
+            <artifactId>sigar</artifactId>
+            <version>1.6.4</version>
+        </dependency>
+
+        <!--httpcore -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4.4</version>
+        </dependency>
+
+        <!--httpclient -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>QRCoder</groupId>
+            <artifactId>QRCoder</artifactId>
+            <version>1.0</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.github.abel533/ECharts -->
+        <dependency>
+            <groupId>com.github.abel533</groupId>
+            <artifactId>ECharts</artifactId>
+            <version>2.2.7</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.6.2</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-email -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-email</artifactId>
+            <version>1.4</version>
+        </dependency>
+
+
+        <!--&lt;!&ndash; https://mvnrepository.com/artifact/javax.activation/activation &ndash;&gt;-->
+        <!--<dependency>-->
+            <!--<groupId>javax.activation</groupId>-->
+            <!--<artifactId>activation</artifactId>-->
+            <!--<version>1.1.1</version>-->
+        <!--</dependency>-->
+        <!-- https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-converter -->
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-bpmn-converter</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-bpmn-model</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-common-rest</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-diagram-rest</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-engine</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-explorer</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-image-generator</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-json-converter</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-modeler</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-process-validation</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-rest</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-spring</artifactId>
+            <version>${activation.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sf.json-lib</groupId>
+            <artifactId>json-lib</artifactId>
+            <version>2.4</version>
+            <classifier>jdk15</classifier>
+        </dependency>
+
+        <!--<dependency>
+            <groupId>net.sf.json-lib</groupId>
+            <artifactId>json-lib</artifactId>
+            <version>2.4</version>
+        </dependency>-->
+
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>2.7.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>logging-interceptor</artifactId>
+            <version>2.7.5</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.squareup.okio</groupId>
+            <artifactId>okio</artifactId>
+            <version>1.6.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.easemob</groupId>
+            <artifactId>rest-java-sdk</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+        <!-- 新增jar包 -->
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.12</version>
+        </dependency>
+        <dependency>
+            <groupId>fr.opensagres.xdocreport</groupId>
+            <artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
+            <version>1.0.4</version>
+        </dependency>
+        <!--阿里云图片服务-->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>2.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>2.1.7</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-sts</artifactId>
+            <version>2.1.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.20</version>
+        </dependency>
+
+        <!-- openoffice -->
+        <dependency>
+            <groupId>org.openoffice</groupId>
+            <artifactId>ridl</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+
+        <!--<dependency>
+            <groupId>org.libreoffice</groupId>
+            <artifactId>ridl</artifactId>
+            <version>5.4.2</version>
+        </dependency>-->
+
+        <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>${pdfbox.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>www.ssrh</groupId>
+            <artifactId>DBstep</artifactId>
+            <version>1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>www.ssrh</groupId>
+            <artifactId>jodconverter</artifactId>
+            <version>2.2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>www.ssrh</groupId>
+            <artifactId>jodconverter-cli</artifactId>
+            <version>2.2.2</version>
+        </dependency>
+        <dependency>
+            <groupId>fakepath</groupId>
+            <artifactId>jurt</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>fakepath</groupId>
+            <artifactId>juh</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.27</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.woodstox</groupId>
+            <artifactId>wstx-asl</artifactId>
+            <version>3.2.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.openoffice</groupId>
+            <artifactId>unoil</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.birt.runtime.3_7_1</groupId>
+            <artifactId>com.lowagie.text</artifactId>
+            <version>2.1.7</version>
+        </dependency>
+        <!--xmpp  smack-->
+        <dependency>
+            <groupId>org.igniterealtime.smack</groupId>
+            <artifactId>smack-core</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.igniterealtime.smack</groupId>
+            <artifactId>smack-tcp</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.igniterealtime.smack</groupId>
+            <artifactId>smack-java7</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.igniterealtime.smack</groupId>
+            <artifactId>smack-extensions</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.igniterealtime.smack</groupId>
+            <artifactId>smack-sasl-provided</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 197 - 0
src/main/java/com/easemob/server/example/api/ChatGroupAPI.java

@@ -0,0 +1,197 @@
+package com.easemob.server.example.api;
+
+/**
+ * This interface is created for RestAPI of Chat Group, it should be
+ * synchronized with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/:
+ *      60groupmgmt
+ */
+public interface ChatGroupAPI {
+
+	/**
+	 * 获取群组,参数为空时获取所有群组 <br>
+	 * GET
+	 * 
+	 * @param limit
+	 *            单页数量
+	 * @param cursor
+	 *            游标,存在更多记录时产生
+	 * @return
+	 */
+	Object getChatGroups(Long limit, String cursor);
+
+	/**
+	 * 获取一个或者多个群组的详情 <br>
+	 * GET
+	 * 
+	 * @param groupIds
+	 *            群组ID数组
+	 * @return
+	 */
+	Object getChatGroupDetails(String[] groupIds);
+
+	/**
+	 * 创建一个群组 <br>
+	 * POST
+	 * 
+	 * @param payload
+	 *            <code>{"groupname":"testrestgrp12","desc":"server create group","public":true,"maxusers":300,"approval":true,"owner":"jma1","members":["jma2","jma3"]}</code>
+	 * @return
+	 */
+	Object createChatGroup(Object payload);
+
+	/**
+	 * 修改群组信息 <br>
+	 * PUT
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param payload
+	 *            <code>{"groupname":"testrestgrp12",description":"update groupinfo","maxusers":300}</code>
+	 * @return
+	 */
+	Object modifyChatGroup(String groupId, Object payload);
+
+	/**
+	 * 删除群组 <br>
+	 * DELETE
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @return
+	 */
+	Object deleteChatGroup(String groupId);
+
+	/**
+	 * 获取群组所有用户 <br>
+	 * GET
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @return
+	 */
+	Object getChatGroupUsers(String groupId);
+
+	/**
+	 * 群组加人[单个] <br>
+	 * POST
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userId
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object addSingleUserToChatGroup(String groupId, String userId);
+
+	/**
+	 * 群组加人[批量] <br>
+	 * POST
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param payload
+	 *            用户ID或用户名,数组形式
+	 * @return
+	 * @see com.easemob.server.example.comm.body.UserNamesBody
+	 */
+	Object addBatchUsersToChatGroup(String groupId, Object payload);
+
+	/**
+	 * 群组减人[单个] <br>
+	 * DELETE
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userId
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object removeSingleUserFromChatGroup(String groupId, String userId);
+
+	/**
+	 * 群组减人[批量] <br>
+	 * DELETE
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userIds
+	 *            用户ID或用户名,数组形式
+	 * @return
+	 */
+	Object removeBatchUsersFromChatGroup(String groupId, String[] userIds);
+
+	/**
+	 * 群组转让 <br>
+	 * PUT
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param payload
+	 *            新群主ID或用户名
+	 * @return
+     * @see com.easemob.server.example.comm.body.GroupOwnerTransferBody
+	 */
+	Object transferChatGroupOwner(String groupId, Object payload);
+
+	/**
+	 * 查询群组黑名单 <br>
+	 * GET
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @return
+	 */
+	Object getChatGroupBlockUsers(String groupId);
+
+	/**
+	 * 群组黑名单个添加 <br>
+	 * POST
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userId
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object addSingleBlockUserToChatGroup(String groupId, String userId);
+
+	/**
+	 * 群组黑名单批量添加 <br>
+	 * POST
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param payload
+	 *            用户ID或用户名,数组形式
+	 * @return
+     * @see com.easemob.server.example.comm.body.UserNamesBody
+	 */
+	Object addBatchBlockUsersToChatGroup(String groupId, Object payload);
+
+	/**
+	 * 群组黑名单单个删除 <br>
+	 * DELETE
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userId
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object removeSingleBlockUserFromChatGroup(String groupId, String userId);
+
+	/**
+	 * 群组黑名单批量删除 <br>
+	 * DELETE
+	 * 
+	 * @param groupId
+	 *            群组标识
+	 * @param userIds
+	 *            用户ID或用户名,数组形式
+	 * @return
+	 */
+	Object removeBatchBlockUsersFromChatGroup(String groupId, String[] userIds);
+}

+ 26 - 0
src/main/java/com/easemob/server/example/api/ChatMessageAPI.java

@@ -0,0 +1,26 @@
+package com.easemob.server.example.api;
+
+/**
+ * This interface is created for RestAPI of Chat Messages, it should be
+ * synchronized with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/
+ */
+public interface ChatMessageAPI {
+	/**
+	 * 导出聊天记录,默认返回10条 <br>
+	 * GET
+	 * 
+	 * @param limit
+	 *            单页条数,最多1000
+	 * @param cursor
+	 *            游标,存在更多页时产生
+	 * @param query
+	 *            查询语句 <code>ql=select * where timestamp>1403164734226</code>
+	 * @return
+	 *
+	 * 此接口已经过期,下个版本将会提供新接口
+	 */
+	Object exportChatMessages(Long limit, String cursor, String query);
+}

+ 110 - 0
src/main/java/com/easemob/server/example/api/ChatRoomAPI.java

@@ -0,0 +1,110 @@
+package com.easemob.server.example.api;
+
+/**
+ * This interface is created for RestAPI of Chat Room, it should be synchronized
+ * with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/
+ */
+public interface ChatRoomAPI {
+	/**
+	 * 创建聊天室 <br>
+	 * POST
+	 * 
+	 * @param payload
+	 *            <code>{name":"testchatroom","description":"server create chatroom","maxusers":300,"owner":"jma1","members":["jma2","jma3"]}</code>
+	 * @return
+	 */
+	Object createChatRoom(Object payload);
+
+	/**
+	 * 修改聊天室信息 <br>
+	 * PUT
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @param payload
+	 *            <code>{"name":"test chatroom","description":
+	 *            "update chatroominfo","maxusers":200}
+	 * @return
+	 */
+	Object modifyChatRoom(String roomId, Object payload);
+
+	/**
+	 * 删除聊天室 <br>
+	 * DELETE
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @return
+	 */
+	Object deleteChatRoom(String roomId);
+
+	/**
+	 * 获取app中所有的聊天室 <br>
+	 * GET
+	 * 
+	 * @return
+	 */
+	Object getAllChatRooms();
+
+	/**
+	 * 获取一个聊天室详情 <br>
+	 * GET
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @return
+	 */
+	Object getChatRoomDetail(String roomId);
+
+	/**
+	 * 聊天室成员添加[单个] <br>
+	 * POST
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @param userName
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object addSingleUserToChatRoom(String roomId, String userName);
+
+	/**
+	 * 聊天室成员添加[批量] <br>
+	 * POST
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @param payload
+	 *            用户ID或用户名,数组形式
+	 * @return
+	 * @see com.easemob.server.example.comm.body.UserNamesBody
+	 */
+	Object addBatchUsersToChatRoom(String roomId, Object payload);
+
+	/**
+	 * 聊天室成员删除[单个] <br>
+	 * DELETE
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @param userName
+	 *            用户ID或用户名
+	 * @return
+	 */
+	Object removeSingleUserFromChatRoom(String roomId, String userName);
+
+	/**
+	 * 聊天室成员删除[批量] <br>
+	 * DELETE
+	 * 
+	 * @param roomId
+	 *            聊天室标识
+	 * @param userNames
+	 *            用户ID或用户名,数组形式
+	 * @return
+	 */
+	Object removeBatchUsersFromChatRoom(String roomId, String[] userNames);
+}

+ 35 - 0
src/main/java/com/easemob/server/example/api/FileAPI.java

@@ -0,0 +1,35 @@
+package com.easemob.server.example.api;
+
+/**
+ * This interface is created for RestAPI of File Upload and Download, it should
+ * be synchronized with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/
+ */
+public interface FileAPI {
+
+	/**
+	 * 上传文件 <br>
+	 * POST
+	 * 
+	 * @param file
+	 *            上传的文件对象,可以是地址、流等,以实现类为准
+	 * @return
+	 */
+	Object uploadFile(Object file);
+
+	/**
+	 * 下载文件 <br>
+	 * GET
+	 * 
+	 * @param fileUUID
+	 *            文件唯一标识,从上传Response-entities-uuid中获取
+	 * @param shareSecret
+	 *            文件访问秘钥,从上传Response-entities-share-secret中获取
+	 * @param isThumbnail
+	 *            ,如果下载图片,是否为缩略图
+	 * @return
+	 */
+	Object downloadFile(String fileUUID, String shareSecret, Boolean isThumbnail);
+}

+ 251 - 0
src/main/java/com/easemob/server/example/api/IMUserAPI.java

@@ -0,0 +1,251 @@
+package com.easemob.server.example.api;
+
+/**
+ * This interface is created for RestAPI of User Integration, it should be
+ * synchronized with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/
+ */
+public interface IMUserAPI {
+
+	/**
+	 * 注册IM用户[单个] <br>
+	 * POST
+	 * 
+	 * @param payload
+	 *            <code>{"username":"${用户名}","password":"${密码}", "nickname":"${昵称值}"}</code>
+	 * @return
+	 */
+	Object createNewIMUserSingle(Object payload);
+
+	/**
+	 * 注册IM用户[批量] <br>
+	 * POST
+	 * 
+	 * @param payload
+	 *            <code>[{"username":"${用户名1}","password":"${密码}"},…,{"username":"${用户名2}","password":"${密码}"}]</code>
+	 * @return
+	 */
+	Object createNewIMUserBatch(Object payload);
+
+	/**
+	 * 获取IM用户[单个] <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object getIMUserByUserName(String userName);
+
+	/**
+	 * 获取IM用户[批量],参数为空时默认返回最早创建的10个用户 <br>
+	 * GET
+	 * 
+	 * @param limit
+	 *            单页获取数量
+	 * @param cursor
+	 *            游标,大于单页记录时会产生
+	 * @return
+	 */
+	Object getIMUsersBatch(Long limit, String cursor);
+
+	/**
+	 * 删除IM用户[单个] <br>
+	 * DELETE
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object deleteIMUserByUserName(String userName);
+
+	/**
+	 * 删除IM用户[批量],随机删除 <br>
+	 * DELETE
+	 * 
+	 * @param limit
+	 *            删除数量,建议100-500
+	 * @return
+	 */
+	Object deleteIMUserBatch(Long limit, String cursor);
+
+	/**
+	 * 重置IM用户密码 <br>
+	 * PUT
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param payload
+	 *            <code>{"newpassword" : "${新密码指定的字符串}"}</code>
+	 * @return
+	 */
+	Object modifyIMUserPasswordWithAdminToken(String userName, Object payload);
+
+	/**
+	 * 修改用户昵称 <br>
+	 * PUT
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param payload
+	 *            <code>{"nickname" : "${昵称值}"}</code>
+	 * @return
+	 */
+	Object modifyIMUserNickNameWithAdminToken(String userName, Object payload);
+
+	/**
+	 * 给IM用户的添加好友 <br>
+	 * POST
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param friendName
+	 *            好友用戶名或用戶ID
+	 * @return
+	 */
+	Object addFriendSingle(String userName, String friendName);
+
+	/**
+	 * 解除IM用户的好友关系 <br>
+	 * DELETE
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param friendName
+	 *            好友用戶名或用戶ID
+	 * @return
+	 */
+	Object deleteFriendSingle(String userName, String friendName);
+
+	/**
+	 * 查看某个IM用户的好友信息 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object getFriends(String userName);
+
+	/**
+	 * 获取IM用户的黑名单 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object getBlackList(String userName);
+
+	/**
+	 * 往IM用户的黑名单中加人 <br>
+	 * POST
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param payload
+	 *            <code>{"usernames":["5cxhactgdj", "mh2kbjyop1"]}</code>
+	 * @return
+	 */
+	Object addToBlackList(String userName, Object payload);
+
+	/**
+	 * 从IM用户的黑名单中减人 <br>
+	 * DELETE
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param blackListName
+	 *            黑名单用戶名或用戶ID
+	 * @return
+	 */
+	Object removeFromBlackList(String userName, String blackListName);
+
+	/**
+	 * 查看用户在线状态 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object getIMUserStatus(String userName);
+
+	/**
+	 * 查询离线消息数 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object getOfflineMsgCount(String userName);
+
+	/**
+	 * 查询某条离线消息状态 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @param msgId
+	 *            消息ID
+	 * @return
+	 */
+	Object getSpecifiedOfflineMsgStatus(String userName, String msgId);
+
+	/**
+	 * 用户账号禁用 <br>
+	 * POST
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object deactivateIMUser(String userName);
+
+	/**
+	 * 用户账号解禁 <br>
+	 * POST
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object activateIMUser(String userName);
+
+	/**
+	 * 强制用户下线 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 */
+	Object disconnectIMUser(String userName);
+
+	/**
+	 * 获取用户参与的群组 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 * @see http://docs.easemob.com/doku.php?id=start:100serverintegration:
+	 *      60groupmgmt
+	 */
+	Object getIMUserAllChatGroups(String userName);
+
+	/**
+	 * 获取用户所有参与的聊天室 <br>
+	 * GET
+	 * 
+	 * @param userName
+	 *            用戶名或用戶ID
+	 * @return
+	 * @see http://docs.easemob.com/doku.php?id=start:100serverintegration:
+	 *      70chatroommgmt
+	 */
+	Object getIMUserAllChatRooms(String userName);
+}

+ 14 - 0
src/main/java/com/easemob/server/example/api/SendMessageAPI.java

@@ -0,0 +1,14 @@
+package com.easemob.server.example.api;
+
+
+/**
+ * This interface is created for RestAPI of Sending Message, it should be
+ * synchronized with the API list.
+ * 
+ * @author Eric23 2016-01-05
+ * @see http://docs.easemob.com/
+ */
+public interface SendMessageAPI {
+
+	Object sendMessage(Object payload);
+}

+ 15 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobAuthToken.java

@@ -0,0 +1,15 @@
+package com.easemob.server.example.api.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.AuthTokenAPI;
+import com.easemob.server.example.comm.TokenUtil;
+
+@Service
+public class EasemobAuthToken implements AuthTokenAPI{
+
+	@Override
+	public Object getAuthToken(){
+		return TokenUtil.getAccessToken();
+	}
+}

+ 183 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobChatGroup.java

@@ -0,0 +1,183 @@
+package com.easemob.server.example.api.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.ChatGroupAPI;
+import com.easemob.server.example.comm.OrgInfo;
+import com.easemob.server.example.comm.ResponseHandler;
+import com.easemob.server.example.comm.EasemobAPI;
+import com.easemob.server.example.comm.TokenUtil;
+
+import io.swagger.client.ApiException;
+import io.swagger.client.StringUtil;
+import io.swagger.client.api.GroupsApi;
+import io.swagger.client.model.*;
+
+@Service
+public class EasemobChatGroup implements ChatGroupAPI {
+
+    private ResponseHandler responseHandler = new ResponseHandler();
+    private GroupsApi api = new GroupsApi();
+    @Override
+    public Object getChatGroups(final Long limit,final String cursor) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),limit+"",cursor);
+            }
+        });
+    }
+
+    @Override
+    public Object getChatGroupDetails(final String[] groupIds) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdsGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),StringUtil.join(groupIds,","));
+            }
+        });
+    }
+    @Override
+    public Object createChatGroup(final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(), (Group) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object modifyChatGroup(final String groupId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdPut(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId, (ModifyGroup) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object deleteChatGroup(final String groupId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId);
+            }
+        });
+    }
+
+    @Override
+    public Object getChatGroupUsers(final String groupId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdUsersGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId);
+            }
+        });
+    }
+
+    @Override
+    public Object addSingleUserToChatGroup(final String groupId,final String userId) {
+        final UserNames userNames = new UserNames();
+        UserName userList = new UserName();
+        userList.add(userId);
+        userNames.usernames(userList);
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,userNames);
+            }
+        });
+    }
+
+    @Override
+    public Object addBatchUsersToChatGroup(final String groupId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId, (UserNames) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object removeSingleUserFromChatGroup(final String groupId,final String userId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdUsersUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,userId);
+            }
+        });
+    }
+
+    @Override
+    public Object removeBatchUsersFromChatGroup(final String groupId,final String[] userIds) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdUsersMembersDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,StringUtil.join(userIds,","));
+            }
+        });
+    }
+
+    @Override
+    public Object transferChatGroupOwner(final String groupId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupidPut(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId, (NewOwner) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object getChatGroupBlockUsers(final String groupId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdBlocksUsersGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId);
+            }
+        });
+    }
+
+    @Override
+    public Object addSingleBlockUserToChatGroup(final String groupId,final String userId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdBlocksUsersUsernamePost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,userId);
+            }
+        });
+    }
+
+    @Override
+    public Object addBatchBlockUsersToChatGroup(final String groupId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdBlocksUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId, (UserNames) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object removeSingleBlockUserFromChatGroup(final String groupId,final String userId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdBlocksUsersUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,userId);
+            }
+        });
+    }
+
+    @Override
+    public Object removeBatchBlockUsersFromChatGroup(final String groupId,final String[] userIds) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatgroupsGroupIdBlocksUsersUsernamesDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),groupId,StringUtil.join(userIds,","));
+            }
+        });
+    }
+}

+ 38 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobChatMessage.java

@@ -0,0 +1,38 @@
+package com.easemob.server.example.api.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.ChatMessageAPI;
+import com.easemob.server.example.comm.OrgInfo;
+import com.easemob.server.example.comm.ResponseHandler;
+import com.easemob.server.example.comm.EasemobAPI;
+import com.easemob.server.example.comm.TokenUtil;
+
+import io.swagger.client.ApiException;
+import io.swagger.client.api.ChatHistoryApi;
+
+@Service
+public class EasemobChatMessage  implements ChatMessageAPI {
+
+    private ResponseHandler responseHandler = new ResponseHandler();
+    private ChatHistoryApi api = new ChatHistoryApi();
+
+    @Override
+    public Object exportChatMessages(final Long limit,final String cursor,final String query) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatmessagesGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),query,limit+"",cursor);
+            }
+        });
+    }
+
+    public Object exportChatMessage(final String time) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatmessagesTimeGet(OrgInfo.ORG_NAME, OrgInfo.APP_NAME, TokenUtil.getAccessToken(), time);
+            }
+        });
+    }
+}

+ 113 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobChatRoom.java

@@ -0,0 +1,113 @@
+package com.easemob.server.example.api.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.ChatRoomAPI;
+import com.easemob.server.example.comm.OrgInfo;
+import com.easemob.server.example.comm.ResponseHandler;
+import com.easemob.server.example.comm.EasemobAPI;
+import com.easemob.server.example.comm.TokenUtil;
+
+import io.swagger.client.ApiException;
+import io.swagger.client.StringUtil;
+import io.swagger.client.api.ChatRoomsApi;
+import io.swagger.client.model.Chatroom;
+import io.swagger.client.model.ModifyChatroom;
+import io.swagger.client.model.UserNames;
+
+
+@Service
+public class EasemobChatRoom implements ChatRoomAPI {
+    private ResponseHandler responseHandler = new ResponseHandler();
+    private ChatRoomsApi api = new ChatRoomsApi();
+
+    @Override
+    public Object createChatRoom(final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(), (Chatroom) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object modifyChatRoom(final String roomId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdPut(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId, (ModifyChatroom) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object deleteChatRoom(final String roomId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId);
+            }
+        });
+    }
+
+    @Override
+    public Object getAllChatRooms() {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken());
+            }
+        });
+    }
+
+    @Override
+    public Object getChatRoomDetail(final String roomId) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId);
+            }
+        });
+    }
+
+    @Override
+    public Object addSingleUserToChatRoom(final String roomId,final String userName) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdUsersUsernamePost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId,userName);
+            }
+        });
+    }
+
+    @Override
+    public Object addBatchUsersToChatRoom(final String roomId,final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId, (UserNames) payload);
+            }
+        });
+    }
+
+    @Override
+    public Object removeSingleUserFromChatRoom(final String roomId,final String userName) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdUsersUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId,userName);
+            }
+        });
+    }
+
+    @Override
+    public Object removeBatchUsersFromChatRoom(final String roomId,final String[] userNames) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameChatroomsChatroomIdUsersUsernamesDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),roomId, StringUtil.join(userNames,","));
+            }
+        });
+    }
+}

+ 243 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobIMUsers.java

@@ -0,0 +1,243 @@
+package com.easemob.server.example.api.impl;
+
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.IMUserAPI;
+import com.easemob.server.example.comm.EasemobAPI;
+import com.easemob.server.example.comm.OrgInfo;
+import com.easemob.server.example.comm.ResponseHandler;
+import com.easemob.server.example.comm.TokenUtil;
+
+import io.swagger.client.ApiException;
+import io.swagger.client.api.UsersApi;
+import io.swagger.client.model.NewPassword;
+import io.swagger.client.model.Nickname;
+import io.swagger.client.model.RegisterUsers;
+import io.swagger.client.model.UserNames;
+
+@Service
+public class EasemobIMUsers  implements IMUserAPI {
+
+	private UsersApi api = new UsersApi();
+	private ResponseHandler responseHandler = new ResponseHandler();
+	@Override
+	public Object createNewIMUserSingle(final Object payload) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME, (RegisterUsers) payload,TokenUtil.getAccessToken());
+			}
+		});
+	}
+
+	@Override
+	public Object createNewIMUserBatch(final Object payload) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME, (RegisterUsers) payload,TokenUtil.getAccessToken());
+			}
+		});
+	}
+
+	@Override
+	public Object getIMUserByUserName(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+		}
+		});
+	}
+
+	@Override
+	public Object getIMUsersBatch(final Long limit,final String cursor) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),limit+"",cursor);
+			}
+		});
+	}
+
+	@Override
+	public Object deleteIMUserByUserName(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object deleteIMUserBatch(final Long limit,final String cursor) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),limit+"",cursor);
+			}
+		});
+	}
+
+	@Override
+	public Object modifyIMUserPasswordWithAdminToken(final String userName, final Object payload) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernamePasswordPut(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,userName, (NewPassword) payload,TokenUtil.getAccessToken());
+			}
+		});
+	}
+
+	@Override
+	public Object modifyIMUserNickNameWithAdminToken(final String userName,final Object payload) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernamePut(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,userName, (Nickname) payload,TokenUtil.getAccessToken());
+			}
+		});
+	}
+
+	@Override
+	public Object addFriendSingle(final String userName,final String friendName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameContactsUsersFriendUsernamePost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName,friendName);
+			}
+		});
+	}
+
+	@Override
+	public Object deleteFriendSingle(final String userName,final String friendName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameContactsUsersFriendUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName,friendName);
+			}
+		});
+	}
+
+	@Override
+	public Object getFriends(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameContactsUsersGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object getBlackList(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameBlocksUsersGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object addToBlackList(final String userName,final Object payload) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameBlocksUsersPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName, (UserNames) payload);
+			}
+		});
+	}
+
+	@Override
+	public Object removeFromBlackList(final String userName,final String blackListName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameBlocksUsersBlockedUsernameDelete(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName,blackListName);
+			}
+		});
+	}
+
+	@Override
+	public Object getIMUserStatus(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameStatusGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object getOfflineMsgCount(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersOwnerUsernameOfflineMsgCountGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object getSpecifiedOfflineMsgStatus(final String userName,final String msgId) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameOfflineMsgStatusMsgIdGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName,msgId);
+			}
+		});
+	}
+
+	@Override
+	public Object deactivateIMUser(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameDeactivatePost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object activateIMUser(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameActivatePost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object disconnectIMUser(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameDisconnectGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object getIMUserAllChatGroups(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameJoinedChatgroupsGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+
+	@Override
+	public Object getIMUserAllChatRooms(final String userName) {
+		return responseHandler.handle(new EasemobAPI() {
+			@Override
+			public Object invokeEasemobAPI() throws ApiException {
+				return api.orgNameAppNameUsersUsernameJoinedChatroomsGet(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(),userName);
+			}
+		});
+	}
+}

+ 28 - 0
src/main/java/com/easemob/server/example/api/impl/EasemobSendMessage.java

@@ -0,0 +1,28 @@
+package com.easemob.server.example.api.impl;
+
+import org.springframework.stereotype.Service;
+
+import com.easemob.server.example.api.SendMessageAPI;
+import com.easemob.server.example.comm.OrgInfo;
+import com.easemob.server.example.comm.ResponseHandler;
+import com.easemob.server.example.comm.EasemobAPI;
+import com.easemob.server.example.comm.TokenUtil;
+
+import io.swagger.client.ApiException;
+import io.swagger.client.api.MessagesApi;
+import io.swagger.client.model.Msg;
+
+@Service
+public class EasemobSendMessage implements SendMessageAPI {
+    private ResponseHandler responseHandler = new ResponseHandler();
+    private MessagesApi api = new MessagesApi();
+    @Override
+    public Object sendMessage(final Object payload) {
+        return responseHandler.handle(new EasemobAPI() {
+            @Override
+            public Object invokeEasemobAPI() throws ApiException {
+                return api.orgNameAppNameMessagesPost(OrgInfo.ORG_NAME,OrgInfo.APP_NAME,TokenUtil.getAccessToken(), (Msg) payload);
+            }
+        });
+    }
+}

+ 10 - 0
src/main/java/com/easemob/server/example/comm/EasemobAPI.java

@@ -0,0 +1,10 @@
+package com.easemob.server.example.comm;
+
+import io.swagger.client.ApiException;
+
+/**
+ * Created by easemob on 2017/3/16.
+ */
+public interface EasemobAPI {
+    Object invokeEasemobAPI() throws ApiException;
+}

+ 30 - 0
src/main/java/com/easemob/server/example/comm/OrgInfo.java

@@ -0,0 +1,30 @@
+package com.easemob.server.example.comm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * Created by easemob on 2017/3/31.
+ */
+public class OrgInfo {
+
+    public static String ORG_NAME;
+    public static String APP_NAME;
+    public static final Logger logger = LoggerFactory.getLogger(OrgInfo.class);
+
+    static {
+        InputStream inputStream = OrgInfo.class.getClassLoader().getResourceAsStream("jeeplus.properties");
+        Properties prop = new Properties();
+        try {
+            prop.load(inputStream);
+        } catch (IOException e) {
+            logger.error(e.getMessage());
+        }
+        ORG_NAME = prop.getProperty("ORG_NAME");
+        APP_NAME = prop.getProperty("APP_NAME");
+    }
+}

+ 70 - 0
src/main/java/com/easemob/server/example/comm/ResponseHandler.java

@@ -0,0 +1,70 @@
+package com.easemob.server.example.comm;
+
+import com.google.gson.Gson;
+import io.swagger.client.ApiException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by easemob on 2017/3/16.
+ */
+public class ResponseHandler {
+    private static final Logger logger = LoggerFactory.getLogger(ResponseHandler.class);
+
+    public Object handle(EasemobAPI easemobAPI) {
+        Object result = null;
+        try {
+            result = easemobAPI.invokeEasemobAPI();
+        } catch (ApiException e) {
+            if (e.getCode() == 401) {
+                logger.info("The current token is invalid, re-generating token for you and calling it again");
+                TokenUtil.initTokenByProp();
+                try {
+                    result = easemobAPI.invokeEasemobAPI();
+                } catch (ApiException e1) {
+                    logger.error(e1.getMessage());
+                }
+                return result;
+            }
+            if (e.getCode() == 429) {
+                logger.warn("The api call is too frequent");
+            }
+            if (e.getCode() >= 500) {
+                logger.info("The server connection failed and is being reconnected");
+                result = retry(easemobAPI);
+                if (result != null) {
+                    return result;
+                }
+                System.out.println(e);
+                logger.error("The server may be faulty. Please try again later");
+            }
+            Gson gson = new Gson();
+            Map<String, String> map = gson.fromJson(e.getResponseBody(), Map.class);
+            logger.error("error_code:{} error_msg:{} error_desc:{}", e.getCode(), e.getMessage(), map.get("error_description"));
+        }
+        return result;
+    }
+
+    public Object retry(EasemobAPI easemobAPI) {
+        Object result = null;
+        long time = 5;
+        for (int i = 0; i < 3; i++) {
+            try {
+                TimeUnit.SECONDS.sleep(time);
+                logger.info("Reconnection is in progress..." + i);
+                result = easemobAPI.invokeEasemobAPI();
+                if (result != null) {
+                    return result;
+                }
+            } catch (ApiException e1) {
+                time *= 3;
+            } catch (InterruptedException e1) {
+                logger.error(e1.getMessage());
+            }
+        }
+        return result;
+    }
+}

+ 75 - 0
src/main/java/com/easemob/server/example/comm/TokenUtil.java

@@ -0,0 +1,75 @@
+package com.easemob.server.example.comm;
+
+import com.google.gson.Gson;
+import io.swagger.client.ApiException;
+import io.swagger.client.api.AuthenticationApi;
+import io.swagger.client.model.Token;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Created by easemob on 2017/3/14.
+ */
+public class TokenUtil {
+    public static String GRANT_TYPE;
+    private static String CLIENT_ID;
+    private static String CLIENT_SECRET;
+    private static Token BODY;
+    private static AuthenticationApi API = new AuthenticationApi();
+    private static String ACCESS_TOKEN;
+    private static Double EXPIREDAT = -1D;
+    private static final Logger logger = LoggerFactory.getLogger(TokenUtil.class);
+
+    /**
+     * get token from server
+     */
+    static {
+        InputStream inputStream = TokenUtil.class.getClassLoader().getResourceAsStream("jeeplus.properties");
+        Properties prop = new Properties();
+        try {
+            prop.load(inputStream);
+        } catch (IOException e) {
+            logger.error(e.getMessage());
+        }
+        GRANT_TYPE = prop.getProperty("GRANT_TYPE");
+        CLIENT_ID = prop.getProperty("CLIENT_ID");
+        CLIENT_SECRET = prop.getProperty("CLIENT_SECRET");
+        BODY = new Token().clientId(CLIENT_ID).grantType(GRANT_TYPE).clientSecret(CLIENT_SECRET);
+    }
+
+    public static void initTokenByProp() {
+        String resp = null;
+        try {
+            resp = API.orgNameAppNameTokenPost(OrgInfo.ORG_NAME, OrgInfo.APP_NAME, BODY);
+        } catch (ApiException e) {
+            logger.error(e.getMessage());
+        }
+        Gson gson = new Gson();
+        Map map = gson.fromJson(resp, Map.class);
+        ACCESS_TOKEN = " Bearer " + map.get("access_token");
+        EXPIREDAT = System.currentTimeMillis() + (Double) map.get("expires_in");
+    }
+
+    /**
+     * get Token from memory
+     *
+     * @return
+     */
+    public static String getAccessToken() {
+        if (ACCESS_TOKEN == null || isExpired()) {
+            initTokenByProp();
+        }
+        return ACCESS_TOKEN;
+    }
+
+    private static Boolean isExpired() {
+        return System.currentTimeMillis() > EXPIREDAT;
+    }
+
+}
+

+ 20 - 0
src/main/java/com/jeeplus/common/annotation/FieldName.java

@@ -0,0 +1,20 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * bean中文名注解
+ */
+@Target(ElementType.METHOD)  
+@Retention(RetentionPolicy.RUNTIME)  
+public @interface FieldName {
+
+	String value();
+	
+}

+ 13 - 0
src/main/java/com/jeeplus/common/beanvalidator/AddGroup.java

@@ -0,0 +1,13 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.beanvalidator;
+
+/**
+ * 添加Bean验证组
+ * @author jeeplus
+ *
+ */
+public interface AddGroup {
+
+}

+ 116 - 0
src/main/java/com/jeeplus/common/beanvalidator/BeanValidators.java

@@ -0,0 +1,116 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.beanvalidator;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * JSR303 Validator(Hibernate Validator)工具类.
+ * 
+ * ConstraintViolation中包含propertyPath, message 和invalidValue等信息.
+ * 提供了各种convert方法,适合不同的i18n需求:
+ * 1. List<String>, String内容为message
+ * 2. List<String>, String内容为propertyPath + separator + message
+ * 3. Map<propertyPath, message>
+ * 
+ * 详情见wiki: https://github.com/springside/springside4/wiki/HibernateValidator
+ * @author calvin
+ * @version 2013-01-15
+ */
+public class BeanValidators {
+
+	/**
+	 * 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.
+	 */
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public static void validateWithException(Validator validator, Object object, Class<?>... groups)
+			throws ConstraintViolationException {
+		Set constraintViolations = validator.validate(object, groups);
+		if (!constraintViolations.isEmpty()) {
+			throw new ConstraintViolationException(constraintViolations);
+		}
+	}
+
+	/**
+	 * 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>中为List<message>.
+	 */
+	public static List<String> extractMessage(ConstraintViolationException e) {
+		return extractMessage(e.getConstraintViolations());
+	}
+
+	/**
+	 * 辅助方法, 转换Set<ConstraintViolation>为List<message>
+	 */
+	@SuppressWarnings("rawtypes")
+	public static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
+		List<String> errorMessages = Lists.newArrayList();
+		for (ConstraintViolation violation : constraintViolations) {
+			errorMessages.add(violation.getMessage());
+		}
+		return errorMessages;
+	}
+
+	/**
+	 * 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为Map<property, message>.
+	 */
+	public static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
+		return extractPropertyAndMessage(e.getConstraintViolations());
+	}
+
+	/**
+	 * 辅助方法, 转换Set<ConstraintViolation>为Map<property, message>.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {
+		Map<String, String> errorMessages = Maps.newHashMap();
+		for (ConstraintViolation violation : constraintViolations) {
+			errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
+		}
+		return errorMessages;
+	}
+
+	/**
+	 * 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath message>.
+	 */
+	public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
+		return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
+	}
+
+	/**
+	 * 辅助方法, 转换Set<ConstraintViolations>为List<propertyPath message>.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
+		return extractPropertyAndMessageAsList(constraintViolations, " ");
+	}
+
+	/**
+	 * 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath +separator+ message>.
+	 */
+	public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
+		return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
+	}
+
+	/**
+	 * 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,
+			String separator) {
+		List<String> errorMessages = Lists.newArrayList();
+		for (ConstraintViolation violation : constraintViolations) {
+			errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
+		}
+		return errorMessages;
+	}
+}

+ 12 - 0
src/main/java/com/jeeplus/common/beanvalidator/DefaultGroup.java

@@ -0,0 +1,12 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.beanvalidator;
+
+/**
+ * 默认Bean验证组
+ * @author jeeplus
+ */
+public interface DefaultGroup {
+
+}

+ 12 - 0
src/main/java/com/jeeplus/common/beanvalidator/EditGroup.java

@@ -0,0 +1,12 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.beanvalidator;
+
+/**
+ * 编辑Bena验证组
+ * @author jeeplus
+ */
+public interface EditGroup {
+
+}

+ 523 - 0
src/main/java/com/jeeplus/common/config/Global.java

@@ -0,0 +1,523 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.config;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Date;
+import java.util.Map;
+import java.util.Properties;
+
+import com.jeeplus.common.utils.DateUtils;
+import org.apache.ibatis.io.Resources;
+import org.springframework.core.io.DefaultResourceLoader;
+
+import com.ckfinder.connector.ServletContextFactory;
+import com.google.common.collect.Maps;
+import com.jeeplus.common.utils.PropertiesLoader;
+import com.jeeplus.common.utils.StringUtils;
+
+/**
+ * 全局配置类
+ * @author jeeplus
+ * @version 2014-06-25
+ */
+public class Global {
+
+	/**
+	 * 当前对象实例
+	 */
+	private static Global global = new Global();
+	
+	/**
+	 * 保存全局属性值
+	 */
+	private static Map<String, String> map = Maps.newHashMap();
+	
+	/**
+	 * 属性文件加载对象
+	 */
+	private static PropertiesLoader loader = new PropertiesLoader("jeeplus.properties");
+
+	/**
+	 * 显示/隐藏
+	 */
+	public static final String SHOW = "1";
+	public static final String HIDE = "0";
+
+	/**
+	 * 是/否
+	 */
+	public static final String YES = "1";
+	public static final String NO = "0";
+	
+	/**
+	 * 对/错
+	 */
+	public static final String TRUE = "true";
+	public static final String FALSE = "false";
+	
+	/**
+	 * 上传文件基础虚拟路径
+	 */
+	public static final String USERFILES_BASE_URL = "/userfiles/";
+	
+	/**
+	 * 获取当前对象实例
+	 */
+	public static Global getInstance() {
+		return global;
+	}
+	
+	/**
+	 * 获取配置
+	 * @see ${fns:getConfig('adminPath')}
+	 */
+	public static String getConfig(String key) {
+		String value = map.get(key);
+		if (value == null){
+			value = loader.getProperty(key);
+			map.put(key, value != null ? value : StringUtils.EMPTY);
+		}
+		return value;
+	}
+	
+	/**
+	 * 获取管理端根路径
+	 */
+	public static String getAdminPath() {
+		return getConfig("adminPath");
+	}
+	/**
+	 */
+	public static String getAliyunUrl() {
+		return getConfig("aliyunDownloadUrl");
+	}
+	/**
+	 */
+	public static String getAliDownloadUrl() {
+		return getConfig("aliyunUrl");
+	}
+	/**
+	 */
+	public static String getEndpoint() {
+		return getConfig("endpoint");
+	}
+	/**
+	 */
+	public static String getAccessKeyId() {
+		return getConfig("accessKeyId");
+	}
+	/**
+	 */
+	public static String getAccessKeySecret() {
+		return getConfig("accessKeySecret");
+	}
+	/**
+	 */
+	public static String getBucketName() {
+		return getConfig("bucketName");
+	}
+	/**
+	 */
+	public static String getAvatarDir() {
+		return getConfig("avatarDir");
+	}
+	/**
+	 */
+	public static String getNotifyDir() {
+		return getConfig("notifyDir");
+	}
+	/**
+	 */
+	public static String getReportDir() {
+		return getConfig("reportDir");
+	}
+	/**
+	 */
+	public static String getRqcode() {
+		return getConfig("rqcode");
+	}
+	/**
+	 */
+	public static String getGoout() {
+		return getConfig("goout");
+	}
+	/**
+	 */
+	public static String getLeave() {
+		return getConfig("leave");
+	}
+	/**
+	 */
+	public static String getOvertimeform() {
+		return getConfig("overtimeform");
+	}
+	/**
+	 */
+	public static String getSealform() {
+		return getConfig("sealform");
+	}
+	/**
+	 *
+	 */
+	public static String getWorkReimbur() {return getConfig("workReimbur");}
+	/**
+	 */
+	public static String getEvection() {
+		return getConfig("evection");
+	}
+	/**
+	 */
+	public static String getOaBuy() {
+		return getConfig("oaBuy");
+	}
+	/**
+	 */
+	public static String getOaAll() {
+		return getConfig("oaAll");
+	}
+	/**
+	 */
+	public static String getIm() {
+		return getConfig("im");
+	}
+	/**
+	 */
+	public static String getNotifyData() {
+		return getConfig("notifyData");
+	}
+    /**
+     */
+    public static String getAppData() {
+        return getConfig("appData");
+    }
+	/**
+	 */
+	public static String getLogo() {
+		return getConfig("logo");
+	}
+    /**
+     */
+	public static String getPhoto() {
+		return getConfig("photo");
+	}
+	/**
+	 *
+	 */
+	public static String getUserEvaluation(){return getConfig("userEvaluation");}
+	/**
+	 */
+	public static String getWorkBidingDocument() {return getConfig("workBidingDocument");}
+	/**
+	 */
+	public static String getWorkEngineeringProject() {return getConfig("workEngineeringProject");}
+
+	/**
+	 */
+	public static String getWorkFullExecute() {return getConfig("workFullExecute");}
+	/**
+	 */
+	public static String getWorkFullMeetingminutes() {return getConfig("workFullMeetingminutes");}
+	/**
+	 */
+	public static String getWorkFullDesignchange(){return getConfig("workFullDesignchange");}
+	/**
+	 */
+	public static String getWorkFullConstructsheet(){return getConfig("workFullConstructsheet");}
+	/**
+	 */
+	public static String getWorkFullProprietorsheet(){return getConfig("workFullProprietorsheet");}
+	/**
+	 */
+	public static String getWorkFullSupervisorsheet(){return getConfig("workFullSupervisorsheet");}
+	/**
+	 */
+	public static String getWorkProjectReport() {return getConfig("workProjectReport");}
+	/**
+	 */
+	public static String getWorkProjectBasis() {return getConfig("workProjectBasis");}
+	/**
+	 */
+	public static String getWorkProjectRemote() {return getConfig("workProjectRemote");}
+	/**
+	 */
+	public static String getWorkProjectSummary() {return getConfig("workProjectSummary");}
+	/**
+	 */
+	public static String getWorkProjectOther() {return getConfig("workProjectOther");}
+	/**
+	 */
+	public static String getWorkVisa() {return getConfig("workVisa");}
+	/**
+	 */
+	public static String getOfficehonor() {return getConfig("Officehonor");}
+	/**
+	 *
+	 */
+	public static String getJobResume() {return getConfig("jobResume");}
+	/**
+	 *
+	 */
+	public static String getSatisfaction() {return getConfig("satisfaction");}
+	/**
+	 *
+	 */
+	public static String getCertificate() {return getConfig("certificate");}
+	/**
+	 *
+	 */
+	public static String getJudgeAttachment() {return getConfig("judgeAttachment");}
+	/**
+	 * 获取前端根路径
+	 */
+	public static String getFrontPath() {
+		return getConfig("frontPath");
+	}
+	
+	/**
+	 * 获取URL后缀
+	 */
+	public static String getUrlSuffix() {
+		return getConfig("urlSuffix");
+	}
+
+	/**
+	 * 获取电子签章文件路径
+	 */
+	public static String getISignature() {
+		return getConfig("iSignature");
+	}
+
+	/**
+	 */
+	public static String getOSSUrl() {
+		return getConfig("oSSUrl");
+	}
+	public static String getContractNumPath() {
+		return getConfig("contract_num_path");
+	}
+	/**
+	 * 是否是演示模式,演示模式下不能修改用户、岗位、密码、菜单、授权
+	 */
+	public static Boolean isDemoMode() {
+		String dm = getConfig("demoMode");
+		return "true".equals(dm) || "1".equals(dm);
+	}
+	
+	/**
+	 * 在修改系统用户和岗位时是否同步到Activiti
+	 */
+	public static Boolean isSynActivitiIndetity() {
+		String dm = getConfig("activiti.isSynActivitiIndetity");
+		return "true".equals(dm) || "1".equals(dm);
+	}
+    
+	/**
+	 * 页面获取常量
+	 * @see ${fns:getConst('YES')}
+	 */
+	public static Object getConst(String field) {
+		try {
+			return Global.class.getField(field).get(null);
+		} catch (Exception e) {
+			// 异常代表无配置,这里什么也不做
+		}
+		return null;
+	}
+
+	/**
+	 * 获取上传文件的根目录
+	 * @return
+	 */
+	public static String getUserfilesBaseDir() {
+		String dir = getConfig("userfiles.basedir");
+		if (StringUtils.isBlank(dir)){
+			try {
+				dir = ServletContextFactory.getServletContext().getRealPath("/");
+			} catch (Exception e) {
+				return "";
+			}
+		}
+		if(!dir.endsWith("/")) {
+			dir += "/";
+		}
+//		System.out.println("userfiles.basedir: " + dir);
+		return dir;
+	}
+	
+    /**
+     * 获取工程路径
+     * @return
+     */
+    public static String getProjectPath(){
+    	// 如果配置了工程路径,则直接返回,否则自动获取。
+		String projectPath = Global.getConfig("projectPath");
+		if (StringUtils.isNotBlank(projectPath)){
+			return projectPath;
+		}
+		try {
+			File file = new DefaultResourceLoader().getResource("").getFile();
+			if (file != null){
+				while(true){
+					File f = new File(file.getPath() + File.separator + "src" + File.separator + "main");
+					if (f == null || f.exists()){
+						break;
+					}
+					if (file.getParentFile() != null){
+						file = file.getParentFile();
+					}else{
+						break;
+					}
+				}
+				projectPath = file.toString();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return projectPath;
+    }
+    
+    /**
+	 * 写入properties信息
+	 * 
+	 * @param key
+	 *            名称
+	 * @param value
+	 *            值
+	 */
+	public static void modifyConfig(String key, String value) {
+		try {
+			// 从输入流中读取属性列表(键和元素对)
+			Properties prop = getProperties();
+			prop.setProperty(key, value);
+			String path = Global.class.getResource("/jeeplus.properties").getPath();
+			FileOutputStream outputFile = new FileOutputStream(path);
+			prop.store(outputFile, "modify");
+			outputFile.close();
+			outputFile.flush();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+	
+	/**
+	 * 返回 Properties
+	 * @param fileName 文件名 (注意:加载的是src下的文件,如果在某个包下.请把包名加上)
+	 * @param 
+	 * @return
+	 */
+	public static Properties getProperties(){
+		Properties prop = new Properties();
+		try {
+			Reader reader = Resources.getResourceAsReader("/jeeplus.properties");
+			prop.load(reader);
+		} catch (Exception e) {
+			return null;
+		}
+		return prop;
+	}
+
+    /**
+     * 获取短信SMS信息
+     */
+    public static String getSmsUserid() {
+        return getConfig("sms_userid");
+    }
+    public static String getSmsAccount() {
+        return getConfig("sms_account");
+    }
+    public static String getSmsPassword() {
+        return getConfig("sms_password");
+    }
+    public static String getSmsMobile() {
+        return getConfig("sms_mobile");
+    }
+    public static String getSmsContent() {
+        return getConfig("sms_content");
+    }
+    public static String getSmsSendTime() {
+        return getConfig("sms_sendTime");
+    }
+    public static String getSmsAction() {
+        return getConfig("sms_action");
+    }
+    public static String getSmsCheckcontent() {
+        return getConfig("sms_checkcontent");
+    }
+
+	/**
+	 * 获取数据库连接信息
+	 */
+	public static String getJdbcUserName() {
+		return getConfig("jdbc.username");
+	}
+	public static String getJdbcPassword() {
+		return getConfig("jdbc.password");
+	}
+	public static String getJdbcUrl() {
+		return getConfig("jdbc.url");
+	}
+	public static String getJdbcDriver() {
+		return getConfig("jdbc.driver");
+	}
+
+	/**
+	 * 获取容联云账户信息
+	 */
+	public static String getRongUserid() {
+		return getConfig("rong_userid");
+	}
+	public static String getRongToken() {
+		return getConfig("rong_token");
+	}
+	//应用id
+	public static String getAppId() {
+		return getConfig("app_id");
+	}
+	//模板id
+	public static String getTemplateId() {
+		return getConfig("template_id");
+	}
+	//短信发送方式(1:旧的 2:容联云通讯)
+	public static String getCodeType() {
+		return getConfig("code_type");
+	}
+
+    public static String getOpenOfficeAddr() {
+        return getConfig("open_office_addr");
+    }
+
+    public static int getOpenOfficePort() {
+        String openOfficePort = getConfig("open_office_port");
+        return StringUtils.isBlank(openOfficePort)?8100:Integer.valueOf(openOfficePort);
+    }
+    public static String getOpenfireServer() {
+        String openOfficePort = getConfig("openfire.server");
+        return StringUtils.isBlank(openOfficePort)?"oa-pre.ssruihua.com":openOfficePort;
+    }
+    public static String getSysNotify() {
+        String sysNotify = getConfig("sys.notify");
+        return StringUtils.isBlank(sysNotify)?"http://cdn.gangwaninfo.com/jeeplus-resource-data/static/sys/notify.png":sysNotify;
+    }
+
+    public static String getStaffBasicFilePath() {
+        return getConfig("staff_basic_file_path");
+    }
+	public static String getDbName() {
+		return getConfig("db.name");
+	}
+	public static String getVersion() {
+		return getConfig("app_version");
+	}
+	public static String getTestVersion() {
+		return getConfig("app_version_test");
+	}
+
+    public static String getProjectTemplatePath() {
+        return getConfig("project.plan.template.path");
+    }
+}

+ 35 - 0
src/main/java/com/jeeplus/common/filter/JeeplusMultipartResolver.java

@@ -0,0 +1,35 @@
+package com.jeeplus.common.filter;
+
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JeeplusMultipartResolver extends CommonsMultipartResolver {
+
+    /**
+     * 不使用CommonsMultipartResolver的url
+     */
+    private List<String> excludeUrls = new ArrayList<String>();
+
+    @Override
+    public boolean isMultipart(HttpServletRequest request) {
+        if (excludeUrls!=null&&!excludeUrls.isEmpty()){
+            for (String s : excludeUrls) {
+                if (request.getRequestURI().contains(s)){
+                    return false;
+                }
+            }
+        }
+        return super.isMultipart(request);
+    }
+
+    public List<String> getExcludeUrls() {
+        return excludeUrls;
+    }
+
+    public void setExcludeUrls(List<String> excludeUrls) {
+        this.excludeUrls = excludeUrls;
+    }
+}

+ 23 - 0
src/main/java/com/jeeplus/common/filter/PageCachingFilter.java

@@ -0,0 +1,23 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.filter;
+
+import com.jeeplus.common.utils.CacheUtils;
+
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;
+
+/**
+ * 页面高速缓存过滤器
+ * @author jeeplus
+ * @version 2013-8-5
+ */
+public class PageCachingFilter extends SimplePageCachingFilter {
+
+	@Override
+	protected CacheManager getCacheManager() {
+		return CacheUtils.getCacheManager();
+	}
+	
+}

+ 73 - 0
src/main/java/com/jeeplus/common/json/AjaxJson.java

@@ -0,0 +1,73 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.json;
+
+import java.util.LinkedHashMap;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.mapper.JsonMapper;
+
+
+/**
+ * $.ajax后需要接受的JSON
+ * 
+ * @author
+ * 
+ */
+public class AjaxJson <T>{
+
+	private boolean success = true;// 是否成功
+	private String errorCode = "-1";//错误代码
+	private String msg = "操作成功";// 提示信息
+	private LinkedHashMap<String, Object> body = new LinkedHashMap();//封装json的map
+	
+	public LinkedHashMap<String, Object> getBody() {
+		return body;
+	}
+
+	public void setBody(LinkedHashMap<String, Object> body) {
+		this.body = body;
+	}
+
+	public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key
+		body.put(key, value);
+	}
+	
+	public void remove(String key){
+		body.remove(key);
+	}
+	
+	
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg
+		this.msg = msg;
+	}
+
+
+	public boolean isSuccess() {
+		return success;
+	}
+
+	public void setSuccess(boolean success) {
+		this.success = success;
+	}
+	
+	@JsonIgnore//返回对象时忽略此属性
+	public String getJsonStr() {//返回json字符串数组,将访问msg和key的方式统一化,都使用data.key的方式直接访问。
+
+		String json = JsonMapper.getInstance().toJson(this);
+		return json;
+	}
+
+	public void setErrorCode(String errorCode) {
+		this.errorCode = errorCode;
+	}
+
+	public String getErrorCode() {
+		return errorCode;
+	}
+}

+ 90 - 0
src/main/java/com/jeeplus/common/json/ComNameJson.java

@@ -0,0 +1,90 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.mapper.JsonMapper;
+
+import java.util.LinkedHashMap;
+
+
+/**
+ * $.ajax后需要接受的JSON
+ * 
+ * @author
+ * 
+ */
+public class ComNameJson {
+
+	private boolean success = true;// 是否成功
+	private String errorCode = "-1";//错误代码
+	private String msg = "操作成功";// 提示信息
+	private String comName;//默认公司AjaxJsonAjaxJson
+
+	public String getComName() {
+		return comName;
+	}
+
+	public void setComName(String comName) {
+		this.comName = comName;
+	}
+
+	private LinkedHashMap<String, Object> body = new LinkedHashMap();//封装json的map
+
+	public LinkedHashMap<String, Object> getBody() {
+		return body;
+	}
+
+	public void setBody(LinkedHashMap<String, Object> body) {
+		this.body = body;
+	}
+
+	public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key
+		body.put(key, value);
+	}
+	
+	public void remove(String key){
+		body.remove(key);
+	}
+	
+	
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg
+		this.msg = msg;
+	}
+
+
+	public boolean isSuccess() {
+		return success;
+	}
+
+	public void setSuccess(boolean success) {
+		this.success = success;
+	}
+	
+	@JsonIgnore//返回对象时忽略此属性
+	public String getJsonStr() {//返回json字符串数组,将访问msg和key的方式统一化,都使用data.key的方式直接访问。
+
+		String json = JsonMapper.getInstance().toJson(this);
+		return json;
+	}
+
+	@Override
+	public String toString() {
+		return "ComNameJson{" +
+				"comName='" + comName + '\'' +
+				'}';
+	}
+
+	public void setErrorCode(String errorCode) {
+		this.errorCode = errorCode;
+	}
+
+	public String getErrorCode() {
+		return errorCode;
+	}
+}

+ 82 - 0
src/main/java/com/jeeplus/common/json/IMAjaxJson.java

@@ -0,0 +1,82 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.mapper.JsonMapper;
+
+import java.util.LinkedHashMap;
+
+
+/**
+ * $.ajax后需要接受的JSON
+ * 
+ * @author
+ * 
+ */
+public class IMAjaxJson {
+
+	private boolean success = true;// 是否成功
+	private String errorCode = "-1";//错误代码
+	private String msg = "操作成功";// 提示信息
+	private String useType = "_online_all_status_";// 全员推送
+	private LinkedHashMap<String, Object> body = new LinkedHashMap();//封装json的map
+	
+	public LinkedHashMap<String, Object> getBody() {
+		return body;
+	}
+
+	public void setBody(LinkedHashMap<String, Object> body) {
+		this.body = body;
+	}
+
+	public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key
+		body.put(key, value);
+	}
+	
+	public void remove(String key){
+		body.remove(key);
+	}
+	
+	
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg
+		this.msg = msg;
+	}
+
+
+	public boolean isSuccess() {
+		return success;
+	}
+
+	public void setSuccess(boolean success) {
+		this.success = success;
+	}
+
+	public String getUseType() {
+		return useType;
+	}
+
+	public void setUseType(String useType) {
+		this.useType = useType;
+	}
+
+	@JsonIgnore//返回对象时忽略此属性
+	public String getJsonStr() {//返回json字符串数组,将访问msg和key的方式统一化,都使用data.key的方式直接访问。
+
+		String json = JsonMapper.getInstance().toJson(this);
+		return json;
+	}
+
+	public void setErrorCode(String errorCode) {
+		this.errorCode = errorCode;
+	}
+
+	public String getErrorCode() {
+		return errorCode;
+	}
+}

+ 28 - 0
src/main/java/com/jeeplus/common/json/PrintJSON.java

@@ -0,0 +1,28 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.json;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletResponse;
+
+public class PrintJSON {
+	
+	
+	public static void write(HttpServletResponse response,String content) {
+		response.reset();
+		response.setContentType("application/json");
+		response.setHeader("Cache-Control", "no-store");
+		response.setCharacterEncoding("UTF-8");
+		try {
+			PrintWriter pw=response.getWriter();
+			pw.write(content);
+			pw.flush();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 20 - 0
src/main/java/com/jeeplus/common/mail/MailAuthenticator.java

@@ -0,0 +1,20 @@
+package com.jeeplus.common.mail;
+/**   
+ *  
+ */ 
+import javax.mail.*;   
+
+public class MailAuthenticator extends Authenticator{   
+    String userName=null;   
+    String password=null;   
+        
+    public MailAuthenticator(){   
+    }   
+    public MailAuthenticator(String username, String password) {    
+        this.userName = username;    
+        this.password = password;    
+    }    
+    protected PasswordAuthentication getPasswordAuthentication(){   
+        return new PasswordAuthentication(userName, password);   
+    }   
+}   

+ 96 - 0
src/main/java/com/jeeplus/common/mail/MailBody.java

@@ -0,0 +1,96 @@
+package com.jeeplus.common.mail;   
+/**   
+ *发送邮件需要使用的基本信息 
+ *  
+ */    
+import java.util.Properties;    
+public class MailBody {    
+    // 发送邮件的服务器的IP和端口    
+    private String mailServerHost;    
+    private String mailServerPort = "25";    
+    // 邮件发送者的地址    
+    private String fromAddress;    
+    // 邮件接收者的地址    
+    private String toAddress;    
+    // 登陆邮件发送服务器的用户名和密码    
+    private String userName;    
+    private String password;    
+    // 是否需要身份验证    
+    private boolean validate = false;    
+    // 邮件主题    
+    private String subject;    
+    // 邮件的文本内容    
+    private String content;    
+    // 邮件附件的文件名    
+    private String[] attachFileNames;      
+    /**   
+      * 获得邮件会话属性   
+      */    
+    public Properties getProperties(){    
+      Properties p = new Properties();    
+      p.put("mail.smtp.host", this.mailServerHost);    
+      p.put("mail.smtp.port", this.mailServerPort);    
+      p.put("mail.smtp.auth", validate ? "true" : "false");    
+      return p;    
+    }    
+    public String getMailServerHost() {    
+      return mailServerHost;    
+    }    
+    public void setMailServerHost(String mailServerHost) {    
+      this.mailServerHost = mailServerHost;    
+    }   
+    public String getMailServerPort() {    
+      return mailServerPort;    
+    }   
+    public void setMailServerPort(String mailServerPort) {    
+      this.mailServerPort = mailServerPort;    
+    }   
+    public boolean isValidate() {    
+      return validate;    
+    }   
+    public void setValidate(boolean validate) {    
+      this.validate = validate;    
+    }   
+    public String[] getAttachFileNames() {    
+      return attachFileNames;    
+    }   
+    public void setAttachFileNames(String[] fileNames) {    
+      this.attachFileNames = fileNames;    
+    }   
+    public String getFromAddress() {    
+      return fromAddress;    
+    }    
+    public void setFromAddress(String fromAddress) {    
+      this.fromAddress = fromAddress;    
+    }   
+    public String getPassword() {    
+      return password;    
+    }   
+    public void setPassword(String password) {    
+      this.password = password;    
+    }   
+    public String getToAddress() {    
+      return toAddress;    
+    }    
+    public void setToAddress(String toAddress) {    
+      this.toAddress = toAddress;    
+    }    
+    public String getUserName() {    
+      return userName;    
+    }   
+    public void setUserName(String userName) {    
+      this.userName = userName;    
+    }   
+    public String getSubject() {    
+      return subject;    
+    }   
+    public void setSubject(String subject) {    
+      this.subject = subject;    
+    }   
+    public String getContent() {    
+      return content;    
+    }   
+    public void setContent(String textContent) {    
+      this.content = textContent;    
+    }    
+}   

+ 154 - 0
src/main/java/com/jeeplus/common/mail/MailSendUtils.java

@@ -0,0 +1,154 @@
+package com.jeeplus.common.mail;
+/**   
+ * 简单邮件(不带附件的邮件)发送器   
+ */ 
+import java.util.Date;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+    
+public class MailSendUtils   {    
+/**   
+  * 以文本格式发送邮件   
+  * @param mailInfo 待发送的邮件的信息   
+  */    
+    public boolean sendTextMail(MailBody mailInfo) throws Exception{    
+      // 判断是否需要身份认证    
+      MailAuthenticator authenticator = null;    
+      Properties pro = mailInfo.getProperties();   
+      if (mailInfo.isValidate()) {    
+      // 如果需要身份认证,则创建一个密码验证器    
+        authenticator = new MailAuthenticator(mailInfo.getUserName(), mailInfo.getPassword());    
+      }   
+      // 根据邮件会话属性和密码验证器构造一个发送邮件的session    
+      Session sendMailSession = Session.getDefaultInstance(pro,authenticator); 
+     // logBefore(logger, "构造一个发送邮件的session");
+      
+      // 根据session创建一个邮件消息    
+      Message mailMessage = new MimeMessage(sendMailSession);    
+      // 创建邮件发送者地址    
+      Address from = new InternetAddress(mailInfo.getFromAddress());    
+      // 设置邮件消息的发送者    
+      mailMessage.setFrom(from);    
+      // 创建邮件的接收者地址,并设置到邮件消息中    
+      Address to = new InternetAddress(mailInfo.getToAddress());    
+      mailMessage.setRecipient(Message.RecipientType.TO,to);    
+      // 设置邮件消息的主题    
+      mailMessage.setSubject(mailInfo.getSubject());    
+      // 设置邮件消息发送的时间    
+      mailMessage.setSentDate(new Date());    
+      // 设置邮件消息的主要内容    
+      String mailContent = mailInfo.getContent();    
+      mailMessage.setText(mailContent);    
+      // 发送邮件    
+      Transport.send(mailMessage); 
+      System.out.println("发送成功!");
+      return true;    
+    }    
+       
+    /**   
+      * 以HTML格式发送邮件   
+      * @param mailInfo 待发送的邮件信息   
+      */    
+    public  boolean sendHtmlMail(MailBody mailInfo) throws Exception{    
+      // 判断是否需要身份认证    
+      MailAuthenticator authenticator = null;   
+      Properties pro = mailInfo.getProperties();   
+      //如果需要身份认证,则创建一个密码验证器     
+      if (mailInfo.isValidate()) {    
+        authenticator = new MailAuthenticator(mailInfo.getUserName(), mailInfo.getPassword());   
+      }    
+      // 根据邮件会话属性和密码验证器构造一个发送邮件的session    
+      Session sendMailSession = Session.getDefaultInstance(pro,authenticator);    
+        
+      // 根据session创建一个邮件消息    
+      Message mailMessage = new MimeMessage(sendMailSession);    
+      // 创建邮件发送者地址    
+      Address from = new InternetAddress(mailInfo.getFromAddress());    
+      // 设置邮件消息的发送者    
+      mailMessage.setFrom(from);    
+      // 创建邮件的接收者地址,并设置到邮件消息中    
+      Address to = new InternetAddress(mailInfo.getToAddress());    
+      // Message.RecipientType.TO属性表示接收者的类型为TO    
+      mailMessage.setRecipient(Message.RecipientType.TO,to);    
+      // 设置邮件消息的主题    
+      mailMessage.setSubject(mailInfo.getSubject());    
+      // 设置邮件消息发送的时间    
+      mailMessage.setSentDate(new Date());    
+      // MiniMultipart类是一个容器类,包含MimeBodyPart类型的对象    
+      Multipart mainPart = new MimeMultipart();    
+      // 创建一个包含HTML内容的MimeBodyPart    
+      BodyPart html = new MimeBodyPart();    
+      // 设置HTML内容    
+      html.setContent(mailInfo.getContent(), "text/html; charset=utf-8");    
+      mainPart.addBodyPart(html);    
+      // 将MiniMultipart对象设置为邮件内容    
+      mailMessage.setContent(mainPart);    
+      // 发送邮件    
+      Transport.send(mailMessage);    
+      return true;    
+    }
+
+	/**
+	 * @param SMTP
+	 *            邮件服务器
+	 * @param PORT
+	 *            端口
+	 * @param EMAIL
+	 *            本邮箱账号
+	 * @param PAW
+	 *            本邮箱密码
+	 * @param toEMAIL
+	 *            对方箱账号
+	 * @param TITLE
+	 *            标题
+	 * @param CONTENT
+	 *            内容
+	 * @param TYPE
+	 *            1:文本格式;2:HTML格式
+	 */
+	public static boolean sendEmail(String SMTP, String PORT, String EMAIL,
+			String PAW, String toEMAIL, String TITLE, String CONTENT,
+			String TYPE) {
+
+		// 这个类主要是设置邮件
+		MailBody mailInfo = new MailBody();
+
+		mailInfo.setMailServerHost(SMTP);
+		mailInfo.setMailServerPort(PORT);
+		mailInfo.setValidate(true);
+		mailInfo.setUserName(EMAIL);
+		mailInfo.setPassword(PAW);
+		mailInfo.setFromAddress(EMAIL);
+		mailInfo.setToAddress(toEMAIL);
+		mailInfo.setSubject(TITLE);
+		mailInfo.setContent(CONTENT);
+		// 这个类主要来发送邮件
+
+		MailSendUtils sms = new MailSendUtils();
+		try {
+			if ("1".equals(TYPE)) {
+				return sms.sendTextMail(mailInfo);
+			} else {
+				return sms.sendHtmlMail(mailInfo);
+			}
+		} catch (Exception e) {
+			return false;
+		}
+
+	}
+    
+    
+
+    
+}   

+ 57 - 0
src/main/java/com/jeeplus/common/mapper/BeanMapper.java

@@ -0,0 +1,57 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.dozer.DozerBeanMapper;
+
+import com.google.common.collect.Lists;
+
+/**
+ * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现:
+ *  
+ * 1. 持有Mapper的单例. 
+ * 2. 返回值类型转换.
+ * 3. 批量转换Collection中的所有对象.
+ * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数.
+ * 
+ * @author calvin
+ * @version 2013-01-15
+ */
+public class BeanMapper {
+
+	/**
+	 * 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
+	 */
+	private static DozerBeanMapper dozer = new DozerBeanMapper();
+
+	/**
+	 * 基于Dozer转换对象的类型.
+	 */
+	public static <T> T map(Object source, Class<T> destinationClass) {
+		return dozer.map(source, destinationClass);
+	}
+
+	/**
+	 * 基于Dozer转换Collection中对象的类型.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass) {
+		List<T> destinationList = Lists.newArrayList();
+		for (Object sourceObject : sourceList) {
+			T destinationObject = dozer.map(sourceObject, destinationClass);
+			destinationList.add(destinationObject);
+		}
+		return destinationList;
+	}
+
+	/**
+	 * 基于Dozer将对象A的值拷贝到对象B中.
+	 */
+	public static void copy(Object source, Object destinationObject) {
+		dozer.map(source, destinationObject);
+	}
+}

+ 78 - 0
src/main/java/com/jeeplus/common/mapper/ConvertBlobTypeHandler.java

@@ -0,0 +1,78 @@
+package com.jeeplus.common.mapper;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+
+/**
+ * className:ConvertBlobTypeHandler
+ * 
+ * 自定义typehandler,解决mybatis存储blob字段后,出现乱码的问题
+ * 配置mapper.xml:
+ * <result  typeHandler="cn.ffcs.drive.common.util.ConvertBlobTypeHandler"/>
+ * 
+ * @author lgf
+ * @version 1.0.0
+ * @date 2016-05-05 11:15:23
+ * 
+ */
+@MappedJdbcTypes(JdbcType.BLOB)
+public class ConvertBlobTypeHandler extends BaseTypeHandler<String> {//指定字符集  
+    private static final String DEFAULT_CHARSET = "utf-8";  
+
+@Override  
+public void setNonNullParameter(PreparedStatement ps, int i,  
+        String parameter, JdbcType jdbcType) throws SQLException {  
+    ByteArrayInputStream bis;  
+    try {  
+        bis = new ByteArrayInputStream(parameter.getBytes(DEFAULT_CHARSET));  
+    } catch (UnsupportedEncodingException e) {  
+        throw new RuntimeException("Blob Encoding Error!");  
+    }     
+    ps.setBinaryStream(i, bis, parameter.length());  
+}  
+
+@Override  
+public String getNullableResult(ResultSet rs, String columnName)  
+        throws SQLException {  
+    Blob blob = rs.getBlob(columnName);  
+    byte[] returnValue = null;  
+    if (null != blob) {  
+        returnValue = blob.getBytes(1, (int) blob.length());  
+    }  
+    try {  
+        return new String(returnValue, DEFAULT_CHARSET);  
+    } catch (UnsupportedEncodingException e) {  
+        throw new RuntimeException("Blob Encoding Error!");  
+    }  
+}  
+
+@Override  
+public String getNullableResult(CallableStatement cs, int columnIndex)  
+        throws SQLException {  
+    Blob blob = cs.getBlob(columnIndex);  
+    byte[] returnValue = null;  
+    if (null != blob) {  
+        returnValue = blob.getBytes(1, (int) blob.length());  
+    }  
+    try {  
+        return new String(returnValue, DEFAULT_CHARSET);  
+    } catch (UnsupportedEncodingException e) {  
+        throw new RuntimeException("Blob Encoding Error!");  
+    }  
+}
+
+@Override
+public String getNullableResult(ResultSet arg0, int arg1) throws SQLException {
+	// TODO Auto-generated method stub
+	return null;
+}  } 

+ 169 - 0
src/main/java/com/jeeplus/common/mapper/JaxbMapper.java

@@ -0,0 +1,169 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.mapper;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAnyElement;
+import javax.xml.namespace.QName;
+
+import org.springframework.http.converter.HttpMessageConversionException;
+import org.springframework.util.Assert;
+
+import com.jeeplus.common.utils.Exceptions;
+import com.jeeplus.common.utils.Reflections;
+import com.jeeplus.common.utils.StringUtils;
+
+/**
+ * 使用Jaxb2.0实现XML<->Java Object的Mapper.
+ * 
+ * 在创建时需要设定所有需要序列化的Root对象的Class.
+ * 特别支持Root对象是Collection的情形.
+ * 
+ * @author calvin
+ * @version 2013-01-15
+ */
+@SuppressWarnings("rawtypes")
+public class JaxbMapper {
+
+	private static ConcurrentMap<Class, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class, JAXBContext>();
+
+	/**
+	 * Java Object->Xml without encoding.
+	 */
+	public static String toXml(Object root) {
+		Class clazz = Reflections.getUserClass(root);
+		return toXml(root, clazz, null);
+	}
+
+	/**
+	 * Java Object->Xml with encoding.
+	 */
+	public static String toXml(Object root, String encoding) {
+		Class clazz = Reflections.getUserClass(root);
+		return toXml(root, clazz, encoding);
+	}
+
+	/**
+	 * Java Object->Xml with encoding.
+	 */
+	public static String toXml(Object root, Class clazz, String encoding) {
+		try {
+			StringWriter writer = new StringWriter();
+			createMarshaller(clazz, encoding).marshal(root, writer);
+			return writer.toString();
+		} catch (JAXBException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * Java Collection->Xml without encoding, 特别支持Root Element是Collection的情形.
+	 */
+	public static String toXml(Collection<?> root, String rootName, Class clazz) {
+		return toXml(root, rootName, clazz, null);
+	}
+
+	/**
+	 * Java Collection->Xml with encoding, 特别支持Root Element是Collection的情形.
+	 */
+	public static String toXml(Collection<?> root, String rootName, Class clazz, String encoding) {
+		try {
+			CollectionWrapper wrapper = new CollectionWrapper();
+			wrapper.collection = root;
+
+			JAXBElement<CollectionWrapper> wrapperElement = new JAXBElement<CollectionWrapper>(new QName(rootName),
+					CollectionWrapper.class, wrapper);
+
+			StringWriter writer = new StringWriter();
+			createMarshaller(clazz, encoding).marshal(wrapperElement, writer);
+
+			return writer.toString();
+		} catch (JAXBException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * Xml->Java Object.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> T fromXml(String xml, Class<T> clazz) {
+		try {
+			StringReader reader = new StringReader(xml);
+			return (T) createUnmarshaller(clazz).unmarshal(reader);
+		} catch (JAXBException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 创建Marshaller并设定encoding(可为null).
+	 * 线程不安全,需要每次创建或pooling。
+	 */
+	public static Marshaller createMarshaller(Class clazz, String encoding) {
+		try {
+			JAXBContext jaxbContext = getJaxbContext(clazz);
+
+			Marshaller marshaller = jaxbContext.createMarshaller();
+
+			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+
+			if (StringUtils.isNotBlank(encoding)) {
+				marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
+			}
+
+			return marshaller;
+		} catch (JAXBException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 创建UnMarshaller.
+	 * 线程不安全,需要每次创建或pooling。
+	 */
+	public static Unmarshaller createUnmarshaller(Class clazz) {
+		try {
+			JAXBContext jaxbContext = getJaxbContext(clazz);
+			return jaxbContext.createUnmarshaller();
+		} catch (JAXBException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	protected static JAXBContext getJaxbContext(Class clazz) {
+		Assert.notNull(clazz, "'clazz' must not be null");
+		JAXBContext jaxbContext = jaxbContexts.get(clazz);
+		if (jaxbContext == null) {
+			try {
+				jaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class);
+				jaxbContexts.putIfAbsent(clazz, jaxbContext);
+			} catch (JAXBException ex) {
+				throw new HttpMessageConversionException("Could not instantiate JAXBContext for class [" + clazz
+						+ "]: " + ex.getMessage(), ex);
+			}
+		}
+		return jaxbContext;
+	}
+
+	/**
+	 * 封装Root Element 是 Collection的情况.
+	 */
+	public static class CollectionWrapper {
+
+		@XmlAnyElement
+		protected Collection<?> collection;
+	}
+	
+}

+ 261 - 0
src/main/java/com/jeeplus/common/mapper/JsonMapper.java

@@ -0,0 +1,261 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.mapper;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonParser.Feature;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * 简单封装Jackson,实现JSON String<->Java Object的Mapper.
+ * 封装不同的输出风格, 使用不同的builder函数创建实例.
+ * @author jeeplus
+ * @version 2013-11-15
+ */
+public class JsonMapper extends ObjectMapper {
+
+	private static final long serialVersionUID = 1L;
+
+	private static Logger logger = LoggerFactory.getLogger(JsonMapper.class);
+
+	private static JsonMapper mapper;
+
+	public JsonMapper() {
+		//this(Include.NON_EMPTY);
+	}
+
+	public JsonMapper(Include include) {
+		// 设置输出时包含属性的风格
+		if (include != null) {
+			this.setSerializationInclusion(include);
+		}
+		// 允许单引号、允许不带引号的字段名称
+		this.enableSimple();
+		// 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
+		this.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+        // 空值处理为空串
+		this.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>(){
+			@Override
+			public void serialize(Object value, JsonGenerator jgen,
+					SerializerProvider provider) throws IOException,
+					JsonProcessingException {
+				jgen.writeString("");
+			}
+        });
+		// 进行HTML解码。
+		this.registerModule(new SimpleModule().addSerializer(String.class, new JsonSerializer<String>(){
+			@Override
+			public void serialize(String value, JsonGenerator jgen,
+					SerializerProvider provider) throws IOException,
+					JsonProcessingException {
+				jgen.writeString(StringEscapeUtils.unescapeHtml4(value));
+			}
+        }));
+		// 设置时区
+		this.setTimeZone(TimeZone.getDefault());//getTimeZone("GMT+8:00")
+	}
+
+	/**
+	 * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用.
+	 */
+	public static JsonMapper getInstance() {
+		if (mapper == null){
+			mapper = new JsonMapper().enableSimple();
+		}
+		return mapper;
+	}
+
+	/**
+	 * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式,建议在内部接口中使用。
+	 */
+	public static JsonMapper nonDefaultMapper() {
+		if (mapper == null){
+			mapper = new JsonMapper(Include.NON_DEFAULT);
+		}
+		return mapper;
+	}
+	
+	/**
+	 * Object可以是POJO,也可以是Collection或数组。
+	 * 如果对象为Null, 返回"null".
+	 * 如果集合为空集合, 返回"[]".
+	 */
+	public String toJson(Object object) {
+		try {
+			return this.writeValueAsString(object);
+		} catch (IOException e) {
+			logger.warn("write to json string error:" + object, e);
+			return null;
+		}
+	}
+
+	/**
+	 * 反序列化POJO或简单Collection如List<String>.
+	 * 
+	 * 如果JSON字符串为Null或"null"字符串, 返回Null.
+	 * 如果JSON字符串为"[]", 返回空集合.
+	 * 
+	 * 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String,JavaType)
+	 * @see #fromJson(String, JavaType)
+	 */
+	public <T> T fromJson(String jsonString, Class<T> clazz) {
+		if (StringUtils.isEmpty(jsonString)) {
+			return null;
+		}
+		try {
+			return this.readValue(jsonString, clazz);
+		} catch (IOException e) {
+			logger.warn("parse json string error:" + jsonString, e);
+			return null;
+		}
+	}
+
+	/**
+	 * 反序列化复杂Collection如List<Bean>, 先使用函數createCollectionType构造类型,然后调用本函数.
+	 * @see #createCollectionType(Class, Class...)
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T fromJson(String jsonString, JavaType javaType) {
+		if (StringUtils.isEmpty(jsonString)) {
+			return null;
+		}
+		try {
+			return (T) this.readValue(jsonString, javaType);
+		} catch (IOException e) {
+			logger.warn("parse json string error:" + jsonString, e);
+			return null;
+		}
+	}
+
+	/**
+	 * 構造泛型的Collection Type如:
+	 * ArrayList<MyBean>, 则调用constructCollectionType(ArrayList.class,MyBean.class)
+	 * HashMap<String,MyBean>, 则调用(HashMap.class,String.class, MyBean.class)
+	 */
+	public JavaType createCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
+		return this.getTypeFactory().constructParametricType(collectionClass, elementClasses);
+	}
+
+	/**
+	 * 當JSON裡只含有Bean的部分屬性時,更新一個已存在Bean,只覆蓋該部分的屬性.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T update(String jsonString, T object) {
+		try {
+			return (T) this.readerForUpdating(object).readValue(jsonString);
+		} catch (JsonProcessingException e) {
+			logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
+		} catch (IOException e) {
+			logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e);
+		}
+		return null;
+	}
+
+	/**
+	 * 輸出JSONP格式數據.
+	 */
+	public String toJsonP(String functionName, Object object) {
+		return toJson(new JSONPObject(functionName, object));
+	}
+
+	/**
+	 * 設定是否使用Enum的toString函數來讀寫Enum,
+	 * 為False時時使用Enum的name()函數來讀寫Enum, 默認為False.
+	 * 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.
+	 */
+	public JsonMapper enableEnumUseToString() {
+		this.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+		this.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+		return this;
+	}
+
+	/**
+	 * 支持使用Jaxb的Annotation,使得POJO上的annotation不用与Jackson耦合。
+	 * 默认会先查找jaxb的annotation,如果找不到再找jackson的。
+	 */
+	public JsonMapper enableJaxbAnnotation() {
+		JaxbAnnotationModule module = new JaxbAnnotationModule();
+		this.registerModule(module);
+		return this;
+	}
+
+	/**
+	 * 允许单引号
+	 * 允许不带引号的字段名称
+	 */
+	public JsonMapper enableSimple() {
+		this.configure(Feature.ALLOW_SINGLE_QUOTES, true);
+		this.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+		return this;
+	}
+	
+	/**
+	 * 取出Mapper做进一步的设置或使用其他序列化API.
+	 */
+	public ObjectMapper getMapper() {
+		return this;
+	}
+
+	/**
+	 * 对象转换为JSON字符串
+	 * @param object
+	 * @return
+	 */
+	public static String toJsonString(Object object){
+		return JsonMapper.getInstance().toJson(object);
+	}
+	
+	/**
+	 * JSON字符串转换为对象
+	 * @param jsonString
+	 * @param clazz
+	 * @return
+	 */
+	public static Object fromJsonString(String jsonString, Class<?> clazz){
+		return JsonMapper.getInstance().fromJson(jsonString, clazz);
+	}
+	
+	/**
+	 * 测试
+	 */
+	public static void main(String[] args) {
+		List<Map<String, Object>> list = Lists.newArrayList();
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("id", 1);
+		map.put("pId", -1);
+		map.put("name", "根节点");
+		list.add(map);
+		map = Maps.newHashMap();
+		map.put("id", 2);
+		map.put("pId", 1);
+		map.put("name", "你好");
+		map.put("open", true);
+		list.add(map);
+		String json = JsonMapper.getInstance().toJson(list);
+		System.out.println(json);
+	}
+	
+}

+ 66 - 0
src/main/java/com/jeeplus/common/mapper/adapters/MapConvertor.java

@@ -0,0 +1,66 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.mapper.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name = "MapConvertor")  
+@XmlAccessorType(XmlAccessType.FIELD)  
+public class MapConvertor {
+	
+    private List<MapEntry> entries = new ArrayList<MapEntry>();  
+  
+    public void addEntry(MapEntry entry) {  
+        entries.add(entry);  
+    }  
+  
+    public List<MapEntry> getEntries() {  
+        return entries;  
+    }  
+      
+    public static class MapEntry {  
+  
+        private String key;  
+  
+        private Object value;  
+          
+        public MapEntry() {  
+            super();  
+        }  
+  
+        public MapEntry(Map.Entry<String, Object> entry) {  
+            super();  
+            this.key = entry.getKey();  
+            this.value = entry.getValue();  
+        }  
+  
+        public MapEntry(String key, Object value) {  
+            super();  
+            this.key = key;  
+            this.value = value;  
+        }  
+  
+        public String getKey() {  
+            return key;  
+        }  
+  
+        public void setKey(String key) {  
+            this.key = key;  
+        }  
+  
+        public Object getValue() {  
+            return value;  
+        }  
+  
+        public void setValue(Object value) {  
+            this.value = value;  
+        }  
+    }  
+}  

+ 48 - 0
src/main/java/com/jeeplus/common/oss/GetObjectProgressListener.java

@@ -0,0 +1,48 @@
+package com.jeeplus.common.oss;
+
+import com.aliyun.oss.event.ProgressEvent;
+import com.aliyun.oss.event.ProgressEventType;
+import com.aliyun.oss.event.ProgressListener;
+
+public class GetObjectProgressListener implements ProgressListener {
+    private long bytesRead = 0;
+    private long totalBytes = -1;
+    private boolean succeed = false;
+    @Override
+    public void progressChanged(ProgressEvent progressEvent) {
+        long bytes = progressEvent.getBytes();
+        ProgressEventType eventType = progressEvent.getEventType();
+        switch (eventType) {
+            case TRANSFER_STARTED_EVENT:
+                System.out.println("Start to download......");
+                break;
+            case RESPONSE_CONTENT_LENGTH_EVENT:
+                this.totalBytes = bytes;
+                System.out.println(this.totalBytes + " bytes in total will be downloaded to a local file");
+                break;
+            case RESPONSE_BYTE_TRANSFER_EVENT:
+                this.bytesRead += bytes;
+                if (this.totalBytes != -1) {
+                    int percent = (int)(this.bytesRead * 100.0 / this.totalBytes);
+                    System.out.println(bytes + " bytes have been read at this time, download progress: " +
+                            percent + "%(" + this.bytesRead + "/" + this.totalBytes + ")");
+                } else {
+                    System.out.println(bytes + " bytes have been read at this time, download ratio: unknown" +
+                            "(" + this.bytesRead + "/...)");
+                }
+                break;
+            case TRANSFER_COMPLETED_EVENT:
+                this.succeed = true;
+                System.out.println("Succeed to download, " + this.bytesRead + " bytes have been transferred in total");
+                break;
+            case TRANSFER_FAILED_EVENT:
+                System.out.println("Failed to download, " + this.bytesRead + " bytes have been transferred");
+                break;
+            default:
+                break;
+        }
+    }
+    public boolean isSucceed() {
+        return succeed;
+    }
+}

+ 541 - 0
src/main/java/com/jeeplus/common/oss/MultipartUpload.java

@@ -0,0 +1,541 @@
+package com.jeeplus.common.oss;
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.*;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.workpartetag.entity.WorkPartEtag;
+import com.jeeplus.modules.workpartetag.service.WorkPartEtagService;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author
+ * @create 2016-10-31 19:13
+ * @others 阿里云OSS测试
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/sys/upload")
+public class MultipartUpload extends OSSConfig{
+
+    //日志记录
+    private static final Logger LOG = LoggerFactory.getLogger(MultipartUpload.class);
+
+    //获取配置文件
+    //ResourceBundle resourceBundle = ResourceBundle.getBundle("config");
+
+    //默认5个线程
+    private static ExecutorService executorService = Executors.newFixedThreadPool(5);
+
+    //存储OSS返回来的partETags,是一个线程同步的arraylist,靠这个去查询
+    private static List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());
+
+    //如果上传完毕,uploadId则失效,这里将client设为null,则进度查询会显示完毕(不是必须)
+    private static OSSClient client = null;
+
+    // 创建一个要存放的Object的名称,使用文件名怕重复,直接改名+存入数据库
+    private static String key;
+
+    //默认一个静态的字段贮备,查询进度需要
+    private static String uploadId;
+
+    //默认一个静态的字段储备文件分割的数量,进度查询需要
+    private static Integer partCount;
+
+    //标识符,false情况下说名还没有开始,true则前端可以开始查询进度(不是必须)
+    private boolean progress = false;
+
+    // 每片最少5MB,这里写死了,最好用配置文件随时修改
+    final long partSize = 5 * 1024 * 1024L;
+
+    @Autowired
+    private WorkPartEtagService workPartEtagService;
+
+    @ResponseBody
+    @RequestMapping("upload")
+    public void upload(@RequestParam(value = "file", required = false) MultipartFile mFile)
+            throws IOException {
+        //随便写一个文件名,这个是要和数据库关联的
+        key = "app-data/appfiles/932059357.war";
+
+        // MultipartFile转File,springmvc得到MultipartFile非常简单
+        CommonsMultipartFile cf= (CommonsMultipartFile)mFile;
+        DiskFileItem dFile = (DiskFileItem)cf.getFileItem();
+        File file = dFile.getStoreLocation();
+
+        // 构造OSSClient
+        client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+
+        try {
+            // 1-获得uploadId
+            uploadId = getUploadId(client, bucketName, key);
+
+            // 2-断点续传分片
+            partCount = getPartCount(file);
+
+            // 3-开始配置子线程,开始上传多片文件到自己的bucket(oss的文件夹)中
+            for(int i = 0; i < partCount; i++){
+                WorkPartEtag workPartEtag = new WorkPartEtag();
+                workPartEtag.setBucketName(bucketName);
+                workPartEtag.setKey(key);
+                workPartEtag.setPartNumber((i + 1)+"");
+                workPartEtag.setUploadId(uploadId);
+                workPartEtag.setSize(this.partSize+"");
+                workPartEtag.setStatus("0");
+                workPartEtagService.save(workPartEtag);
+            }
+            for (int i = 0; i < partCount; i++) {
+                long startPos = i * partSize;
+                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
+                try{
+                    executorService.execute(new PartUploader(file, startPos, curPartSize, i + 1, uploadId));
+                }catch (Exception e){
+                    executorService.shutdown();
+                }
+            }
+
+            // 4-方法执行中(只是步骤展示,这里没有代码)
+
+            // 5-将标识符设为true,前端开始可以访问到上传进度
+            progress = true;
+
+            // 6-等待所有的线程上传完毕后关闭线程
+            executorService.shutdown();
+            while (!executorService.isTerminated()) {
+                try {
+                    //如果有线程没有完毕,等待5秒
+                    executorService.awaitTermination(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            // 7-判断是否所有的分片都上传完毕(不是必须)
+            judgePartCount();
+
+            // 8-必须:在将所有数据Part都上传完成后对整个文件的分片验证。
+            verification();
+
+            // 9-成功,恢复初始值(恢复初始值是为了不让这次上传影响其他的文件上传)
+            restoreDefaultValues();
+
+            // 10-下载上传的文件(不是必须)
+            //client.getObject(new GetObjectRequest(bucketName, key), new File(localFilePath));
+
+        } catch (OSSException oe) {
+            System.out.println("OSS错误原因:");
+            System.out.println("Error Message: " + oe.getErrorCode());
+            System.out.println("Error Code:       " + oe.getErrorCode());
+            System.out.println("Request ID:      " + oe.getRequestId());
+            System.out.println("Host ID:           " + oe.getHostId());
+        } catch (ClientException ce) {
+            System.out.println("OSSClient错误原因:");
+            System.out.println("Error Message: " + ce.getMessage());
+        } finally {
+            //注意关闭资源
+            if (client != null) {
+                client.shutdown();
+            }
+        }
+    }
+
+    @ResponseBody
+    @RequestMapping("listParts")
+    public void listParts()
+            throws IOException {
+        String key = "app-data/appfiles/932059357.war";
+        String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
+        WorkPartEtag workPartEtag = new WorkPartEtag();
+        workPartEtag.setUploadId("B985174C128442268744D5D226A85324");
+        workPartEtag.setPartNumber(27+"");
+        WorkPartEtag workPartEtag1 = workPartEtagService.getByuploadId(workPartEtag);
+        List<PartETag> partETags = partListing(key);
+        multipartUploadListing();
+        System.out.println("--------------233");
+        return;
+    }
+
+    @ResponseBody
+    @RequestMapping("uploadFile")
+    public void uploadFile()
+            throws Throwable {
+        try{
+            partETags = Collections.synchronizedList(new ArrayList<PartETag>());
+            // 构造OSSClient
+            client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+            OSSClientUtil ossClientUtil = new OSSClientUtil();
+            key = "app-data/appfiles/932059357.war";
+            uploadId = "5C10BC9C728A41E69994DF0556B933E7";
+            String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
+            WorkPartEtag workPart = new WorkPartEtag();
+            workPart.setUploadId(uploadId);
+            workPart.setStatus("0");
+            List<WorkPartEtag> workPartEtags= workPartEtagService.findList(workPart);
+            workPart.setStatus("1");
+            List<WorkPartEtag> workPartEtagolds= workPartEtagService.findList(workPart);
+            for (WorkPartEtag workPartEtag:workPartEtagolds){
+                PartETag partETag = new PartETag(Integer.parseInt(workPartEtag.getPartNumber()),workPartEtag.getETag(),Long.parseLong(workPartEtag.getSize()),Long.parseLong(workPartEtag.getPartCRC()));
+                partETags.add(partETag);
+            }
+            List<String> numbers = new ArrayList<String>();
+            for (WorkPartEtag workPartEtag:workPartEtags){
+                String number = workPartEtag.getPartNumber();
+                numbers.add(number);
+            }
+            File file = new File(url);
+            // 2-断点续传分片
+            partCount = getPartCount(file);
+
+            // 3-开始配置子线程,开始上传多片文件到自己的bucket(oss的文件夹)中
+            for (String id :numbers){
+                int i = Integer.parseInt(id)-1;
+                long startPos = i * partSize;
+                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
+                try {
+                    executorService.execute(new PartUploader(file, startPos, curPartSize, i+1, uploadId));
+                } catch (Exception e) {
+                    executorService.shutdown();
+                }
+            }
+            for (int i = 0; i < partCount; i++) {
+                long startPos = i * partSize;
+                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
+                try {
+                    executorService.execute(new PartUploader(file, startPos, curPartSize, i + 1, uploadId));
+                } catch (Exception e) {
+                    executorService.shutdown();
+                }
+            }
+
+            // 4-方法执行中(只是步骤展示,这里没有代码)
+
+            // 5-将标识符设为true,前端开始可以访问到上传进度
+            progress = true;
+
+            // 6-等待所有的线程上传完毕后关闭线程
+            executorService.shutdown();
+            while (!executorService.isTerminated()) {
+                try {
+                    //如果有线程没有完毕,等待5秒
+                    executorService.awaitTermination(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            // 7-判断是否所有的分片都上传完毕(不是必须)
+            judgePartCount();
+
+            // 8-必须:在将所有数据Part都上传完成后对整个文件的分片验证。
+            verification();
+
+            // 9-成功,恢复初始值(恢复初始值是为了不让这次上传影响其他的文件上传)
+            restoreDefaultValues();
+        } catch (OSSException oe) {
+            System.out.println("OSS错误原因:");
+            System.out.println("Error Message: " + oe.getErrorCode());
+            System.out.println("Error Code:       " + oe.getErrorCode());
+            System.out.println("Request ID:      " + oe.getRequestId());
+            System.out.println("Host ID:           " + oe.getHostId());
+        } catch (ClientException ce) {
+            System.out.println("OSSClient错误原因:");
+            System.out.println("Error Message: " + ce.getMessage());
+        } finally {
+            //注意关闭资源
+            if (client != null) {
+                client.shutdown();
+            }
+        }
+
+    }
+
+    /**
+     * 获得uploadId
+     */
+    public String getUploadId(OSSClient client, String bucketName, String key){
+        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
+        InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
+        return result.getUploadId();
+    }
+
+    /**
+     * 上传分片
+     */
+    public void uploadSetEnableCheckpoint(String key,String localFile, List<PartETag> partETagList){
+        List<PartETag> partETags = new ArrayList<PartETag>();
+        InputStream instream = null;
+        try {
+            instream = new FileInputStream(new File(localFile));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        UploadPartRequest uploadPartRequest = new UploadPartRequest();
+        uploadPartRequest.setBucketName(bucketName);
+        uploadPartRequest.setKey(key);
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+        uploadPartRequest.setUploadId(uploadId);
+        uploadPartRequest.setInputStream(instream);
+        // 设置分片大小,除最后一个分片外,其它分片要大于100KB
+        uploadPartRequest.setPartSize(partSize);
+        // 设置分片号,范围是1~10000,
+        uploadPartRequest.setPartNumber(1);
+        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
+        partETags.add(uploadPartResult.getPartETag());
+        partETags.remove(partETagList);
+        completeMultipartUpload(key,partETags);
+    }
+
+    /**
+     * 完成分片上传
+     */
+    public void completeMultipartUpload(String key,List<PartETag> partETags){
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+        Collections.sort(partETags, new Comparator<PartETag>() {
+            @Override
+            public int compare(PartETag p1, PartETag p2) {
+                return p1.getPartNumber() - p2.getPartNumber();
+            }
+        });
+        CompleteMultipartUploadRequest completeMultipartUploadRequest =
+                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
+        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
+    }
+
+    /**
+     * 取消分片上传事件
+     */
+    public void abortMultipartUpload(String key){
+        // 取消分片上传,其中uploadId来自于initiateMultipartUpload
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+        AbortMultipartUploadRequest abortMultipartUploadRequest =
+                new AbortMultipartUploadRequest(bucketName, key, uploadId);
+        ossClient.abortMultipartUpload(abortMultipartUploadRequest);
+    }
+
+    /**
+     * 简单列举分片上传事件
+     */
+    public void multipartUploadListing(){
+        // 取消分片上传,其中uploadId来自于initiateMultipartUpload
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+        // 列举分片上传事件
+        ListMultipartUploadsRequest listMultipartUploadsRequest = new ListMultipartUploadsRequest(bucketName);
+        MultipartUploadListing multipartUploadListing = ossClient.listMultipartUploads(listMultipartUploadsRequest);
+        for (com.aliyun.oss.model.MultipartUpload multipartUpload : multipartUploadListing.getMultipartUploads()) {
+            // Upload Id
+            multipartUpload.getUploadId();
+            // Key
+            multipartUpload.getKey();
+            // Date of initiate multipart upload
+            multipartUpload.getInitiated();
+        }
+    }
+
+    /**
+     * 获取已上传的分片
+     */
+    public List<PartETag> partListing(String key){
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+        // 列举已上传的分片,其中uploadId来自于initiateMultipartUpload
+        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+        PartListing partListing = ossClient.listParts(listPartsRequest);
+        List<PartETag> partETags = new ArrayList<PartETag>();
+        for (PartSummary part : partListing.getParts()) {
+            PartETag partETag = new PartETag(part.getPartNumber(),part.getETag());
+            // 分片号,上传时候指定
+            part.getPartNumber();
+            // 分片数据大小
+            part.getSize();
+            // Part的ETag
+            partETags.add(partETag);
+            // Part的最后修改上传
+            part.getLastModified();
+        }
+        return partETags;
+    }
+
+    /**
+     * 获取所有已上传分片
+     */
+    public ListPartsRequest listParts(String key){
+        String uploadId = "490C8D60323F4AE3ACA2C627A785902A";;
+        // 列举所有已上传的分片
+        PartListing partListing;
+        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+        do {
+            partListing = ossClient.listParts(listPartsRequest);
+            for (PartSummary part : partListing.getParts()) {
+                // 分片号,上传时候指定
+                part.getPartNumber();
+                // 分片数据大小
+                part.getSize();
+                // Part的ETag
+                part.getETag();
+                // Part的最后修改上传
+                part.getLastModified();
+            }
+            listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
+        } while (partListing.isTruncated());
+        return listPartsRequest;
+    }
+
+
+    /**
+     * 断点续传分片数量判断
+     */
+    public int getPartCount(File file){
+        long fileLength = file.length();
+        int partCount = (int) (fileLength / partSize); // 分片个数,不能超过10000
+        if (fileLength % partSize != 0) {
+            partCount++;
+        }
+        if (partCount > 10000) {
+            throw new RuntimeException("分片不能超过10000,请修改每片大小");
+        } else {
+            return partCount;
+        }
+    }
+
+    /**
+     * 子线程上传(多线程的上传效果提升明显,如果单线程则在for循环中一个个上传即可)
+     */
+    private class PartUploader implements Runnable {
+        private File localFile;
+        private long startPos;
+        private long partSize;
+        private int partNumber;
+        private String uploadId;
+        public PartUploader(File localFile, long startPos, long partSize, int partNumber, String uploadId) {
+            this.localFile = localFile;
+            this.startPos = startPos;
+            this.partSize = partSize;
+            this.partNumber = partNumber;
+            this.uploadId = uploadId;
+        }
+        @Override
+        public void run() {
+            InputStream instream = null;
+            try {
+                instream = new FileInputStream(this.localFile);
+                instream.skip(this.startPos);
+
+                UploadPartRequest uploadPartRequest = new UploadPartRequest();
+                uploadPartRequest.setBucketName(bucketName);
+                uploadPartRequest.setKey(key);
+                uploadPartRequest.setUploadId(this.uploadId);
+                uploadPartRequest.setInputStream(instream);
+                uploadPartRequest.setPartSize(this.partSize);
+                uploadPartRequest.setPartNumber(this.partNumber);
+
+                UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
+                synchronized (partETags) {
+                    partETags.add(uploadPartResult.getPartETag());
+                    WorkPartEtag workPartEtag = new WorkPartEtag();
+                    workPartEtag.setUploadId(this.uploadId);
+                    workPartEtag.setPartNumber(this.partNumber+"");
+                    WorkPartEtag workPartEtag1 = MultipartUpload.this.workPartEtagService.getByuploadId(workPartEtag);
+                    if (workPartEtag1!=null && StringUtils.isNotBlank(workPartEtag1.getId())){
+                        workPartEtag1.setStatus("1");
+                        workPartEtag1.setETag(uploadPartResult.getETag());
+                        workPartEtag1.setSize(uploadPartResult.getPartETag().getPartSize()+"");
+                        workPartEtag1.setPartCRC(uploadPartResult.getPartETag().getPartCRC()+"");
+                        workPartEtagService.save(workPartEtag1);
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                if (instream != null) {
+                    try {
+                        instream.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 判断是否上传完毕,这里起始需要得到哪个错了,从哪个开始重新上传,获得part#,然后计算5M的大小上传
+     * 这里只是单纯的记录一下,以后再完善
+     * @return
+     */
+    public void judgePartCount(){
+        if (partETags.size() != partCount) {
+            System.out.println("一部分没有上传成功,需要从ListMultipartUploads里面查看上传数量");
+            throw new IllegalStateException("Upload multiparts fail due to some parts are not finished yet");
+        } else {
+            System.out.println("上传成功,文件名称:" + key + "\n");
+        }
+    }
+
+    /**
+     * 验证文件是否完全,OSS必须走这个方法
+     * 最后返回CompleteMultipartUploadResult,这里不继续深化了
+     * @return
+     */
+    public void verification(){
+        // 修改顺序
+        Collections.sort(partETags, new Comparator<PartETag>() {
+            @Override
+            public int compare(PartETag p1, PartETag p2) {
+                return p1.getPartNumber() - p2.getPartNumber();
+            }
+        });
+        System.out.println("开始验证\n");
+        CompleteMultipartUploadRequest completeMultipartUploadRequest =
+                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
+        client.completeMultipartUpload(completeMultipartUploadRequest);
+    }
+
+    /**
+     * 恢复初始值
+     * @return
+     */
+    public void restoreDefaultValues(){
+        executorService = Executors.newFixedThreadPool(5);
+        partETags = Collections.synchronizedList(new ArrayList<PartETag>());
+        client = null;
+        uploadId = null;
+        partCount = 0;
+    }
+
+    /**
+     * 页面上进度查询走这个方法
+     * @return
+     */
+    @ResponseBody
+    @RequestMapping("u_test")
+    public Object u_test(){
+        //获得OSS上面的已经好了的分片和总分片相除获得进度
+        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+        PartListing partListing;
+        try {
+            partListing = client.listParts(listPartsRequest);
+            Integer nowPartCount = partListing.getParts().size();
+            return (int)((double)nowPartCount/partCount * 100);
+        }catch (Exception e){
+            //如果标识符为true说明已经开始上传,设为100%;否则说明刚进入方法,还没有上传,设为1%
+            if(progress)
+                return "100";
+            else
+                return "1";
+        }
+    }
+}

+ 247 - 0
src/main/java/com/jeeplus/common/oss/MultipartUploadSample.java

@@ -0,0 +1,247 @@
+package com.jeeplus.common.oss;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.aliyun.oss.ClientConfiguration;
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.CompleteMultipartUploadRequest;
+import com.aliyun.oss.model.GetObjectRequest;
+import com.aliyun.oss.model.InitiateMultipartUploadRequest;
+import com.aliyun.oss.model.InitiateMultipartUploadResult;
+import com.aliyun.oss.model.ListPartsRequest;
+import com.aliyun.oss.model.PartETag;
+import com.aliyun.oss.model.PartListing;
+import com.aliyun.oss.model.PartSummary;
+import com.aliyun.oss.model.UploadPartRequest;
+import com.aliyun.oss.model.UploadPartResult;
+import com.jeeplus.common.config.Global;
+
+public class MultipartUploadSample {
+
+    private static String endpoint = Global.getEndpoint();
+    private static String accessKeyId = Global.getAccessKeyId();
+    private static String accessKeySecret = Global.getAccessKeySecret();
+
+    private static OSSClient client = null;
+
+    private static String bucketName = Global.getBucketName();
+    private static String key = "*** Provide key ***";
+
+    private static ExecutorService executorService = Executors.newFixedThreadPool(5);
+    private static List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());
+
+    public static void main(String[] args) throws IOException {
+        /*
+         * Constructs a client instance with your account for accessing OSS
+         */
+        ClientConfiguration conf = new ClientConfiguration();
+        conf.setIdleConnectionTime(1000);
+        client = new OSSClient(endpoint, accessKeyId, accessKeySecret, conf);
+
+        try {
+            /*
+             * Claim a upload id firstly
+             */
+            String uploadId = claimUploadId();
+            System.out.println("Claiming a new upload id " + uploadId + "\n");
+
+            /*
+             * Calculate how many parts to be divided
+             */
+            final long partSize = 5 * 1024 * 1024L;   // 5MB
+            final File sampleFile = createSampleFile();
+            long fileLength = sampleFile.length();
+            int partCount = (int) (fileLength / partSize);
+            if (fileLength % partSize != 0) {
+                partCount++;
+            }
+            if (partCount > 10000) {
+                throw new RuntimeException("Total parts count should not exceed 10000");
+            } else {
+                System.out.println("Total parts count " + partCount + "\n");
+            }
+
+            /*
+             * Upload multiparts to your bucket
+             */
+            System.out.println("Begin to upload multiparts to OSS from a file\n");
+            for (int i = 0; i < partCount; i++) {
+                long startPos = i * partSize;
+                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
+                executorService.execute(new PartUploader(sampleFile, startPos, curPartSize, i + 1, uploadId));
+            }
+
+            /*
+             * Waiting for all parts finished
+             */
+            executorService.shutdown();
+            while (!executorService.isTerminated()) {
+                try {
+                    executorService.awaitTermination(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            /*
+             * Verify whether all parts are finished
+             */
+            if (partETags.size() != partCount) {
+                throw new IllegalStateException("Upload multiparts fail due to some parts are not finished yet");
+            } else {
+                System.out.println("Succeed to complete multiparts into an object named " + key + "\n");
+            }
+
+            /*
+             * View all parts uploaded recently
+             */
+            listAllParts(uploadId);
+
+            /*
+             * Complete to upload multiparts
+             */
+            completeMultipartUpload(uploadId);
+
+            /*
+             * Fetch the object that newly created at the step below.
+             */
+            System.out.println("Fetching an object");
+            client.getObject(new GetObjectRequest(bucketName, key), new File("C:/Users/Meng/Desktop/ruihua/Tool9-15匞匞服务平台问题反馈.docx"));
+
+        } catch (OSSException oe) {
+            System.out.println("Caught an OSSException, which means your request made it to OSS, "
+                    + "but was rejected with an error response for some reason.");
+            System.out.println("Error Message: " + oe.getErrorCode());
+            System.out.println("Error Code:       " + oe.getErrorCode());
+            System.out.println("Request ID:      " + oe.getRequestId());
+            System.out.println("Host ID:           " + oe.getHostId());
+        } catch (ClientException ce) {
+            System.out.println("Caught an ClientException, which means the client encountered "
+                    + "a serious internal problem while trying to communicate with OSS, "
+                    + "such as not being able to access the network.");
+            System.out.println("Error Message: " + ce.getMessage());
+        } finally {
+            /*
+             * Do not forget to shut down the client finally to release all allocated resources.
+             */
+            if (client != null) {
+                client.shutdown();
+            }
+        }
+    }
+
+    private static class PartUploader implements Runnable {
+
+        private File localFile;
+        private long startPos;
+
+        private long partSize;
+        private int partNumber;
+        private String uploadId;
+
+        public PartUploader(File localFile, long startPos, long partSize, int partNumber, String uploadId) {
+            this.localFile = localFile;
+            this.startPos = startPos;
+            this.partSize = partSize;
+            this.partNumber = partNumber;
+            this.uploadId = uploadId;
+        }
+
+        @Override
+        public void run() {
+            InputStream instream = null;
+            try {
+                instream = new FileInputStream(this.localFile);
+                instream.skip(this.startPos);
+
+                UploadPartRequest uploadPartRequest = new UploadPartRequest();
+                uploadPartRequest.setBucketName(bucketName);
+                uploadPartRequest.setKey(key);
+                uploadPartRequest.setUploadId(this.uploadId);
+                uploadPartRequest.setInputStream(instream);
+                uploadPartRequest.setPartSize(this.partSize);
+                uploadPartRequest.setPartNumber(this.partNumber);
+
+                UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
+                System.out.println("Part#" + this.partNumber + " done\n");
+                synchronized (partETags) {
+                    partETags.add(uploadPartResult.getPartETag());
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                if (instream != null) {
+                    try {
+                        instream.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    private static File createSampleFile() throws IOException {
+        File file = File.createTempFile("oss-java-sdk-", ".txt");
+        file.deleteOnExit();
+
+        Writer writer = new OutputStreamWriter(new FileOutputStream(file));
+        for (int i = 0; i < 1000000; i++) {
+            writer.write("abcdefghijklmnopqrstuvwxyz\n");
+            writer.write("0123456789011234567890\n");
+        }
+        writer.close();
+
+        return file;
+    }
+
+    private static String claimUploadId() {
+        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
+        InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
+        return result.getUploadId();
+    }
+
+    private static void completeMultipartUpload(String uploadId) {
+        // Make part numbers in ascending order
+        Collections.sort(partETags, new Comparator<PartETag>() {
+
+            @Override
+            public int compare(PartETag p1, PartETag p2) {
+                return p1.getPartNumber() - p2.getPartNumber();
+            }
+        });
+
+        System.out.println("Completing to upload multiparts\n");
+        CompleteMultipartUploadRequest completeMultipartUploadRequest =
+                new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
+        client.completeMultipartUpload(completeMultipartUploadRequest);
+    }
+
+    private static void listAllParts(String uploadId) {
+        System.out.println("Listing all parts......");
+        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+        PartListing partListing = client.listParts(listPartsRequest);
+
+        int partCount = partListing.getParts().size();
+        for (int i = 0; i < partCount; i++) {
+            PartSummary partSummary = partListing.getParts().get(i);
+            System.out.println("\tPart#" + partSummary.getPartNumber() + ", ETag=" + partSummary.getETag());
+        }
+        System.out.println();
+    }
+}

+ 742 - 0
src/main/java/com/jeeplus/common/oss/OSSClientUtil.java

@@ -0,0 +1,742 @@
+package com.jeeplus.common.oss;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.*;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.Encodes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.util.*;
+
+/**
+ * Created by Meng on 2017/6/8.
+ */
+public class OSSClientUtil {
+    private static Logger logger = LoggerFactory.getLogger(OSSClientUtil.class);
+    private String appData = Global.getAppData();
+    private String endpoint = Global.getEndpoint();
+    private String accessKeyId = Global.getAccessKeyId();
+    private String accessKeySecret = Global.getAccessKeySecret();
+    private String bucketName = Global.getBucketName();
+    private String savePath = Global.getAliyunUrl();
+    private String avatarDir = Global.getAvatarDir();
+    private String notifyDir = Global.getNotifyDir();
+    private String reportDir = Global.getReportDir();
+    private String rqcode = Global.getRqcode();
+    private String goout = Global.getGoout();
+    private String leave = Global.getLeave();
+    private String overtimeform = Global.getOvertimeform();
+    private String sealform = Global.getSealform();
+    private String workReimbur = Global.getWorkReimbur();
+    private String evection = Global.getEvection();
+    private String photo = Global.getPhoto();
+    private String userEvaluation = Global.getUserEvaluation();
+    private String workBidingDocument = Global.getWorkBidingDocument();
+    private String workEngineeringProject = Global.getWorkEngineeringProject();
+    private String workFullExecute = Global.getWorkFullExecute();
+    private String workFullMeetingminutes = Global.getWorkFullMeetingminutes();
+    private String workFullDesignchange = Global.getWorkFullDesignchange();
+    private String workFullConstructsheet = Global.getWorkFullConstructsheet();
+    private String workFullProprietorsheet = Global.getWorkFullProprietorsheet();
+    private String workFullSupervisorsheet = Global.getWorkFullSupervisorsheet();
+    private String workProjectReport = Global.getWorkProjectReport();
+    private String workProjectBasis = Global.getWorkProjectBasis();
+    private String workProjectRemote = Global.getWorkProjectRemote();
+    private String workProjectSummary = Global.getWorkProjectSummary();
+    private String workProjectOther = Global.getWorkProjectOther();
+    private String workVisa = Global.getWorkVisa();
+    private String Officehonor = Global.getOfficehonor();
+    private String jobResume = Global.getJobResume();
+    private String satisfaction = Global.getSatisfaction();
+    private String certificate = Global.getCertificate();
+    private String judgeAttachment = Global.getJudgeAttachment();
+    private String oaBuy = Global.getOaBuy();
+    private String oaAll = Global.getOaAll();
+    private String im = Global.getIm();
+    private OSSClient ossClient;
+
+    public OSSClientUtil() {
+        ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+    }
+
+    /**
+     * 初始化
+     */
+    public void init() {
+        ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+    }
+
+    /**
+     * 销毁
+     */
+    public void destroy() {
+        ossClient.shutdown();
+    }
+
+     /**
+     * 上传文件
+     *
+     * @param fileFile 文件
+      *  @param type 文件类型        avatar 头像;notify公告
+     */
+    public String uploadFile2OSS(MultipartFile fileFile,String type){
+        switch (type){
+            case "appData":
+                return uploadImg2OSS(fileFile,appData);
+            case "oaBuy":
+                return uploadImg2OSS(fileFile,oaBuy);
+            case "oaAll":
+                return uploadImg2OSS(fileFile,oaAll);
+            case "avatar":
+                return uploadImg2OSS(fileFile,avatarDir);
+            case  "notify":
+                return uploadImg2OSS(fileFile,notifyDir);
+            case "report":
+                return uploadImg2OSS(fileFile,reportDir);
+            case "rqcode":
+                return uploadImg2OSS(fileFile,rqcode);
+            case "goout":
+                return uploadImg2OSS(fileFile,goout);
+            case  "leave":
+                return uploadImg2OSS(fileFile,leave);
+            case "overtimeform":
+                return uploadImg2OSS(fileFile,overtimeform);
+            case "sealform":
+                return uploadImg2OSS(fileFile,sealform);
+            case "workReimbur":
+                return uploadImg2OSS(fileFile,workReimbur);
+            case "evection":
+                return uploadImg2OSS(fileFile,evection);
+            case "photo":
+                return uploadImg2OSS(fileFile,photo);
+            case "userEvaluation" :
+                return uploadImg2OSS(fileFile,userEvaluation);
+            case "workBidingDocument":
+                return uploadImg2OSS(fileFile,workBidingDocument);
+            case "workEngineeringProject":
+                return uploadImg2OSS(fileFile,workEngineeringProject);
+            case "workFullExecute":
+                return uploadImg2OSS(fileFile,workFullExecute);
+            case "workFullMeetingminutes":
+                return uploadImg2OSS(fileFile,workFullMeetingminutes);
+            case "workFullDesignchange":
+                return uploadImg2OSS(fileFile,workFullDesignchange);
+            case "workFullConstructsheet":
+                return uploadImg2OSS(fileFile,workFullConstructsheet);
+            case "workFullProprietorsheet":
+                return uploadImg2OSS(fileFile,workFullProprietorsheet);
+            case "workFullSupervisorsheet":
+                return uploadImg2OSS(fileFile,workFullSupervisorsheet);
+            case "workProjectReport":
+                return uploadImg2OSS(fileFile,workProjectReport);
+            case "workProjectBasis":
+                return uploadImg2OSS(fileFile,workProjectBasis);
+            case "workProjectRemote":
+                return uploadImg2OSS(fileFile,workProjectRemote);
+            case "workProjectSummary":
+                return uploadImg2OSS(fileFile,workProjectSummary);
+            case "workProjectOther":
+                return uploadImg2OSS(fileFile,workProjectOther);
+            case "workVisa":
+                return uploadImg2OSS(fileFile,workVisa);
+            case "Officehonor":
+                return uploadImg2OSS(fileFile,Officehonor);
+            case "jobResume":
+                return uploadImg2OSS(fileFile,jobResume);
+            case "satisfaction":
+                return uploadImg2OSS(fileFile,satisfaction);
+            case "certificate":
+                return uploadImg2OSS(fileFile,certificate);
+            case "judgeAttachment":
+                return uploadImg2OSS(fileFile,judgeAttachment);
+            case "im":
+                return uploadImg2OSS(fileFile,im);
+            default:
+                return "";
+        }
+
+    }
+
+//    /**
+//     * 上传文件
+//     *
+//     * @param url
+//     */
+//    public void uploadImg2OSS(String url) {
+//        File fileOnServer = new File(url);
+//        FileInputStream fin;
+//        try {
+//            fin = new FileInputStream(fileOnServer);
+//            String[] split = url.split("/");
+//            this.uploadFile2OSS(fin,this/filedir, split[split.length - 1]);
+//        } catch (FileNotFoundException e) {
+//            System.out.println("文件上传失败");
+//            e.printStackTrace();
+//        }
+//    }
+
+    public String uploadImg2OSS(MultipartFile file,String fileDir) {
+        if (file.getSize() > 10 * 1024 * 1024) {
+            System.out.println("上传文件大小不能超过10M!");
+            return "上传文件大小不能超过10M!";
+        }
+        String originalFilename = file.getOriginalFilename();
+        String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
+        String fileName="";
+        fileDir = fileDir+System.nanoTime()+"/";
+        String md5 ="";
+        File f = null;
+        try {
+            InputStream inputStream = file.getInputStream();
+            f=File.createTempFile("tmp", null);
+            file.transferTo(f);
+            f.deleteOnExit();
+            md5 = getMd5ByFile(f);
+            System.out.println(md5);
+            fileName=  md5+ substring;
+            System.out.println(fileName);
+            this.uploadFile2OSS(inputStream,fileDir, fileName);
+        }catch (IOException e){
+            e.printStackTrace();
+        }catch (Exception e1){
+            e1.printStackTrace();
+            System.out.println("文件上传失败");
+        }
+
+        String name = savePath+"/"+fileDir+fileName;
+        return name;
+    }
+
+    public String uploadImg2OSSAndroid(MultipartFile file,String fileDir) {
+        /*if (file.getSize() > 10 * 1024 * 1024) {
+            System.out.println("上传文件大小不能超过10M!");
+            return "上传文件大小不能超过10M!";
+        }*/
+        String originalFilename = file.getOriginalFilename();
+        String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
+        String fileName="";
+        fileDir = fileDir+System.nanoTime()+"/";
+        String md5 ="";
+        File f = null;
+        try {
+            InputStream inputStream = file.getInputStream();
+            f=File.createTempFile("tmp", null);
+            file.transferTo(f);
+            f.deleteOnExit();
+            md5 = getMd5ByFile(f);
+            System.out.println(md5);
+            fileName=  md5+ substring;
+            System.out.println(fileName);
+            this.uploadFile2OSSAndroid(inputStream,fileDir, fileName);
+        }catch (IOException e){
+            e.printStackTrace();
+        }catch (Exception e1){
+            e1.printStackTrace();
+            System.out.println("文件上传失败");
+        }
+
+        String name = savePath+"/"+fileDir+fileName;
+        return name;
+    }
+
+        /**
+         * 上传到OSS服务器  如果同名文件会覆盖服务器上的
+         *
+         * @param fileName 文件名称 包括后缀名
+         * @return 出错返回"" ,唯一MD5数字签名
+         */
+        public String uploadFile2OSS(InputStream inStream,String fileDir, String fileName) {
+            long start = System.currentTimeMillis();
+            //上传文件
+            PutObjectResult putResult = ossClient.putObject(bucketName, fileDir + fileName, inStream);
+            String ret = putResult.getETag();
+
+            try {
+                if (inStream != null) {
+                    inStream.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            long end = System.currentTimeMillis();
+            logger.info("上传文件到云服务器成功,文件名:{},耗时:{}ms",fileName,end-start);
+            return ret;
+        }
+    /**
+     *
+     *Android安卓固定下载地址
+     * @param fileName 文件名称 包括后缀名
+     * @return 出错返回"" ,唯一MD5数字签名
+     */
+    public String uploadFile2OSSAndroid(InputStream inStream,String fileDir, String fileName) {
+        long start = System.currentTimeMillis();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len;
+        try {
+            while ((len = inStream.read(buffer)) > -1 ) {
+                baos.write(buffer, 0, len);
+            }
+            baos.flush();
+        } catch (IOException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+        }
+        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
+        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
+        //上传文件
+        PutObjectResult putResult = ossClient.putObject(bucketName, fileDir + fileName, is1);
+        ossClient.putObject(bucketName, Global.getVersion()+"ruihuaoa.apk", is2);
+        //ossClient.putObject(bucketName, Global.getTestVersion()+"ruihuaoa.apk", is2);//测试用
+        String ret = putResult.getETag();
+
+        try {
+            if (inStream != null) {
+                is1.close();
+                is2.close();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        long end = System.currentTimeMillis();
+        logger.info("上传文件到云服务器成功,文件名:{},耗时:{}ms",fileName,end-start);
+        return ret;
+    }
+        /**
+         *
+         */
+        public void uploadFile2OSSToo(String key, String url) throws Throwable {
+            // 设置断点续传请求
+            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, key);
+            // 指定上传的本地文件
+            uploadFileRequest.setUploadFile(url);
+            // 指定上传并发线程数
+            uploadFileRequest.setTaskNum(5);
+            // 指定上传的分片大小
+            uploadFileRequest.setPartSize(1 * 1024 * 1024);
+            // 开启断点续传
+            uploadFileRequest.setEnableCheckpoint(true);
+            // 断点续传上传
+            UploadFileResult result = ossClient.uploadFile(uploadFileRequest);
+            System.out.println("233--"+result);
+            // 关闭client
+        }
+
+        public String getUploadId(String key){
+            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
+            InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
+            String uploadId = result.getUploadId();
+            return uploadId;
+        }
+
+
+        /**
+         *获取已上传的分片
+         * UploadId,initiateMultipartUpload返回的结果获取
+         */
+        public void uploadPartListing (String key){
+            String uploadId = getUploadId(key);
+            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+            PartListing partListing = ossClient.listParts(listPartsRequest);
+            for (PartSummary part : partListing.getParts()) {
+            // 分片号,上传时候指定
+                part.getPartNumber();
+            // 分片数据大小
+                part.getSize();
+            // Part的ETag
+                part.getETag();
+            // Part的最后修改上传
+                part.getLastModified();
+            }
+        }
+
+        /**
+         * 上传分片
+         */
+        public void uploadSetEnableCheckpoint(String key,String localFile){
+            List<PartETag> partETags = new ArrayList<PartETag>();
+            InputStream instream = null;
+            try {
+                instream = new FileInputStream(new File(localFile));
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+            UploadPartRequest uploadPartRequest = new UploadPartRequest();
+            uploadPartRequest.setBucketName(bucketName);
+            uploadPartRequest.setKey(key);
+            String uploadId = "7188157413A24508B8E327913AFBFCCE";
+            uploadPartRequest.setUploadId(uploadId);
+            uploadPartRequest.setInputStream(instream);
+            // 设置分片大小,除最后一个分片外,其它分片要大于100KB
+            uploadPartRequest.setPartSize(100 * 1024);
+            // 设置分片号,范围是1~10000,
+            uploadPartRequest.setPartNumber(1);
+            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
+            partETags.add(uploadPartResult.getPartETag());
+            completeMultipartUpload(key,partETags);
+        }
+
+        /**
+         * 完成分片上传
+         */
+        public void completeMultipartUpload(String key,List<PartETag> partETags){
+            String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+            Collections.sort(partETags, new Comparator<PartETag>() {
+                @Override
+                public int compare(PartETag p1, PartETag p2) {
+                    return p1.getPartNumber() - p2.getPartNumber();
+                }
+            });
+            CompleteMultipartUploadRequest completeMultipartUploadRequest =
+                    new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
+            ossClient.completeMultipartUpload(completeMultipartUploadRequest);
+        }
+
+        /**
+         * 取消分片上传事件
+         */
+        public void abortMultipartUpload(String key){
+            // 取消分片上传,其中uploadId来自于initiateMultipartUpload
+            String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+            AbortMultipartUploadRequest abortMultipartUploadRequest =
+                    new AbortMultipartUploadRequest(bucketName, key, uploadId);
+            ossClient.abortMultipartUpload(abortMultipartUploadRequest);
+        }
+
+        /**
+         * 获取已上传的分片
+         */
+        public void partListing(String key){
+            String uploadId = "490C8D60323F4AE3ACA2C627A785902A";
+            // 列举已上传的分片,其中uploadId来自于initiateMultipartUpload
+            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+            PartListing partListing = ossClient.listParts(listPartsRequest);
+            for (PartSummary part : partListing.getParts()) {
+                // 分片号,上传时候指定
+                part.getPartNumber();
+                // 分片数据大小
+                part.getSize();
+                // Part的ETag
+                part.getETag();
+                // Part的最后修改上传
+                part.getLastModified();
+            }
+        }
+
+        /**
+         * 获取所有已上传分片
+         */
+        public ListPartsRequest listParts(String key){
+            String uploadId = getUploadId(key);
+            // 列举所有已上传的分片
+            PartListing partListing;
+            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+            do {
+                partListing = ossClient.listParts(listPartsRequest);
+                for (PartSummary part : partListing.getParts()) {
+                    // 分片号,上传时候指定
+                    part.getPartNumber();
+                    // 分片数据大小
+                    part.getSize();
+                    // Part的ETag
+                    part.getETag();
+                    // Part的最后修改上传
+                    part.getLastModified();
+                }
+                listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
+            } while (partListing.isTruncated());
+            return listPartsRequest;
+        }
+
+        /**
+         * 分页获取所有分片
+         */
+        public void listPartsPage(String key){
+            String uploadId = getUploadId(key);
+            // 分页列举已上传的分片
+            PartListing partListing;
+            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
+            // 每页100个分片
+            listPartsRequest.setMaxParts(100);
+            do {
+                partListing = ossClient.listParts(listPartsRequest);
+                for (PartSummary part : partListing.getParts()) {
+                    // 分片号,上传时候指定
+                    part.getPartNumber();
+                    // 分片数据大小
+                    part.getSize();
+                    // Part的ETag
+                    part.getETag();
+                    // Part的最后修改上传
+                    part.getLastModified();
+                }
+                listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
+            } while (partListing.isTruncated());
+        }
+
+        /**
+         * Description: 判断OSS服务文件上传时文件的contentType
+         *
+         * @param FilenameExtension 文件后缀
+         * @return String
+         */
+        public String getcontentType(String FilenameExtension) {
+            if (FilenameExtension.equalsIgnoreCase("bmp")) {
+                return "image/bmp";
+            }
+            if (FilenameExtension.equalsIgnoreCase("gif")) {
+                return "image/gif";
+            }
+            if (FilenameExtension.equalsIgnoreCase("jpeg") ||
+                    FilenameExtension.equalsIgnoreCase("jpg") ||
+                    FilenameExtension.equalsIgnoreCase("png")) {
+                return "image/jpeg";
+            }
+            if (FilenameExtension.equalsIgnoreCase("html")) {
+                return "text/html";
+            }
+            if (FilenameExtension.equalsIgnoreCase("txt")) {
+                return "text/plain";
+            }
+            if (FilenameExtension.equalsIgnoreCase("vsd")) {
+                return "application/vnd.visio";
+            }
+            if (FilenameExtension.equalsIgnoreCase("pptx") ||
+                    FilenameExtension.equalsIgnoreCase("ppt")) {
+                return "application/vnd.ms-powerpoint";
+            }
+            if (FilenameExtension.equalsIgnoreCase("docx") ||
+                    FilenameExtension.equalsIgnoreCase("doc")) {
+                return "application/msword";
+            }
+            if (FilenameExtension.equalsIgnoreCase("xml")) {
+                return "text/xml";
+            }
+            return "image/jpeg";
+        }
+
+        /**
+         * 获得url链接
+         *
+         * @param key
+         * @return
+         */
+        public String getUrl(String key) {
+            // 设置URL过期时间为10年  3600l* 1000*24*365*10
+            Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10);
+            // 生成URL
+            URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);
+            if (url != null) {
+                return url.toString();
+            }
+            return null;
+        }
+
+        public  String getMd5ByFile(File file) throws FileNotFoundException {
+            String value = null;
+            FileInputStream in = new FileInputStream(file);
+            try {
+                MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+                MessageDigest md5 = MessageDigest.getInstance("MD5");
+                md5.update(byteBuffer);
+                BigInteger bi = new BigInteger(1, md5.digest());
+                value = bi.toString(16);
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                if(null != in) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+            return value;
+        }
+
+        /**
+         * 删除单个文件
+         * @param key  Bucket下的文件的路径名+文件名
+         */
+        public void deleteSingleObject(String key){
+            OSSObject object = null ;
+            try {
+                object = ossClient.getObject(bucketName, key);
+                if(object != null){
+                    ossClient.deleteObject(bucketName, key);
+                }
+            } catch (OSSException oe) {
+                oe.printStackTrace();
+            } catch (ClientException ce) {
+                ce.printStackTrace();
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                if(ossClient != null){
+                    try {
+                        ossClient.shutdown();
+                    }catch (Exception e){
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+
+    public void downLoadFile(String key,String fileName) {
+        String bucketName = Global.getBucketName();
+        try {
+            // 带进度条的下载
+            ossClient.getObject(new GetObjectRequest(bucketName, key).
+                            <GetObjectRequest>withProgressListener(new GetObjectProgressListener()),
+                    new File(fileName));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        ossClient.shutdown();
+    }
+
+    /**
+     * 附件下载
+     * @param key
+     * @param fileName
+     * @param response
+     */
+    public void downByStream(String key, String fileName, HttpServletResponse response,String agent){
+        try {
+            String newName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20").replaceAll("%28", "\\(").replaceAll("%29", "\\)").replaceAll("%3B", ";").replaceAll("%40", "@").replaceAll("%23", "\\#").replaceAll("%26", "\\&").replaceAll("%2C", "\\,");
+            // 创建OSSClient实例
+            OSSObject ossObject = ossClient.getObject(bucketName, key);
+            BufferedInputStream in = new BufferedInputStream(ossObject.getObjectContent());
+            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
+            response.setHeader("Content-Disposition","attachment;filename*=UTF-8''"+ newName);
+
+            /*if(agent != null && agent.toLowerCase().indexOf("firefox") > 0){
+                response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"+ URLEncoder.encode(fileName,"utf-8"));
+            }else {
+                response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"utf-8"));
+            }*/
+            byte[] car=new byte[1024];
+            int L=0;
+            while((L=in.read(car))!=-1){
+                out.write(car, 0,L);
+            }
+            if(out!=null){
+                out.flush();
+                out.close();
+            }
+            if(in!=null){
+                in.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 文件下载
+     * @param key
+     * @param fileName
+     */
+    public byte[] downBytesByStream(String key, String fileName){
+        byte[] bytes = null;
+        BufferedInputStream in = null;
+        ByteArrayOutputStream outputStream = null;
+        logger.info("开始从云服务器加载文件,文件名:{}",fileName);
+        try {
+            // 创建OSSClient实例
+            long start = System.currentTimeMillis();
+            OSSObject ossObject = ossClient.getObject(bucketName, key);
+            in = new BufferedInputStream(ossObject.getObjectContent());
+            outputStream = new ByteArrayOutputStream();
+            byte[] car=new byte[1024];
+            int L=0;
+            while((L=in.read(car))!=-1){
+                outputStream.write(car, 0,L);
+            }
+            bytes = outputStream.toByteArray();
+            long end = System.currentTimeMillis();
+            logger.info("从云服务器加载文件成功,文件名:{},耗时:{}ms",fileName,end-start);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }finally {
+            if (in!=null){
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (outputStream!=null){
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return  bytes;
+    }
+
+    /**
+     * 附件转pdf
+     * @param key
+     */
+    public BufferedInputStream recInputStream(String key){
+        // 创建OSSClient实例
+        OSSObject ossObject = ossClient.getObject(bucketName, key);
+        BufferedInputStream in = new BufferedInputStream(ossObject.getObjectContent());
+        return in;
+    }
+
+
+    public static void main(String[] args){
+       /* OSSClientUtil os = new OSSClientUtil();
+        os.downByOpenOffice("app-data/notify/35690003238131.docx","e:/pre.pdf","");*/
+        /*String endpoint = Global.getEndpoint();
+        String accessKeyId = Global.getAccessKeyId();
+        String accessKeySecret = Global.getAccessKeySecret();
+        String bucketName = Global.getBucketName();
+        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+        String key = "app-data/appfiles/932059357.war";
+        String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
+        // 设置断点续传请求
+        UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, key);
+        // 指定上传的本地文件
+        uploadFileRequest.setUploadFile(url);
+        // 指定上传并发线程数
+        uploadFileRequest.setTaskNum(5);
+        // 指定上传的分片大小
+        uploadFileRequest.setPartSize(1 * 1024 * 1024);
+        // 开启断点续传
+        uploadFileRequest.setEnableCheckpoint(true);
+        // 断点续传上传
+        try {
+            ossClient.uploadFile(uploadFileRequest);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+
+        try {
+            // 带进度条的上传
+            ossClient.putObject(new PutObjectRequest(bucketName, key, new FileInputStream(url)).
+                    <PutObjectRequest>withProgressListener(new PutObjectProgressListener()));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        ossClient.shutdown();*/
+    }
+}

+ 180 - 0
src/main/java/com/jeeplus/common/oss/OssPostObject.java

@@ -0,0 +1,180 @@
+package com.jeeplus.common.oss;
+
+import com.aliyun.oss.common.utils.BinaryUtil;
+
+import javax.activation.MimetypesFileTypeMap;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Created by yushuting on 16/4/17.
+ */
+public class OssPostObject {
+
+    private String postFileName = "F:\\code\\jeeplus_maven\\target\\classes.rar";  // 确保运行代码的路径中有该文件
+    private String ossEndpoint = "http://oss-cn-hangzhou.aliyuncs.com";  // 如: http://oss-cn-shanghai.aliyuncs.com
+    private String ossAccessId = "LTAIi7VuxcgfJR2x";  // 你的访问AK信息
+    private String ossAccessKey = "Q9xF9V7tcnCI28ttUsP8H4GyAhZta7";  // 你的访问AK信息
+    private String objectName = "app-data/appfiles/"+System.currentTimeMillis()+"/classes.rar";  // 你上传文件之后的object名称
+    private String bucket = "gangwan-app";  // 你之前创建的bucket,确保这个bucket已经创建
+
+    private void PostObject() throws Exception {
+
+        String filepath=postFileName;
+        String urlStr = ossEndpoint.replace("http://", "http://"+bucket+"."); // 提交表单的URL为bucket域名
+        LinkedHashMap<String, String> textMap = new LinkedHashMap<String, String>();
+        // key
+        String objectName = this.objectName;
+        textMap.put("key", objectName);
+        // Content-Disposition
+        textMap.put("Content-Disposition", "attachment;filename="+filepath);
+        // OSSAccessKeyId
+        textMap.put("OSSAccessKeyId", ossAccessId);
+        // policy
+        String policy = "{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600]]}";
+        System.out.println("policy:"+policy);
+        String encodePolicy = BinaryUtil.toBase64String(policy.getBytes());
+        textMap.put("policy", encodePolicy);
+        System.out.println("encodePolicy:"+encodePolicy);
+        // Signature
+        String signaturecom = com.aliyun.oss.common.auth.ServiceSignature.create().computeSignature(ossAccessKey, encodePolicy);
+        textMap.put("Signature", signaturecom);
+
+        Map<String, String> fileMap = new HashMap<String, String>();
+        fileMap.put("file", filepath);
+
+        String ret = formUpload(urlStr, textMap, fileMap);
+        System.out.println("[" + bucket + "] post_object:" + objectName);
+        System.out.println("post reponse:" + ret);
+    }
+
+    private static String formUpload(String urlStr, Map<String, String> textMap, Map<String, String> fileMap) throws Exception {
+        String res = "";
+        HttpURLConnection conn = null;
+        String BOUNDARY = "9431149156168";
+        try {
+            URL url = new URL(urlStr);
+            conn = (HttpURLConnection) url.openConnection();
+            conn.setConnectTimeout(5000);
+            conn.setReadTimeout(30000);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("POST");
+            conn.setRequestProperty("User-Agent",
+                    "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
+            conn.setRequestProperty("Content-Type",
+                    "multipart/form-data; boundary=" + BOUNDARY);
+
+            OutputStream out = new DataOutputStream(conn.getOutputStream());
+            // text
+            if (textMap != null) {
+                StringBuffer strBuf = new StringBuffer();
+                Iterator iter = textMap.entrySet().iterator();
+                int i = 0;
+                while (iter.hasNext()) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String inputName = (String) entry.getKey();
+                    String inputValue = (String) entry.getValue();
+                    if (inputValue == null) {
+                        continue;
+                    }
+                    if (i == 0) {
+                        strBuf.append("--").append(BOUNDARY).append(
+                                "\r\n");
+                        strBuf.append("Content-Disposition: form-data; name=\""
+                                + inputName + "\"\r\n\r\n");
+                        strBuf.append(inputValue);
+                    } else {
+                        strBuf.append("\r\n").append("--").append(BOUNDARY).append(
+                                "\r\n");
+                        strBuf.append("Content-Disposition: form-data; name=\""
+                                + inputName + "\"\r\n\r\n");
+
+                        strBuf.append(inputValue);
+                    }
+
+                    i++;
+                }
+                out.write(strBuf.toString().getBytes());
+            }
+
+            // file
+            if (fileMap != null) {
+                Iterator iter = fileMap.entrySet().iterator();
+                while (iter.hasNext()) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String inputName = (String) entry.getKey();
+                    String inputValue = (String) entry.getValue();
+                    if (inputValue == null) {
+                        continue;
+                    }
+                    File file = new File(inputValue);
+                    String filename = file.getName();
+                    String contentType = new MimetypesFileTypeMap().getContentType(file);
+                    if (contentType == null || contentType.equals("")) {
+                        contentType = "application/octet-stream";
+                    }
+
+                    StringBuffer strBuf = new StringBuffer();
+                    strBuf.append("\r\n").append("--").append(BOUNDARY).append(
+                            "\r\n");
+                    strBuf.append("Content-Disposition: form-data; name=\""
+                            + inputName + "\"; filename=\"" + filename
+                            + "\"\r\n");
+                    strBuf.append("Content-Type: " + contentType + "\r\n\r\n");
+
+                    out.write(strBuf.toString().getBytes());
+
+                    DataInputStream in = new DataInputStream(new FileInputStream(file));
+                    int bytes = 0;
+                    byte[] bufferOut = new byte[1024];
+                    while ((bytes = in.read(bufferOut)) != -1) {
+                        out.write(bufferOut, 0, bytes);
+                    }
+                    in.close();
+                }
+                StringBuffer strBuf = new StringBuffer();
+                out.write(strBuf.toString().getBytes());
+            }
+
+            byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
+            out.write(endData);
+            out.flush();
+            out.close();
+
+            // 读取返回数据
+            StringBuffer strBuf = new StringBuffer();
+            InputStreamReader inputStreamReader = new InputStreamReader(
+                    conn.getInputStream());
+
+            BufferedReader reader = new BufferedReader(inputStreamReader);
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                strBuf.append(line).append("\n");
+            }
+            res = strBuf.toString();
+            reader.close();
+            reader = null;
+        } catch (Exception e) {
+            System.err.println("发送POST请求出错: " + urlStr);
+            throw e;
+        } finally {
+            if (conn != null) {
+                conn.disconnect();
+                conn = null;
+            }
+        }
+        return res;
+    }
+
+    public static void main(String[] args) throws Exception {
+        OssPostObject ossPostObject = new OssPostObject();
+        ossPostObject.PostObject();
+    }
+
+}

+ 159 - 0
src/main/java/com/jeeplus/common/oss/OssUploadPart.java

@@ -0,0 +1,159 @@
+package com.jeeplus.common.oss;
+
+import com.aliyun.oss.common.utils.BinaryUtil;
+
+import javax.activation.MimetypesFileTypeMap;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Created by yushuting on 16/4/17.
+ */
+public class OssUploadPart {
+
+    private String postFileName = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";  // 确保运行代码的路径中有该文件
+    private String ossEndpoint = "http://oss-cn-hangzhou.aliyuncs.com";  // 如: http://oss-cn-shanghai.aliyuncs.com
+    private String ossAccessId = "LTAIi7VuxcgfJR2x";  // 你的访问AK信息
+    private String ossAccessKey = "Q9xF9V7tcnCI28ttUsP8H4GyAhZta7";  // 你的访问AK信息
+    private String objectName = "app-data/appfiles/"+System.currentTimeMillis()+"/classes.rar";  // 你上传文件之后的object名称
+    private String bucket = "gangwan-app";  // 你之前创建的bucket,确保这个bucket已经创建
+
+    private void PostObject() throws Exception {
+
+        String filepath=postFileName;
+        String urlStr = ossEndpoint.replace("http://", "http://"+bucket+".")+"/"+objectName; // 提交表单的URL为bucket域名
+        LinkedHashMap<String, String> textMap = new LinkedHashMap<String, String>();
+        // key
+        String objectName = this.objectName;
+        textMap.put("key", objectName);
+        // Content-Disposition
+        textMap.put("Content-Disposition", "attachment;filename="+filepath);
+        // OSSAccessKeyId
+        textMap.put("OSSAccessKeyId", ossAccessId);
+        // policy
+        String policy = "{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600]]}";
+        System.out.println("policy:"+policy);
+        String encodePolicy = BinaryUtil.toBase64String(policy.getBytes());
+        textMap.put("policy", encodePolicy);
+        System.out.println("encodePolicy:"+encodePolicy);
+        // Signature
+        String signaturecom = com.aliyun.oss.common.auth.ServiceSignature.create().computeSignature(ossAccessKey, encodePolicy);
+        textMap.put("Authorization", signaturecom);
+
+        Map<String, String> fileMap = new HashMap<String, String>();
+        fileMap.put("file", filepath);
+
+        String ret = formUpload(urlStr, textMap, fileMap);
+        System.out.println("[" + bucket + "]  put_object:" + objectName);
+        System.out.println("put reponse:" + ret);
+    }
+
+    private String formUpload(String urlStr, Map<String, String> textMap, Map<String, String> fileMap) throws Exception {
+        String res = "";
+        HttpURLConnection conn = null;
+        try {
+            URL url = new URL(urlStr);
+            conn = (HttpURLConnection) url.openConnection();
+            conn.setConnectTimeout(5000);
+            conn.setReadTimeout(30000);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("PUT");
+            conn.setRequestProperty("Content-Length","344606");
+            conn.setRequestProperty("x-oss-server-side-encryption","AES256");
+            conn.setRequestProperty("x-oss-object-acl","public-read-write");
+            conn.setRequestProperty("x-oss-date",new Date()+"");
+            conn.setRequestProperty("content-encoding","utf-8");
+            conn.setRequestProperty("content-disposition","attachment;filename="+objectName);
+
+            OutputStream out = new DataOutputStream(conn.getOutputStream());
+            // text
+            if (textMap != null) {
+                StringBuffer strBuf = new StringBuffer();
+                Iterator iter = textMap.entrySet().iterator();
+                int i = 0;
+                while (iter.hasNext()) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String inputValue = (String) entry.getValue();
+                    if (inputValue == null) {
+                        continue;
+                    }
+                    if (i == 0) {
+                        strBuf.append(inputValue);
+                    } else {
+                        strBuf.append(inputValue);
+                    }
+
+                    i++;
+                }
+                out.write(strBuf.toString().getBytes());
+            }
+
+            // file
+            if (fileMap != null) {
+                Iterator iter = fileMap.entrySet().iterator();
+                while (iter.hasNext()) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String inputValue = (String) entry.getValue();
+                    if (inputValue == null) {
+                        continue;
+                    }
+                    File file = new File(inputValue);
+                    String contentType = new MimetypesFileTypeMap().getContentType(file);
+                    if (contentType == null || contentType.equals("")) {
+                        contentType = "application/octet-stream";
+                    }
+
+                    StringBuffer strBuf = new StringBuffer();
+                    strBuf.append("Content-Type: " + contentType + "\r\n\r\n");
+
+                    out.write(strBuf.toString().getBytes());
+
+                    DataInputStream in = new DataInputStream(new FileInputStream(file));
+                    int bytes = 0;
+                    byte[] bufferOut = new byte[1024];
+                    while ((bytes = in.read(bufferOut)) != -1) {
+                        out.write(bufferOut, 0, bytes);
+                    }
+                    in.close();
+                }
+                StringBuffer strBuf = new StringBuffer();
+                out.write(strBuf.toString().getBytes());
+            }
+            out.flush();
+            out.close();
+
+            // 读取返回数据
+            StringBuffer strBuf = new StringBuffer();
+            InputStreamReader inputStreamReader = new InputStreamReader(
+                    conn.getInputStream());
+
+            BufferedReader reader = new BufferedReader(inputStreamReader);
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                strBuf.append(line).append("\n");
+            }
+            res = strBuf.toString();
+            reader.close();
+            reader = null;
+        } catch (Exception e) {
+            System.err.println("发送POST请求出错: " + urlStr);
+            throw e;
+        } finally {
+            if (conn != null) {
+                conn.disconnect();
+                conn = null;
+            }
+        }
+        return res;
+    }
+
+    public static void main(String[] args) throws Exception {
+        OssUploadPart ossPostObject = new OssUploadPart();
+        ossPostObject.PostObject();
+    }
+
+}

+ 49 - 0
src/main/java/com/jeeplus/common/oss/PutObjectProgressListener.java

@@ -0,0 +1,49 @@
+package com.jeeplus.common.oss;
+
+import com.aliyun.oss.event.ProgressEvent;
+import com.aliyun.oss.event.ProgressEventType;
+import com.aliyun.oss.event.ProgressListener;
+
+public class PutObjectProgressListener implements ProgressListener {
+    private long bytesWritten = 0;
+    private long totalBytes = -1;
+    private boolean succeed = false;
+    @Override
+    public void progressChanged(ProgressEvent progressEvent) {
+        long bytes = progressEvent.getBytes();
+        ProgressEventType eventType = progressEvent.getEventType();
+        switch (eventType) {
+            case TRANSFER_STARTED_EVENT:
+                System.out.println("Start to upload......");
+                break;
+            case REQUEST_CONTENT_LENGTH_EVENT:
+                this.totalBytes = bytes;
+                System.out.println(this.totalBytes + " bytes in total will be uploaded to OSS");
+                break;
+            case REQUEST_BYTE_TRANSFER_EVENT:
+                this.bytesWritten += bytes;
+                if (this.totalBytes != -1) {
+                    int percent = (int)(this.bytesWritten * 100.0 / this.totalBytes);
+                    System.out.println(bytes + " bytes have been written at this time, upload progress: " + percent + "%(" +
+                            this.bytesWritten + "/" + this.totalBytes + ")");
+                } else {
+                    System.out.println(bytes + " bytes have been written at this time, upload ratio: unknown" + "(" + this.bytesWritten
+                            + "/...)");
+                }
+                break;
+            case TRANSFER_COMPLETED_EVENT:
+                this.succeed = true;
+                System.out.println("Succeed to upload, " + this.bytesWritten + " bytes have been transferred in total");
+                break;
+            case TRANSFER_FAILED_EVENT:
+                System.out.println("Failed to upload, " + this.bytesWritten + " bytes have been transferred");
+                break;
+            default:
+                break;
+        }
+    }
+    public boolean isSucceed() {
+        return succeed;
+    }
+
+}

+ 125 - 0
src/main/java/com/jeeplus/common/oss/UploadSample.java

@@ -0,0 +1,125 @@
+package com.jeeplus.common.oss;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.*;
+import com.jeeplus.common.config.Global;
+
+public class UploadSample {
+    private static String endpoint = Global.getEndpoint();
+    private static String key = "<downloadKey>";
+    private static String uploadFile = "<uploadFile>";
+    private static String accessKeyId = Global.getAccessKeyId();
+    private static String accessKeySecret = Global.getAccessKeySecret();
+    private static String bucketName = Global.getBucketName();
+    private String savePath = Global.getAliyunUrl();
+    private String avatarDir = Global.getAvatarDir();
+    private String notifyDir = Global.getNotifyDir();
+    private String reportDir = Global.getReportDir();
+    private String rqcode = Global.getRqcode();
+    private String goout = Global.getGoout();
+    private String leave = Global.getLeave();
+    private String overtimeform = Global.getOvertimeform();
+    private String sealform = Global.getSealform();
+    private String evection = Global.getEvection();
+    private String photo = Global.getPhoto();
+    private String userEvaluation = Global.getUserEvaluation();
+    private String workBidingDocument = Global.getWorkBidingDocument();
+    private String workEngineeringProject = Global.getWorkEngineeringProject();
+    private String workFullExecute = Global.getWorkFullExecute();
+    private String workFullMeetingminutes = Global.getWorkFullMeetingminutes();
+    private String workFullDesignchange = Global.getWorkFullDesignchange();
+    private String workFullConstructsheet = Global.getWorkFullConstructsheet();
+    private String workFullProprietorsheet = Global.getWorkFullProprietorsheet();
+    private String workFullSupervisorsheet = Global.getWorkFullSupervisorsheet();
+    private String workProjectReport = Global.getWorkProjectReport();
+    private String workProjectBasis = Global.getWorkProjectBasis();
+    private String workProjectRemote = Global.getWorkProjectRemote();
+    private String workProjectSummary = Global.getWorkProjectSummary();
+    private String workProjectOther = Global.getWorkProjectOther();
+    private String workVisa = Global.getWorkVisa();
+    private String Officehonor = Global.getOfficehonor();
+    private String jobResume = Global.getJobResume();
+    private String satisfaction = Global.getSatisfaction();
+    private String certificate = Global.getCertificate();
+
+
+    public static void main(String[] args) throws IOException {
+        String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
+        String accessKeyId = "LTAIi7VuxcgfJR2x";
+        String accessKeySecret = "Q9xF9V7tcnCI28ttUsP8H4GyAhZta7";
+        String bucketName = "gangwan-app";
+        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+        String key = "app-data/appfiles/932059357.war";
+        String url = "F:\\code\\jeeplus_maven\\target\\jeeplus.war";
+        List<PartETag> partETags = new ArrayList<PartETag>();
+        InputStream instream = new FileInputStream(new File(url));
+        UploadPartRequest uploadPartRequest = new UploadPartRequest();
+        uploadPartRequest.setBucketName(bucketName);
+        uploadPartRequest.setKey(key);
+        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
+        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
+        String uploadId = result.getUploadId();
+        uploadPartRequest.setUploadId(uploadId);
+        uploadPartRequest.setInputStream(instream);
+        // 设置分片大小,除最后一个分片外,其它分片要大于100KB
+        uploadPartRequest.setPartSize(100 * 1024);
+        // 设置分片号,范围是1~10000,
+        uploadPartRequest.setPartNumber(1);
+        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
+        partETags.add(uploadPartResult.getPartETag());
+
+        try {
+            // 带进度条的上传
+            ossClient.putObject(new PutObjectRequest(bucketName, key, new FileInputStream(url)).
+                    <PutObjectRequest>withProgressListener(new PutObjectProgressListener()));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        ossClient.shutdown();
+        /*OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
+
+        try {
+            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, "attachment-file/workBidingDocument/3fcf03e37b3ba0fd5c7821998e392395.doc");
+            // The local file to upload---it must exist.
+            uploadFileRequest.setUploadFile("F:\\2017082515");
+            // Sets the concurrent upload task number to 5.
+            uploadFileRequest.setTaskNum(5);
+            // Sets the part size to 1MB.
+            uploadFileRequest.setPartSize(1024 * 1024 * 1);
+            // Enables the checkpoint file. By default it's off.
+            uploadFileRequest.setEnableCheckpoint(true);
+
+            UploadFileResult uploadResult = ossClient.uploadFile(uploadFileRequest);
+
+            CompleteMultipartUploadResult multipartUploadResult =
+                    uploadResult.getMultipartUploadResult();
+            System.out.println(multipartUploadResult.getETag());
+
+        } catch (OSSException oe) {
+            System.out.println("Caught an OSSException, which means your request made it to OSS, "
+                    + "but was rejected with an error response for some reason.");
+            System.out.println("Error Message: " + oe.getErrorCode());
+            System.out.println("Error Code:       " + oe.getErrorCode());
+            System.out.println("Request ID:      " + oe.getRequestId());
+            System.out.println("Host ID:           " + oe.getHostId());
+        } catch (ClientException ce) {
+            System.out.println("Caught an ClientException, which means the client encountered "
+                    + "a serious internal problem while trying to communicate with OSS, "
+                    + "such as not being able to access the network.");
+            System.out.println("Error Message: " + ce.getMessage());
+        } catch (Throwable e) {
+            e.printStackTrace();
+        } finally {
+            ossClient.shutdown();
+        }*/
+    }
+}

+ 57 - 0
src/main/java/com/jeeplus/common/persistence/ActEntity.java

@@ -0,0 +1,57 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.modules.act.entity.Act;
+
+/**
+ * Activiti Entity类
+ * @author jeeplus
+ * @version 2013-05-28
+ */
+public abstract class ActEntity<T> extends DataEntity<T> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	protected Act act; 		// 流程任务对象
+
+	public ActEntity() {
+		super();
+	}
+	
+	public ActEntity(String id) {
+		super(id);
+	}
+	
+	@JsonIgnore
+	public Act getAct() {
+		if (act == null){
+			act = new Act();
+		}
+		return act;
+	}
+
+	public void setAct(Act act) {
+		this.act = act;
+	}
+
+	/**
+	 * 获取流程实例ID
+	 * @return
+	 */
+	public String getProcInsId() {
+		return this.getAct().getProcInsId();
+	}
+
+	/**
+	 * 设置流程实例ID
+	 * @param procInsId
+	 */
+	public void setProcInsId(String procInsId) {
+		this.getAct().setProcInsId(procInsId);
+	}
+}

+ 13 - 0
src/main/java/com/jeeplus/common/persistence/BaseDao.java

@@ -0,0 +1,13 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+/**
+ * DAO支持类实现
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+public interface BaseDao {
+
+}

+ 186 - 0
src/main/java/com/jeeplus/common/persistence/BaseEntity.java

@@ -0,0 +1,186 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.collect.Maps;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.UserUtils;
+
+/**
+ * Entity支持类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+public abstract class BaseEntity<T> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 实体编号(唯一标识)
+	 */
+	protected String id;
+	
+	/**
+	 * 当前用户
+	 */
+	protected User currentUser;
+	
+	/**
+	 * 当前实体分页对象
+	 */
+	protected Page<T> page;
+	
+	/**
+	 * 自定义SQL(SQL标识,SQL内容)
+	 */
+	protected Map<String, String> sqlMap;
+	
+	/**
+	 * 是否是新记录(默认:false),调用setIsNewRecord()设置新记录,使用自定义ID。
+	 * 设置为true后强制执行插入语句,ID不会自动生成,需从手动传入。
+	 */
+	protected boolean isNewRecord = false;
+
+	public BaseEntity() {
+		
+	}
+	
+	public BaseEntity(String id) {
+		this();
+		this.id = id;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+	
+	@JsonIgnore
+	@XmlTransient
+	public User getCurrentUser() {
+		if(currentUser == null){
+			currentUser = UserUtils.getUser();
+		}else {
+			if(currentUser.getBranchOffice()==null ||StringUtils.isBlank(currentUser.getBranchOffice().getId())){
+				currentUser = UserUtils.getUser();
+			}
+		}
+		return currentUser;
+	}
+	
+	public void setCurrentUser(User currentUser) {
+		this.currentUser = currentUser;
+	}
+
+	@JsonIgnore
+	@XmlTransient
+	public Page<T> getPage() {
+		if (page == null){
+			page = new Page<T>();
+		}
+		return page;
+	}
+	
+	public Page<T> setPage(Page<T> page) {
+		this.page = page;
+		return page;
+	}
+
+	@JsonIgnore
+	@XmlTransient
+	public Map<String, String> getSqlMap() {
+		if (sqlMap == null){
+			sqlMap = Maps.newHashMap();
+		}
+		return sqlMap;
+	}
+
+	public void setSqlMap(Map<String, String> sqlMap) {
+		this.sqlMap = sqlMap;
+	}
+	
+	/**
+	 * 插入之前执行方法,子类实现
+	 */
+	public abstract void preInsert();
+	
+	/**
+	 * 更新之前执行方法,子类实现
+	 */
+	public abstract void preUpdate();
+	
+    /**
+	 * 是否是新记录(默认:false),调用setIsNewRecord()设置新记录,使用自定义ID。
+	 * 设置为true后强制执行插入语句,ID不会自动生成,需从手动传入。
+     * @return
+     */
+	public boolean getIsNewRecord() {
+        return isNewRecord || StringUtils.isBlank(getId());
+    }
+
+	/**
+	 * 是否是新记录(默认:false),调用setIsNewRecord()设置新记录,使用自定义ID。
+	 * 设置为true后强制执行插入语句,ID不会自动生成,需从手动传入。
+	 */
+	public void setIsNewRecord(boolean isNewRecord) {
+		this.isNewRecord = isNewRecord;
+	}
+
+	/**
+	 * 全局变量对象
+	 */
+	@JsonIgnore
+	public Global getGlobal() {
+		return Global.getInstance();
+	}
+	
+	/**
+	 * 获取数据库名称
+	 */
+	@JsonIgnore
+	public String getDbName(){
+		return Global.getConfig("jdbc.type");
+	}
+	
+    @Override
+    public boolean equals(Object obj) {
+        if (null == obj) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (!getClass().equals(obj.getClass())) {
+            return false;
+        }
+        BaseEntity<?> that = (BaseEntity<?>) obj;
+        return null == this.getId() ? false : this.getId().equals(that.getId());
+    }
+    
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilder.toString(this);
+    }
+    
+	/**
+	 * 删除标记(0:正常;1:删除;2:审核;)
+	 */
+	public static final String DEL_FLAG_NORMAL = "0";
+	public static final String DEL_FLAG_DELETE = "1";
+	public static final String DEL_FLAG_AUDIT = "2";
+	
+}

+ 118 - 0
src/main/java/com/jeeplus/common/persistence/CrudDao.java

@@ -0,0 +1,118 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * DAO支持类实现
+ * @author jeeplus
+ * @version 2014-05-16
+ * @param <T>
+ */
+public interface CrudDao<T> extends BaseDao {
+
+	/**
+	 * 获取单条数据
+	 * @param id
+	 * @return
+	 */
+	public T get(String id);
+	
+	/**
+	 * 获取单条数据
+	 * @param entity
+	 * @return
+	 */
+	public T get(T entity);
+	
+	/**
+	 * 根据实体名称和字段名称和字段值获取唯一记录
+	 * 
+	 * @param <T>
+	 * @param entityClass
+	 * @param propertyName
+	 * @param value
+	 * @return
+	 */
+	public  T findUniqueByProperty(@Param(value="propertyName")String propertyName, @Param(value="value")Object value);
+
+	
+	/**
+	 * 查询数据列表,如果需要分页,请设置分页对象,如:entity.setPage(new Page<T>());
+	 * @param entity
+	 * @return
+	 */
+	public List<T> findList(T entity);
+
+	/**
+	 *  查询数据列表,针对普通用户,普通用户只能查看自己的记录
+	 * @param entity
+	 * @return
+	 */
+	public List<T> findListByUser(T entity);
+	/**
+	 * 查询所有数据列表
+	 * @param entity
+	 * @return
+	 */
+	public List<T> findAllList(T entity);
+	
+	/**
+	 * 查询所有数据列表
+	 * @see public List<T> findAllList(T entity)
+	 * @return
+	 */
+	@Deprecated
+	public List<T> findAllList();
+	
+	/**
+	 * 插入数据
+	 * @param entity
+	 * @return
+	 */
+	public int insert(T entity);
+	
+	/**
+	 * 更新数据
+	 * @param entity
+	 * @return
+	 */
+	public int update(T entity);
+	
+	/**
+	 * 删除数据(物理删除,从数据库中彻底删除)
+	 * @param id
+	 * @see public int delete(T entity)
+	 * @return
+	 */
+	@Deprecated
+	public int delete(String id);
+	
+	/**
+	 * 删除数据(逻辑删除,更新del_flag字段为1,在表包含字段del_flag时,可以调用此方法,将数据隐藏)
+	 * @param id
+	 * @see public int delete(T entity)
+	 * @return
+	 */
+	@Deprecated
+	public int deleteByLogic(String id);
+	
+	/**
+	 * 删除数据(物理删除,从数据库中彻底删除)
+	 * @param entity
+	 * @return
+	 */
+	public int delete(T entity);
+	
+	/**
+	 * 删除数据(逻辑删除,更新del_flag字段为1,在表包含字段del_flag时,可以调用此方法,将数据隐藏)
+	 * @param entity
+	 * @return
+	 */
+	public int deleteByLogic(T entity);
+	
+}

+ 127 - 0
src/main/java/com/jeeplus/common/persistence/DataEntity.java

@@ -0,0 +1,127 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.util.Date;
+
+import org.apache.commons.lang3.StringUtils;
+import org.hibernate.validator.constraints.Length;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.utils.IdGen;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.utils.UserUtils;
+
+/**
+ * 数据Entity类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+public abstract class DataEntity<T> extends BaseEntity<T> {
+
+	private static final long serialVersionUID = 1L;
+	
+	protected String remarks;	// 备注
+	protected User createBy;	// 创建者
+	protected Date createDate;	// 创建日期
+	protected User updateBy;	// 更新者
+	protected Date updateDate;	// 更新日期
+	protected String delFlag; 	// 删除标记(0:正常;1:删除;2:审核)
+
+	public DataEntity() {
+		super();
+		this.delFlag = DEL_FLAG_NORMAL;
+	}
+	
+	public DataEntity(String id) {
+		super(id);
+	}
+	
+	/**
+	 * 插入之前执行方法,需要手动调用
+	 */
+	@Override
+	public void preInsert(){
+		// 不限制ID为UUID,调用setIsNewRecord()使用自定义ID
+		if (!this.isNewRecord){
+			setId(IdGen.uuid());
+		}
+		User user = UserUtils.getUser();
+		if (StringUtils.isNotBlank(user.getId())){
+			this.updateBy = user;
+			this.createBy = user;
+		}
+		this.updateDate = new Date();
+		this.createDate = this.updateDate;
+	}
+	
+	/**
+	 * 更新之前执行方法,需要手动调用
+	 */
+	@Override
+	public void preUpdate(){
+		User user = UserUtils.getUser();
+		if (StringUtils.isNotBlank(user.getId())){
+			this.updateBy = user;
+		}
+		this.updateDate = new Date();
+	}
+	
+	@Length(min=0, max=255)
+	public String getRemarks() {
+		return remarks;
+	}
+
+	public void setRemarks(String remarks) {
+		this.remarks = remarks;
+	}
+	
+	@JsonIgnore
+	public User getCreateBy() {
+		return createBy;
+	}
+
+	public void setCreateBy(User createBy) {
+		this.createBy = createBy;
+	}
+
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	public Date getCreateDate() {
+		return createDate;
+	}
+
+	public void setCreateDate(Date createDate) {
+		this.createDate = createDate;
+	}
+
+	@JsonIgnore
+	public User getUpdateBy() {
+		return updateBy;
+	}
+
+	public void setUpdateBy(User updateBy) {
+		this.updateBy = updateBy;
+	}
+
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	public Date getUpdateDate() {
+		return updateDate;
+	}
+
+	public void setUpdateDate(Date updateDate) {
+		this.updateDate = updateDate;
+	}
+
+	@JsonIgnore
+	@Length(min=1, max=1)
+	public String getDelFlag() {
+		return delFlag;
+	}
+
+	public void setDelFlag(String delFlag) {
+		this.delFlag = delFlag;
+	}
+
+}

+ 212 - 0
src/main/java/com/jeeplus/common/persistence/MapperLoader.java

@@ -0,0 +1,212 @@
+package com.jeeplus.common.persistence;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.mapper.MapperScannerConfigurer;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.NestedIOException;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Mybatis的mapper文件中的sql语句被修改后, 只能重启服务器才能被加载, 非常耗时,所以就写了一个自动加载的类,
+ * 配置后检查xml文件更改,如果发生变化,重新加载xml里面的内容.
+ */
+//@Service
+//@Lazy(false)
+public class MapperLoader implements DisposableBean, InitializingBean, ApplicationContextAware {
+
+	private ConfigurableApplicationContext context = null;
+	private transient String basePackage = null;
+	private HashMap<String, String> fileMapping = new HashMap<String, String>();
+	private Scanner scanner = null;
+	private ScheduledExecutorService service = null;
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		this.context = (ConfigurableApplicationContext) applicationContext;
+
+	}
+
+	@Override
+	public void afterPropertiesSet() throws Exception {
+		try {
+			service = Executors.newScheduledThreadPool(1);
+			
+			// 获取xml所在包
+			MapperScannerConfigurer config = context.getBean(MapperScannerConfigurer.class);
+			Field field = config.getClass().getDeclaredField("basePackage");
+			field.setAccessible(true);
+			basePackage = (String) field.get(config);
+			
+			// 触发文件监听事件
+			scanner = new Scanner();
+			scanner.scan();
+
+			service.scheduleAtFixedRate(new Task(), 5, 5, TimeUnit.SECONDS);
+
+		} catch (Exception e1) {
+			e1.printStackTrace();
+		}
+
+	}
+
+	class Task implements Runnable {
+		@Override
+		public void run() {
+			try {
+				if (scanner.isChanged()) {
+					System.out.println("*Mapper.xml文件改变,重新加载.");
+					scanner.reloadXML();
+					System.out.println("加载完毕.");
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+
+	}
+
+	@SuppressWarnings({ "rawtypes" })
+	class Scanner {
+		
+		private String[] basePackages;
+		private static final String XML_RESOURCE_PATTERN = "**/*.xml";
+		private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+		public Scanner() {
+			basePackages = StringUtils.tokenizeToStringArray(MapperLoader.this.basePackage,
+					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
+		}
+
+		public Resource[] getResource(String basePackage, String pattern) throws IOException {
+			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+					+ ClassUtils.convertClassNameToResourcePath(context.getEnvironment().resolveRequiredPlaceholders(
+							basePackage)) + "/" + pattern;
+			Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
+			return resources;
+		}
+
+		public void reloadXML() throws Exception {
+			SqlSessionFactory factory = context.getBean(SqlSessionFactory.class);
+			Configuration configuration = factory.getConfiguration();
+			// 移除加载项
+			removeConfig(configuration);
+			// 重新扫描加载
+			for (String basePackage : basePackages) {
+				Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
+				if (resources != null) {
+					for (int i = 0; i < resources.length; i++) {
+						if (resources[i] == null) {
+							continue;
+						}
+						try {
+							XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resources[i].getInputStream(),
+									configuration, resources[i].toString(), configuration.getSqlFragments());
+							xmlMapperBuilder.parse();
+						} catch (Exception e) {
+							throw new NestedIOException("Failed to parse mapping resource: '" + resources[i] + "'", e);
+						} finally {
+							ErrorContext.instance().reset();
+						}
+					}
+				}
+			}
+
+		}
+
+		private void removeConfig(Configuration configuration) throws Exception {
+			Class<?> classConfig = configuration.getClass();
+			clearMap(classConfig, configuration, "mappedStatements");
+			clearMap(classConfig, configuration, "caches");
+			clearMap(classConfig, configuration, "resultMaps");
+			clearMap(classConfig, configuration, "parameterMaps");
+			clearMap(classConfig, configuration, "keyGenerators");
+			clearMap(classConfig, configuration, "sqlFragments");
+
+			clearSet(classConfig, configuration, "loadedResources");
+
+		}
+
+		private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
+			Field field = classConfig.getDeclaredField(fieldName);
+			field.setAccessible(true);
+			Map mapConfig = (Map) field.get(configuration);
+			mapConfig.clear();
+		}
+
+		private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
+			Field field = classConfig.getDeclaredField(fieldName);
+			field.setAccessible(true);
+			Set setConfig = (Set) field.get(configuration);
+			setConfig.clear();
+		}
+
+		public void scan() throws IOException {
+			if (!fileMapping.isEmpty()) {
+				return;
+			}
+			for (String basePackage : basePackages) {
+				Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
+				if (resources != null) {
+					for (int i = 0; i < resources.length; i++) {
+						String multi_key = getValue(resources[i]);
+						fileMapping.put(resources[i].getFilename(), multi_key);
+					}
+				}
+			}
+		}
+
+		private String getValue(Resource resource) throws IOException {
+			String contentLength = String.valueOf((resource.contentLength()));
+			String lastModified = String.valueOf((resource.lastModified()));
+			return new StringBuilder(contentLength).append(lastModified).toString();
+		}
+
+		public boolean isChanged() throws IOException {
+			boolean isChanged = false;
+			for (String basePackage : basePackages) {
+				Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN);
+				if (resources != null) {
+					for (int i = 0; i < resources.length; i++) {
+						String name = resources[i].getFilename();
+						String value = fileMapping.get(name);
+						String multi_key = getValue(resources[i]);
+						if (!multi_key.equals(value)) {
+							isChanged = true;
+							fileMapping.put(name, multi_key);
+						}
+					}
+				}
+			}
+			return isChanged;
+		}
+	}
+
+	@Override
+	public void destroy() throws Exception {
+		if (service != null) {
+			service.shutdownNow();
+		}
+	}
+
+}

+ 623 - 0
src/main/java/com/jeeplus/common/persistence/Page.java

@@ -0,0 +1,623 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.CookieUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * 分页类
+ * @author jeeplus
+ * @version 2013-7-2
+ * @param <T>
+ */
+public class Page<T> {
+	
+	protected int pageNo = 1; // 当前页码
+	protected int pageSize = Integer.valueOf(Global.getConfig("page.pageSize")); // 页面大小,设置为“-1”表示不进行分页(分页无效)
+	
+	protected long count;// 总记录数,设置为“-1”表示不查询总数
+    protected Boolean countFlag = true; //是否自动查询总数
+	
+	protected int first;// 首页索引
+	protected int last;// 尾页索引
+	protected int prev;// 上一页索引
+	protected int next;// 下一页索引
+	
+	private boolean firstPage;//是否是第一页
+	private boolean lastPage;//是否是最后一页
+
+	protected int length = 8;// 显示页面长度
+	protected int slider = 1;// 前后显示页面长度
+	
+	private List<T> list = new ArrayList<T>();
+	
+	private String orderBy = ""; // 标准查询有效, 实例: updatedate desc, name asc
+
+	protected String funcName = "page"; // 设置点击页码调用的js函数名称,默认为page,在一页有多个分页对象时使用。
+	
+	protected String funcParam = ""; // 函数的附加参数,第三个参数值。
+	
+	private String message = ""; // 设置提示消息,显示在“共n条”之后
+
+	public Page() {
+		this.pageSize = -1;
+	}
+	
+	/**
+	 * 构造方法
+	 * @param request 传递 repage 参数,来记住页码
+	 * @param response 用于设置 Cookie,记住页码
+	 */
+	public Page(HttpServletRequest request, HttpServletResponse response){
+		this(request, response, -2);
+	}
+
+	/**
+	 * 构造方法
+	 * @param request 传递 repage 参数,来记住页码
+	 * @param response 用于设置 Cookie,记住页码
+	 * @param defaultPageSize 默认分页大小,如果传递 -1 则为不分页,返回所有数据
+	 */
+	public Page(HttpServletRequest request, HttpServletResponse response, int defaultPageSize){
+		// 设置页码参数(传递repage参数,来记住页码)
+		String no = request.getParameter("pageNo");
+		if (StringUtils.isNumeric(no)){
+			CookieUtils.setCookie(response, "pageNo", no);
+			this.setPageNo(Integer.parseInt(no));
+		}else if (request.getParameter("repage")!=null){
+			no = CookieUtils.getCookie(request, "pageNo");
+			if (StringUtils.isNumeric(no)){
+				this.setPageNo(Integer.parseInt(no));
+			}
+		}
+		// 设置页面大小参数(传递repage参数,来记住页码大小)
+		String size = request.getParameter("pageSize");
+		if (StringUtils.isNumeric(size)){
+			CookieUtils.setCookie(response, "pageSize", size);
+			this.setPageSize(Integer.parseInt(size));
+		}else if (request.getParameter("repage")!=null){
+			no = CookieUtils.getCookie(request, "pageSize");
+			if (StringUtils.isNumeric(size)){
+				this.setPageSize(Integer.parseInt(size));
+			}
+		}else if (defaultPageSize != -2){
+			this.pageSize = defaultPageSize;
+		}
+		// 设置排序参数
+		String orderBy = request.getParameter("orderBy");
+		if (StringUtils.isNotBlank(orderBy)){
+			this.setOrderBy(orderBy);
+		}
+	}
+	
+	/**
+	 * 构造方法
+	 * @param pageNo 当前页码
+	 * @param pageSize 分页大小
+	 */
+	public Page(int pageNo, int pageSize) {
+		this(pageNo, pageSize, 0);
+	}
+	
+	/**
+	 * 构造方法
+	 * @param pageNo 当前页码
+	 * @param pageSize 分页大小
+	 * @param count 数据条数
+	 */
+	public Page(int pageNo, int pageSize, long count) {
+		this(pageNo, pageSize, count, new ArrayList<T>());
+	}
+	
+	/**
+	 * 构造方法
+	 * @param pageNo 当前页码
+	 * @param pageSize 分页大小
+	 * @param count 数据条数
+	 * @param list 本页数据对象列表
+	 */
+	public Page(int pageNo, int pageSize, long count, List<T> list) {
+		this.setCount(count);
+		this.setPageNo(pageNo);
+		this.pageSize = pageSize;
+		this.list = list;
+	}
+	
+	/**
+	 * 初始化参数
+	 */
+	public void initialize(){
+				
+		//1
+		this.first = 1;
+		
+		this.last = (int)(count / (this.pageSize < 1 ? 20 : this.pageSize) + first - 1);
+		
+		if (this.count % this.pageSize != 0 || this.last == 0) {
+			this.last++;
+		}
+
+		if (this.last < this.first) {
+			this.last = this.first;
+		}
+		
+		if (this.pageNo <= 1) {
+			this.pageNo = this.first;
+			this.firstPage=true;
+		}
+
+		if (this.pageNo >= this.last) {
+			this.pageNo = this.last;
+			this.lastPage=true;
+		}
+
+		if (this.pageNo < this.last - 1) {
+			this.next = this.pageNo + 1;
+		} else {
+			this.next = this.last;
+		}
+
+		if (this.pageNo > 1) {
+			this.prev = this.pageNo - 1;
+		} else {
+			this.prev = this.first;
+		}
+		
+		//2
+		if (this.pageNo < this.first) {// 如果当前页小于首页
+			this.pageNo = this.first;
+		}
+
+		if (this.pageNo > this.last) {// 如果当前页大于尾页
+			this.pageNo = this.last;
+		}
+		
+	}
+	
+	/**
+	 * 默认输出当前分页标签 
+	 * <div class="page">${page}</div>
+	 */
+	@Override
+	public String toString() {
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("<div class=\"fixed-table-pagination\" style=\"display: block;\">");
+//		sb.append("<div class=\"dataTables_info\">");
+//		sb.append("<li class=\"disabled controls\"><a href=\"javascript:\">当前 ");
+//		sb.append("<input type=\"text\" value=\""+pageNo+"\" onkeypress=\"var e=window.event||this;var c=e.keyCode||e.which;if(c==13)");
+//		sb.append(funcName+"(this.value,"+pageSize+",'"+funcParam+"');\" onclick=\"this.select();\"/> / ");
+//		sb.append("<input type=\"text\" value=\""+pageSize+"\" onkeypress=\"var e=window.event||this;var c=e.keyCode||e.which;if(c==13)");
+//		sb.append(funcName+"("+pageNo+",this.value,'"+funcParam+"');\" onclick=\"this.select();\"/> 条,");
+//		sb.append("共 " + count + " 条"+(message!=null?message:"")+"</a></li>\n");
+//		sb.append("</div>");
+		long startIndex = (pageNo-1)*pageSize + 1;
+		long endIndex = pageNo*pageSize <=count? pageNo*pageSize:count;
+		
+		sb.append("<div class=\"pull-left pagination-detail\">");
+		sb.append("<span class=\"pagination-info\">显示第 "+startIndex+" 到第 "+ endIndex +" 条记录,总共 "+count+" 条记录</span>");
+		sb.append("<span class=\"page-list\">每页显示 <span class=\"btn-group dropup\">");
+		sb.append("<button type=\"button\" class=\"btn btn-default  btn-outline dropdown-toggle\" data-toggle=\"dropdown\" aria-expanded=\"false\">");
+		sb.append("<span class=\"page-size\">"+pageSize+"</span> <span class=\"caret\"></span>");
+		sb.append("</button>");
+		sb.append("<ul class=\"dropdown-menu\" role=\"menu\">");
+		sb.append("<li class=\""+getSelected(pageSize,10)+ "\"><a href=\"javascript:"+funcName+"("+pageNo+",10,'"+funcParam+"');\">10</a></li>");
+		sb.append("<li class=\""+getSelected(pageSize,25)+ "\"><a href=\"javascript:"+funcName+"("+pageNo+",25,'"+funcParam+"');\">25</a></li>");
+		sb.append("<li class=\""+getSelected(pageSize,50)+ "\"><a href=\"javascript:"+funcName+"("+pageNo+",50,'"+funcParam+"');\">50</a></li>");
+		sb.append("<li class=\""+getSelected(pageSize,100)+ "\"><a href=\"javascript:"+funcName+"("+pageNo+",100,'"+funcParam+"');\">100</a></li>");
+		sb.append("</ul>");
+		sb.append("</span> 条记录</span>");
+		sb.append("</div>");
+//		sb.append("<p>每页 <select onChange=\""+funcName+"("+pageNo+",this.value,'"+funcParam+"');\"" +"style=\"display:display  !important;\" class=\"form-control m-b input-sm\">" +
+//		        "<option value=\"10\" "+getSelected(pageSize,10)+ ">10</option>" +
+//				"<option value=\"25\" "+getSelected(pageSize,25)+ ">25</option>" +
+//				"<option value=\"50\" "+getSelected(pageSize,50)+ ">50</option>" +
+//				"<option value=\"100\" "+getSelected(pageSize,100)+ ">100</option>" +
+//				"</select> 条记录,显示 " +startIndex+ " 到 "+ endIndex +" 条,共 "+count+" 条</p>");
+//		sb.append("</div>");
+//		sb.append("</div>");
+		
+		
+		
+		
+		sb.append("<div class=\"pull-right pagination-roll\">");
+		sb.append("<ul class=\"pagination pagination-outline\">");
+		if (pageNo == first) {// 如果是首页
+			sb.append("<li class=\"paginate_button previous disabled\"><a href=\"javascript:\"><i class=\"fa fa-angle-double-left\"></i></a></li>\n");
+			sb.append("<li class=\"paginate_button previous disabled\"><a href=\"javascript:\"><i class=\"fa fa-angle-left\"></i></a></li>\n");
+		} else {
+			sb.append("<li class=\"paginate_button previous\"><a href=\"javascript:\" onclick=\""+funcName+"("+first+","+pageSize+",'"+funcParam+"');\"><i class=\"fa fa-angle-double-left\"></i></a></li>\n");
+			sb.append("<li class=\"paginate_button previous\"><a href=\"javascript:\" onclick=\""+funcName+"("+prev+","+pageSize+",'"+funcParam+"');\"><i class=\"fa fa-angle-left\"></i></a></li>\n");
+		}
+
+		int begin = pageNo - (length / 2);
+
+		if (begin < first) {
+			begin = first;
+		}
+
+		int end = begin + length - 1;
+
+		if (end >= last) {
+			end = last;
+			begin = end - length + 1;
+			if (begin < first) {
+				begin = first;
+			}
+		}
+
+		if (begin > first) {
+			int i = 0;
+			for (i = first; i < first + slider && i < begin; i++) {
+				sb.append("<li class=\"paginate_button \"><a href=\"javascript:\" onclick=\""+funcName+"("+i+","+pageSize+",'"+funcParam+"');\">"
+						+ (i + 1 - first) + "</a></li>\n");
+			}
+			if (i < begin) {
+				sb.append("<li class=\"paginate_button disabled\"><a href=\"javascript:\">...</a></li>\n");
+			}
+		}
+
+		for (int i = begin; i <= end; i++) {
+			if (i == pageNo) {
+				sb.append("<li class=\"paginate_button active\"><a href=\"javascript:\">" + (i + 1 - first)
+						+ "</a></li>\n");
+			} else {
+				sb.append("<li class=\"paginate_button \"><a href=\"javascript:\" onclick=\""+funcName+"("+i+","+pageSize+",'"+funcParam+"');\">"
+						+ (i + 1 - first) + "</a></li>\n");
+			}
+		}
+
+		if (last - end > slider) {
+			sb.append("<li class=\"paginate_button disabled\"><a href=\"javascript:\">...</a></li>\n");
+			end = last - slider;
+		}
+
+		for (int i = end + 1; i <= last; i++) {
+			sb.append("<li class=\"paginate_button \"><a href=\"javascript:\" onclick=\""+funcName+"("+i+","+pageSize+",'"+funcParam+"');\">"
+					+ (i + 1 - first) + "</a></li>\n");
+		}
+
+		if (pageNo == last) {
+			sb.append("<li class=\"paginate_button next disabled\"><a href=\"javascript:\"><i class=\"fa fa-angle-right\"></i></a></li>\n");
+			sb.append("<li class=\"paginate_button next disabled\"><a href=\"javascript:\"><i class=\"fa fa-angle-double-right\"></i></a></li>\n");
+		} else {
+			sb.append("<li class=\"paginate_button next\"><a href=\"javascript:\" onclick=\""+funcName+"("+next+","+pageSize+",'"+funcParam+"');\">"
+					+ "<i class=\"fa fa-angle-right\"></i></a></li>\n");
+			sb.append("<li class=\"paginate_button next\"><a href=\"javascript:\" onclick=\""+funcName+"("+last+","+pageSize+",'"+funcParam+"');\">"
+					+ "<i class=\"fa fa-angle-double-right\"></i></a></li>\n");
+		}
+
+		
+        sb.append("</ul>");
+        sb.append("</div>");
+        sb.append("</div>");
+//		sb.insert(0,"<ul>\n").append("</ul>\n");
+		
+//		sb.append("<div style=\"clear:both;\"></div>");
+
+//		sb.insert(0,"<div class=\"page\">\n").append("</div>\n");
+		
+		return sb.toString();
+	}
+	
+	protected String getSelected(int pageNo, int selectedPageNo){
+		if(pageNo == selectedPageNo){
+			//return "selected";
+			return "active";
+		}else{
+			return "";
+		}
+		
+	}
+	/**
+	 * 获取分页HTML代码
+	 * @return
+	 */
+	public String getHtml(){
+		return toString();
+	}
+	
+//	public static void main(String[] args) {
+//		Page<String> p = new Page<String>(3, 3);
+//		System.out.println(p);
+//		System.out.println("首页:"+p.getFirst());
+//		System.out.println("尾页:"+p.getLast());
+//		System.out.println("上页:"+p.getPrev());
+//		System.out.println("下页:"+p.getNext());
+//	}
+
+	/**
+	 * 获取设置总数
+	 * @return
+	 */
+	public long getCount() {
+		return count;
+	}
+
+	/**
+	 * 设置数据总数
+	 * @param count
+	 */
+	public void setCount(long count) {
+		this.count = count;
+		if (pageSize >= count){
+			pageNo = 1;
+		}
+	}
+	
+	/**
+	 * 获取当前页码
+	 * @return
+	 */
+	public int getPageNo() {
+		return pageNo;
+	}
+	
+	/**
+	 * 设置当前页码
+	 * @param pageNo
+	 */
+	public void setPageNo(int pageNo) {
+		this.pageNo = pageNo;
+	}
+	
+	/**
+	 * 获取页面大小
+	 * @return
+	 */
+	public int getPageSize() {
+		return pageSize;
+	}
+
+	/**
+	 * 设置页面大小(最大500)
+	 * @param pageSize
+	 */
+	public void setPageSize(int pageSize) {
+		this.pageSize = pageSize <= 0 ? 10 : pageSize;// > 500 ? 500 : pageSize;
+	}
+
+	/**
+	 * 首页索引
+	 * @return
+	 */
+	@JsonIgnore
+	public int getFirst() {
+		return first;
+	}
+
+	/**
+	 * 尾页索引
+	 * @return
+	 */
+	@JsonIgnore
+	public int getLast() {
+		return last;
+	}
+	
+	/**
+	 * 获取页面总数
+	 * @return getLast();
+	 */
+	@JsonIgnore
+	public int getTotalPage() {
+		return getLast();
+	}
+
+	/**
+	 * 是否为第一页
+	 * @return
+	 */
+	@JsonIgnore
+	public boolean isFirstPage() {
+		return firstPage;
+	}
+
+	/**
+	 * 是否为最后一页
+	 * @return
+	 */
+	@JsonIgnore
+	public boolean isLastPage() {
+		return lastPage;
+	}
+	
+	/**
+	 * 上一页索引值
+	 * @return
+	 */
+	@JsonIgnore
+	public int getPrev() {
+		if (isFirstPage()) {
+			return pageNo;
+		} else {
+			return pageNo - 1;
+		}
+	}
+
+	/**
+	 * 下一页索引值
+	 * @return
+	 */
+	@JsonIgnore
+	public int getNext() {
+		if (isLastPage()) {
+			return pageNo;
+		} else {
+			return pageNo + 1;
+		}
+	}
+	
+	/**
+	 * 获取本页数据对象列表
+	 * @return List<T>
+	 */
+	public List<T> getList() {
+		return list;
+	}
+
+	/**
+	 * 设置本页数据对象列表
+	 * @param list
+	 */
+	public Page<T> setList(List<T> list) {
+		this.list = list;
+		initialize();
+		return this;
+	}
+
+	/**
+	 * 获取查询排序字符串
+	 * @return
+	 */
+	@JsonIgnore
+	public String getOrderBy() {
+		// SQL过滤,防止注入 
+		String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|"
+					+ "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
+		Pattern sqlPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
+		if (sqlPattern.matcher(orderBy).find()) {
+			return "";
+		}
+		return orderBy;
+	}
+
+	/**
+	 * 设置查询排序,标准查询有效, 实例: updatedate desc, name asc
+	 */
+	public void setOrderBy(String orderBy) {
+		this.orderBy = orderBy;
+	}
+
+	/**
+	 * 获取点击页码调用的js函数名称
+	 * function ${page.funcName}(pageNo){location="${ctx}/list-${category.id}${urlSuffix}?pageNo="+i;}
+	 * @return
+	 */
+	@JsonIgnore
+	public String getFuncName() {
+		return funcName;
+	}
+
+	/**
+	 * 设置点击页码调用的js函数名称,默认为page,在一页有多个分页对象时使用。
+	 * @param funcName 默认为page
+	 */
+	public void setFuncName(String funcName) {
+		this.funcName = funcName;
+	}
+
+	/**
+	 * 获取分页函数的附加参数
+	 * @return
+	 */
+	@JsonIgnore
+	public String getFuncParam() {
+		return funcParam;
+	}
+
+	/**
+	 * 设置分页函数的附加参数
+	 * @return
+	 */
+	public void setFuncParam(String funcParam) {
+		this.funcParam = funcParam;
+	}
+
+	/**
+	 * 设置提示消息,显示在“共n条”之后
+	 * @param message
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+	
+	/**
+	 * 分页是否有效
+	 * @return this.pageSize==-1
+	 */
+	@JsonIgnore
+	public boolean isDisabled() {
+		return this.pageSize==-1;
+	}
+	
+	/**
+	 * 是否进行总数统计
+	 * @return this.count==-1
+	 */
+	@JsonIgnore
+	public boolean isNotCount() {
+		return this.count==-1;
+	}
+	
+	/**
+	 * 获取 Hibernate FirstResult
+	 */
+	public int getFirstResult(){
+		int firstResult = (getPageNo() - 1) * getPageSize();
+		if (firstResult >= getCount() || firstResult<0) {
+			firstResult = 0;
+		}
+		return firstResult;
+	}
+	/**
+	 * 获取 Hibernate MaxResults
+	 */
+	public int getMaxResults(){
+		return getPageSize();
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+    public Boolean getCountFlag() {
+        return countFlag;
+    }
+
+    public void setCountFlag(Boolean countFlag) {
+        this.countFlag = countFlag;
+    }
+//	/**
+//	 * 获取 Spring data JPA 分页对象
+//	 */
+//	public Pageable getSpringPage(){
+//		List<Order> orders = new ArrayList<Order>();
+//		if (orderBy!=null){
+//			for (String order : StringUtils.split(orderBy, ",")){
+//				String[] o = StringUtils.split(order, " ");
+//				if (o.length==1){
+//					orders.add(new Order(Direction.ASC, o[0]));
+//				}else if (o.length==2){
+//					if ("DESC".equals(o[1].toUpperCase())){
+//						orders.add(new Order(Direction.DESC, o[0]));
+//					}else{
+//						orders.add(new Order(Direction.ASC, o[0]));
+//					}
+//				}
+//			}
+//		}
+//		return new PageRequest(this.pageNo - 1, this.pageSize, new Sort(orders));
+//	}
+//	
+//	/**
+//	 * 设置 Spring data JPA 分页对象,转换为本系统分页对象
+//	 */
+//	public void setSpringPage(org.springframework.data.domain.Page<T> page){
+//		this.pageNo = page.getNumber();
+//		this.pageSize = page.getSize();
+//		this.count = page.getTotalElements();
+//		this.list = page.getContent();
+//	}
+	
+}

+ 43 - 0
src/main/java/com/jeeplus/common/persistence/Parameter.java

@@ -0,0 +1,43 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.util.HashMap;
+
+/**
+ * 查询参数类
+ * @author jeeplus
+ * @version 2013-8-23
+ */
+public class Parameter extends HashMap<String, Object> {
+	
+	private static final long serialVersionUID = 1L;
+	
+	/**
+	 * 构造类,例:new Parameter(id, parentIds)
+	 * @param values 参数值
+	 */
+	public Parameter(Object... values) {
+		if (values != null){
+			for (int i=0; i<values.length; i++){
+				put("p"+(i+1), values[i]);
+			}
+		}
+	}
+	
+	/**
+	 * 构造类,例:new Parameter(new Object[][]{{"id", id}, {"parentIds", parentIds}})
+	 * @param parameters 参数二维数组
+	 */
+	public Parameter(Object[][] parameters) {
+		if (parameters != null){
+			for (Object[] os : parameters){
+				if (os.length == 2){
+					put((String)os[0], os[1]);
+				}
+			}
+		}
+	}
+	
+}

+ 32 - 0
src/main/java/com/jeeplus/common/persistence/TreeDao.java

@@ -0,0 +1,32 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import java.util.List;
+
+/**
+ * DAO支持类实现
+ * @author jeeplus
+ * @version 2014-05-16
+ * @param <T>
+ */
+public interface TreeDao<T extends TreeEntity<T>> extends CrudDao<T> {
+
+	/**
+	 * 找到所有子节点
+	 * @param entity
+	 * @return
+	 */
+	public List<T> findByParentIdsLike(T entity);
+
+	public List<T> findByParent(T entity);
+
+	/**
+	 * 更新所有父节点字段
+	 * @param entity
+	 * @return
+	 */
+	public int updateParentIds(T entity);
+	
+}

+ 85 - 0
src/main/java/com/jeeplus/common/persistence/TreeEntity.java

@@ -0,0 +1,85 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence;
+
+import javax.validation.constraints.NotNull;
+
+import org.hibernate.validator.constraints.Length;
+
+import com.fasterxml.jackson.annotation.JsonBackReference;
+import com.jeeplus.common.utils.Reflections;
+import com.jeeplus.common.utils.StringUtils;
+
+/**
+ * 数据Entity类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+public abstract class TreeEntity<T> extends DataEntity<T> {
+
+	private static final long serialVersionUID = 1L;
+
+	protected T parent;	// 父级编号
+	protected String parentIds; // 所有父级编号
+	protected String name; 	// 机构名称
+	protected Integer sort;		// 排序
+	
+	public TreeEntity() {
+		super();
+		this.sort = 30;
+	}
+	
+	public TreeEntity(String id) {
+		super(id);
+	}
+	
+	/**
+	 * 父对象,只能通过子类实现,父类实现mybatis无法读取
+	 * @return
+	 */
+	@JsonBackReference
+	@NotNull
+	public abstract T getParent();
+
+	/**
+	 * 父对象,只能通过子类实现,父类实现mybatis无法读取
+	 * @return
+	 */
+	public abstract void setParent(T parent);
+
+	@Length(min=1, max=2000)
+	public String getParentIds() {
+		return parentIds;
+	}
+
+	public void setParentIds(String parentIds) {
+		this.parentIds = parentIds;
+	}
+
+	@Length(min=1, max=100)
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Integer getSort() {
+		return sort;
+	}
+
+	public void setSort(Integer sort) {
+		this.sort = sort;
+	}
+	
+	public String getParentId() {
+		String id = null;
+		if (parent != null){
+			id = (String)Reflections.getFieldValue(parent, "id");
+		}
+		return StringUtils.isNotBlank(id) ? id : "0";
+	}
+	
+}

+ 32 - 0
src/main/java/com/jeeplus/common/persistence/annotation/MyBatisDao.java

@@ -0,0 +1,32 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * 标识MyBatis的DAO,方便{@link org.mybatis.spring.mapper.MapperScannerConfigurer}的扫描。 
+ * @author jeeplus
+ * @version 2013-8-28
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Component
+public @interface MyBatisDao {
+	
+	/**
+	 * The value may indicate a suggestion for a logical component name,
+	 * to be turned into a Spring bean in case of an autodetected component.
+	 * @return the suggested component name, if any
+	 */
+	String value() default "";
+
+}

+ 33 - 0
src/main/java/com/jeeplus/common/persistence/dialect/Dialect.java

@@ -0,0 +1,33 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect;
+
+/**
+ * 类似hibernate的Dialect,但只精简出分页部分
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2011-11-18 下午12:31
+ * @since JDK 1.5
+ */
+public interface Dialect {
+
+    /**
+     * 数据库本身是否支持分页当前的分页查询方式
+     * 如果数据库不支持的话,则不进行数据库分页
+     *
+     * @return true:支持当前的分页查询方式
+     */
+    public boolean supportsLimit();
+
+    /**
+     * 将sql转换为分页SQL,分别调用分页sql
+     *
+     * @param sql    SQL语句
+     * @param offset 开始条数
+     * @param limit  每页显示多少纪录条数
+     * @return 分页查询的sql
+     */
+    public String getLimitString(String sql, int offset, int limit);
+
+}

+ 89 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/DB2Dialect.java

@@ -0,0 +1,89 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * DB2的分页数据库方言实现
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class DB2Dialect implements Dialect {
+    @Override
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    private static String getRowNumber(String sql) {
+        StringBuilder rownumber = new StringBuilder(50)
+                .append("rownumber() over(");
+
+        int orderByIndex = sql.toLowerCase().indexOf("order by");
+
+        if (orderByIndex > 0 && !hasDistinct(sql)) {
+            rownumber.append(sql.substring(orderByIndex));
+        }
+
+        rownumber.append(") as rownumber_,");
+
+        return rownumber.toString();
+    }
+
+    private static boolean hasDistinct(String sql) {
+        return sql.toLowerCase().contains("select distinct");
+    }
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset, Integer.toString(offset), Integer.toString(limit));
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    public String getLimitString(String sql, int offset, String offsetPlaceholder, String limitPlaceholder) {
+        int startOfSelect = sql.toLowerCase().indexOf("select");
+
+        StringBuilder pagingSelect = new StringBuilder(sql.length() + 100)
+                .append(sql.substring(0, startOfSelect)) //add the comment
+                .append("select * from ( select ") //nest the main query in an outer select
+                .append(getRowNumber(sql)); //add the rownnumber bit into the outer query select list
+
+        if (hasDistinct(sql)) {
+            pagingSelect.append(" row_.* from ( ") //add another (inner) nested select
+                    .append(sql.substring(startOfSelect)) //add the main query
+                    .append(" ) as row_"); //close off the inner nested select
+        } else {
+            pagingSelect.append(sql.substring(startOfSelect + 6)); //add the main query
+        }
+
+        pagingSelect.append(" ) as temp_ where rownumber_ ");
+
+        //add the restriction to the outer select
+        if (offset > 0) {
+//			int end = offset + limit;
+            String endString = offsetPlaceholder + "+" + limitPlaceholder;
+            pagingSelect.append("between ").append(offsetPlaceholder)
+                    .append("+1 and ").append(endString);
+        } else {
+            pagingSelect.append("<= ").append(limitPlaceholder);
+        }
+
+        return pagingSelect.toString();
+    }
+}

+ 44 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/DerbyDialect.java

@@ -0,0 +1,44 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class DerbyDialect implements Dialect {
+    @Override
+    public boolean supportsLimit() {
+        return false;
+	}
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+//        return getLimitString(sql,offset,Integer.toString(offset),limit,Integer.toString(limit));
+        throw new UnsupportedOperationException("paged queries not supported");
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limit             分页每页显示纪录条数
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+	public String getLimitString(String sql, int offset,String offsetPlaceholder, int limit, String limitPlaceholder) {
+		throw new UnsupportedOperationException( "paged queries not supported" );
+	}
+
+}

+ 45 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/H2Dialect.java

@@ -0,0 +1,45 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * A dialect compatible with the H2 database.
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class H2Dialect implements Dialect {
+
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limit             分页每页显示纪录条数
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    private String getLimitString(String sql, int offset, String offsetPlaceholder, int limit, String limitPlaceholder) {
+        return sql + ((offset > 0) ? " limit " + limitPlaceholder + " offset "
+                + offsetPlaceholder : " limit " + limitPlaceholder);
+    }
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset, Integer.toString(offset), limit, Integer.toString(limit));
+    }
+}

+ 53 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/MySQLDialect.java

@@ -0,0 +1,53 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * Mysql方言的实现
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class MySQLDialect implements Dialect {
+
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset, Integer.toString(offset),
+                Integer.toString(limit));
+    }
+
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    public String getLimitString(String sql, int offset, String offsetPlaceholder, String limitPlaceholder) {
+        StringBuilder stringBuilder = new StringBuilder(sql);
+        stringBuilder.append(" limit ");
+        if (offset > 0) {
+            stringBuilder.append(offsetPlaceholder).append(",").append(limitPlaceholder);
+        } else {
+            stringBuilder.append(limitPlaceholder);
+        }
+        return stringBuilder.toString();
+    }
+
+}

+ 68 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/OracleDialect.java

@@ -0,0 +1,68 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * Oracle的方言实现
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class OracleDialect implements Dialect {
+    @Override
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset, Integer.toString(offset), Integer.toString(limit));
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    public String getLimitString(String sql, int offset, String offsetPlaceholder, String limitPlaceholder) {
+        sql = sql.trim();
+        boolean isForUpdate = false;
+        if (sql.toLowerCase().endsWith(" for update")) {
+            sql = sql.substring(0, sql.length() - 11);
+            isForUpdate = true;
+        }
+        StringBuilder pagingSelect = new StringBuilder(sql.length() + 100);
+
+        if (offset > 0) {
+			pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
+		} else {
+			pagingSelect.append("select * from ( ");
+		}
+		pagingSelect.append(sql);
+		if (offset > 0) {
+			String endString = offsetPlaceholder + "+" + limitPlaceholder;
+			pagingSelect.append(" ) row_ where rownum <= "+endString+") where rownum_ > ").append(offsetPlaceholder);
+		} else {
+			pagingSelect.append(" ) where rownum <= "+limitPlaceholder);
+		}
+
+        if (isForUpdate) {
+            pagingSelect.append(" for update");
+        }
+
+        return pagingSelect.toString();
+    }
+
+}

+ 48 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/PostgreSQLDialect.java

@@ -0,0 +1,48 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * Postgre Sql的方言实现
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class PostgreSQLDialect implements Dialect {
+
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset, Integer.toString(offset),
+                Integer.toString(limit));
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    public String getLimitString(String sql, int offset,
+                                 String offsetPlaceholder, String limitPlaceholder) {
+        StringBuilder pageSql = new StringBuilder().append(sql);
+        pageSql = offset <= 0
+                ? pageSql.append(" limit ").append(limitPlaceholder) :
+                pageSql.append(" limit ").append(limitPlaceholder).append(" offset ").append(offsetPlaceholder);
+        return pageSql.toString();
+    }
+}

+ 95 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/SQLServer2005Dialect.java

@@ -0,0 +1,95 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * Sql 2005的方言实现
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class SQLServer2005Dialect implements Dialect {
+
+    @Override
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimitString(sql, offset,
+                limit, Integer.toString(limit));
+    }
+
+    /**
+     * Add a LIMIT clause to the given SQL SELECT
+     * <p/>
+     * The LIMIT SQL will look like:
+     * <p/>
+     * WITH query AS
+     * (SELECT TOP 100 percent ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __row_number__, * from table_name)
+     * SELECT *
+     * FROM query
+     * WHERE __row_number__ BETWEEN :offset and :lastRows
+     * ORDER BY __row_number__
+     *
+     * @param querySqlString   The SQL statement to base the limit query off of.
+     * @param offset           Offset of the first row to be returned by the query (zero-based)
+     * @param limit            Maximum number of rows to be returned by the query
+     * @param limitPlaceholder limitPlaceholder
+     * @return A new SQL statement with the LIMIT clause applied.
+     */
+    private String getLimitString(String querySqlString, int offset, int limit, String limitPlaceholder) {
+        StringBuilder pagingBuilder = new StringBuilder();
+        String orderby = getOrderByPart(querySqlString);
+        String distinctStr = "";
+
+        String loweredString = querySqlString.toLowerCase();
+        String sqlPartString = querySqlString;
+        if (loweredString.trim().startsWith("select")) {
+            int index = 6;
+            if (loweredString.startsWith("select distinct")) {
+                distinctStr = "DISTINCT ";
+                index = 15;
+            }
+            sqlPartString = sqlPartString.substring(index);
+        }
+        pagingBuilder.append(sqlPartString);
+
+        // if no ORDER BY is specified use fake ORDER BY field to avoid errors
+        if (StringUtils.isEmpty(orderby)) {
+            orderby = "ORDER BY CURRENT_TIMESTAMP";
+        }
+
+        StringBuilder result = new StringBuilder();
+        result.append("WITH query AS (SELECT ")
+                .append(distinctStr)
+                .append("TOP 100 PERCENT ")
+                .append(" ROW_NUMBER() OVER (")
+                .append(orderby)
+                .append(") as __row_number__, ")
+                .append(pagingBuilder)
+                .append(") SELECT * FROM query WHERE __row_number__ BETWEEN ")
+                .append(offset).append(" AND ").append(offset + limit)
+                .append(" ORDER BY __row_number__");
+
+        return result.toString();
+    }
+
+    static String getOrderByPart(String sql) {
+        String loweredString = sql.toLowerCase();
+        int orderByIndex = loweredString.indexOf("order by");
+        if (orderByIndex != -1) {
+            // if we find a new "order by" then we need to ignore
+            // the previous one since it was probably used for a subquery
+            return sql.substring(orderByIndex);
+        } else {
+            return "";
+        }
+    }
+}

+ 55 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/SQLServerDialect.java

@@ -0,0 +1,55 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * MSSQLServer 数据库实现分页方言
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class SQLServerDialect implements Dialect {
+
+    public boolean supportsLimit() {
+        return true;
+    }
+
+    static int getAfterSelectInsertPoint(String sql) {
+        int selectIndex = sql.toLowerCase().indexOf("select");
+        final int selectDistinctIndex = sql.toLowerCase().indexOf("select distinct");
+        return selectIndex + (selectDistinctIndex == selectIndex ? 15 : 6);
+    }
+
+    public String getLimitString(String sql, int offset, int limit) {
+        return getLimit(sql, offset, limit);
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql    实际SQL语句
+     * @param offset 分页开始纪录条数
+     * @param limit  分页每页显示纪录条数
+     * @return 包含占位符的分页sql
+     */
+    public String getLimit(String sql, int offset, int limit) {
+        if (offset > 0) {
+            throw new UnsupportedOperationException("sql server has no offset");
+        }
+        return new StringBuffer(sql.length() + 8)
+                .append(sql)
+                .insert(getAfterSelectInsertPoint(sql), " top " + limit)
+                .toString();
+    }
+
+
+}

+ 47 - 0
src/main/java/com/jeeplus/common/persistence/dialect/db/SybaseDialect.java

@@ -0,0 +1,47 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.dialect.db;
+
+import com.jeeplus.common.persistence.dialect.Dialect;
+
+/**
+ * Sybase数据库分页方言实现。
+ * 还未实现
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2010-10-10 下午12:31
+ * @since JDK 1.5
+ */
+public class SybaseDialect implements Dialect {
+
+    public boolean supportsLimit() {
+        return false;
+    }
+
+
+    @Override
+    public String getLimitString(String sql, int offset, int limit) {
+        return null;
+    }
+
+    /**
+     * 将sql变成分页sql语句,提供将offset及limit使用占位符号(placeholder)替换.
+     * <pre>
+     * 如mysql
+     * dialect.getLimitString("select * from user", 12, ":offset",0,":limit") 将返回
+     * select * from user limit :offset,:limit
+     * </pre>
+     *
+     * @param sql               实际SQL语句
+     * @param offset            分页开始纪录条数
+     * @param offsetPlaceholder 分页开始纪录条数-占位符号
+     * @param limit             分页每页显示纪录条数
+     * @param limitPlaceholder  分页纪录条数占位符号
+     * @return 包含占位符的分页sql
+     */
+    public String getLimitString(String sql, int offset, String offsetPlaceholder, int limit, String limitPlaceholder) {
+        throw new UnsupportedOperationException("paged queries not supported");
+    }
+
+}

+ 110 - 0
src/main/java/com/jeeplus/common/persistence/interceptor/BaseInterceptor.java

@@ -0,0 +1,110 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.interceptor;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.plugin.Interceptor;
+
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.persistence.dialect.Dialect;
+import com.jeeplus.common.persistence.dialect.db.DB2Dialect;
+import com.jeeplus.common.persistence.dialect.db.DerbyDialect;
+import com.jeeplus.common.persistence.dialect.db.H2Dialect;
+import com.jeeplus.common.persistence.dialect.db.HSQLDialect;
+import com.jeeplus.common.persistence.dialect.db.MySQLDialect;
+import com.jeeplus.common.persistence.dialect.db.OracleDialect;
+import com.jeeplus.common.persistence.dialect.db.PostgreSQLDialect;
+import com.jeeplus.common.persistence.dialect.db.SQLServer2005Dialect;
+import com.jeeplus.common.persistence.dialect.db.SybaseDialect;
+import com.jeeplus.common.utils.Reflections;
+
+import java.io.Serializable;
+import java.util.Properties;
+
+/**
+ * Mybatis分页拦截器基类
+ * @author poplar.yfyang / jeeplus
+ * @version 2013-8-28
+ */
+public abstract class BaseInterceptor implements Interceptor, Serializable {
+	
+	private static final long serialVersionUID = 1L;
+
+    protected static final String PAGE = "page";
+    
+    protected static final String DELEGATE = "delegate";
+
+    protected static final String MAPPED_STATEMENT = "mappedStatement";
+
+    protected Log log = LogFactory.getLog(this.getClass());
+
+    protected Dialect DIALECT;
+
+//    /**
+//     * 拦截的ID,在mapper中的id,可以匹配正则
+//     */
+//    protected String _SQL_PATTERN = "";
+
+    /**
+     * 对参数进行转换和检查
+     * @param parameterObject 参数对象
+     * @param page            分页对象
+     * @return 分页对象
+     * @throws NoSuchFieldException 无法找到参数
+     */
+    @SuppressWarnings("unchecked")
+	protected static Page<Object> convertParameter(Object parameterObject, Page<Object> page) {
+    	try{
+            if (parameterObject instanceof Page) {
+                return (Page<Object>) parameterObject;
+            } else {
+                return (Page<Object>)Reflections.getFieldValue(parameterObject, PAGE);
+            }
+    	}catch (Exception e) {
+			return null;
+		}
+    }
+
+    /**
+     * 设置属性,支持自定义方言类和制定数据库的方式
+     * <code>dialectClass</code>,自定义方言类。可以不配置这项
+     * <ode>dbms</ode> 数据库类型,插件支持的数据库
+     * <code>sqlPattern</code> 需要拦截的SQL ID
+     * @param p 属性
+     */
+    protected void initProperties(Properties p) {
+    	Dialect dialect = null;
+        String dbType = Global.getConfig("jdbc.type");
+        if ("db2".equals(dbType)){
+        	dialect = new DB2Dialect();
+        }else if("derby".equals(dbType)){
+        	dialect = new DerbyDialect();
+        }else if("h2".equals(dbType)){
+        	dialect = new H2Dialect();
+        }else if("hsql".equals(dbType)){
+        	dialect = new HSQLDialect();
+        }else if("mysql".equals(dbType)){
+        	dialect = new MySQLDialect();
+        }else if("oracle".equals(dbType)){
+        	dialect = new OracleDialect();
+        }else if("postgre".equals(dbType)){
+        	dialect = new PostgreSQLDialect();
+        }else if("mssql".equals(dbType) || "sqlserver".equals(dbType)){
+        	dialect = new SQLServer2005Dialect();
+        }else if("sybase".equals(dbType)){
+        	dialect = new SybaseDialect();
+        }
+        if (dialect == null) {
+            throw new RuntimeException("mybatis dialect error.");
+        }
+        DIALECT = dialect;
+//        _SQL_PATTERN = p.getProperty("sqlPattern");
+//        _SQL_PATTERN = Global.getConfig("mybatis.pagePattern");
+//        if (StringUtils.isEmpty(_SQL_PATTERN)) {
+//            throw new RuntimeException("sqlPattern property is not found!");
+//        }
+    }
+}

+ 128 - 0
src/main/java/com/jeeplus/common/persistence/interceptor/PaginationInterceptor.java

@@ -0,0 +1,128 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.interceptor;
+
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Plugin;
+import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.utils.Reflections;
+import com.jeeplus.common.utils.StringUtils;
+
+import java.util.Properties;
+
+/**
+ * 数据库分页插件,只拦截查询语句.
+ * @author poplar.yfyang / jeeplus
+ * @version 2013-8-28
+ */
+@Intercepts({@Signature(type = Executor.class, method = "query",
+        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
+public class PaginationInterceptor extends BaseInterceptor {
+
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+
+        final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
+        
+//        //拦截需要分页的SQL
+////        if (mappedStatement.getId().matches(_SQL_PATTERN)) {
+//        if (StringUtils.indexOfIgnoreCase(mappedStatement.getId(), _SQL_PATTERN) != -1) {
+            Object parameter = invocation.getArgs()[1];
+            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
+            Object parameterObject = boundSql.getParameterObject();
+
+            //获取分页参数对象
+            Page<Object> page = null;
+            if (parameterObject != null) {
+                page = convertParameter(parameterObject, page);
+            }
+
+            //如果设置了分页对象,则进行分页
+            if (page != null && page.getPageSize() != -1) {
+
+            	if (StringUtils.isBlank(boundSql.getSql())){
+                    return null;
+                }
+                String originalSql = boundSql.getSql().trim();
+            	
+                //得到总记录数
+                if(page.getCountFlag())
+                page.setCount(SQLHelper.getCount(originalSql, null, mappedStatement, parameterObject, boundSql, log));
+
+                //分页查询 本地化对象 修改数据库注意修改实现
+                String pageSql = SQLHelper.generatePageSql(originalSql, page, DIALECT);
+//                if (log.isDebugEnabled()) {
+//                    log.debug("PAGE SQL:" + StringUtils.replace(pageSql, "\n", ""));
+//                }
+                invocation.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
+                BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
+                //解决MyBatis 分页foreach 参数失效 start
+                if (Reflections.getFieldValue(boundSql, "metaParameters") != null) {
+                    MetaObject mo = (MetaObject) Reflections.getFieldValue(boundSql, "metaParameters");
+                    Reflections.setFieldValue(newBoundSql, "metaParameters", mo);
+                }
+                //解决MyBatis 分页foreach 参数失效 end
+                MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
+
+                invocation.getArgs()[0] = newMs;
+            }
+//        }
+        return invocation.proceed();
+    }
+
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+        super.initProperties(properties);
+    }
+
+    private MappedStatement copyFromMappedStatement(MappedStatement ms,
+                                                    SqlSource newSqlSource) {
+        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
+                ms.getId(), newSqlSource, ms.getSqlCommandType());
+        builder.resource(ms.getResource());
+        builder.fetchSize(ms.getFetchSize());
+        builder.statementType(ms.getStatementType());
+        builder.keyGenerator(ms.getKeyGenerator());
+        if (ms.getKeyProperties() != null) {
+            for (String keyProperty : ms.getKeyProperties()) {
+                builder.keyProperty(keyProperty);
+            }
+        }
+        builder.timeout(ms.getTimeout());
+        builder.parameterMap(ms.getParameterMap());
+        builder.resultMaps(ms.getResultMaps());
+        builder.cache(ms.getCache());
+        return builder.build();
+    }
+
+    public static class BoundSqlSqlSource implements SqlSource {
+        BoundSql boundSql;
+
+        public BoundSqlSqlSource(BoundSql boundSql) {
+            this.boundSql = boundSql;
+        }
+
+        public BoundSql getBoundSql(Object parameterObject) {
+            return boundSql;
+        }
+    }
+}

+ 89 - 0
src/main/java/com/jeeplus/common/persistence/interceptor/PreparePaginationInterceptor.java

@@ -0,0 +1,89 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.interceptor;
+
+import org.apache.ibatis.executor.statement.BaseStatementHandler;
+import org.apache.ibatis.executor.statement.RoutingStatementHandler;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Plugin;
+import org.apache.ibatis.plugin.Signature;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.utils.Reflections;
+
+import java.sql.Connection;
+import java.util.Properties;
+
+/**
+ * Mybatis数据库分页插件,拦截StatementHandler的prepare方法
+ * @author poplar.yfyang / jeeplus
+ * @version 2013-8-28
+ */
+@Intercepts({
+	@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})
+})
+public class PreparePaginationInterceptor extends BaseInterceptor {
+    
+    private static final long serialVersionUID = 1L;
+
+    public PreparePaginationInterceptor() {
+        super();
+    }
+
+    @Override
+    public Object intercept(Invocation ivk) throws Throwable {
+        if (ivk.getTarget().getClass().isAssignableFrom(RoutingStatementHandler.class)) {
+            final RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();
+            final BaseStatementHandler delegate = (BaseStatementHandler) Reflections.getFieldValue(statementHandler, DELEGATE);
+            final MappedStatement mappedStatement = (MappedStatement) Reflections.getFieldValue(delegate, MAPPED_STATEMENT);
+
+//            //拦截需要分页的SQL
+////            if (mappedStatement.getId().matches(_SQL_PATTERN)) { 
+//            if (StringUtils.indexOfIgnoreCase(mappedStatement.getId(), _SQL_PATTERN) != -1) {
+                BoundSql boundSql = delegate.getBoundSql();
+                //分页SQL<select>中parameterType属性对应的实体参数,即Mapper接口中执行分页方法的参数,该参数不得为空
+                Object parameterObject = boundSql.getParameterObject();
+                if (parameterObject == null) {
+                    log.error("参数未实例化");
+                    throw new NullPointerException("parameterObject尚未实例化!");
+                } else {
+                    final Connection connection = (Connection) ivk.getArgs()[0];
+                    final String sql = boundSql.getSql();
+                    //记录统计
+                    final int count = SQLHelper.getCount(sql, connection, mappedStatement, parameterObject, boundSql, log);
+                    Page<Object> page = null;
+                    page = convertParameter(parameterObject, page);
+                    page.setCount(count);
+                    String pagingSql = SQLHelper.generatePageSql(sql, page, DIALECT);
+                    if (log.isDebugEnabled()) {
+                        log.debug("PAGE SQL:" + pagingSql);
+                    }
+                    //将分页sql语句反射回BoundSql.
+                    Reflections.setFieldValue(boundSql, "sql", pagingSql);
+                }
+                
+                if (boundSql.getSql() == null || "".equals(boundSql.getSql())){
+                    return null;
+                }
+                
+            }
+//        }
+        return ivk.proceed();
+    }
+
+
+    @Override
+    public Object plugin(Object o) {
+        return Plugin.wrap(o, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+        initProperties(properties);
+    }
+}

+ 188 - 0
src/main/java/com/jeeplus/common/persistence/interceptor/SQLHelper.java

@@ -0,0 +1,188 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.interceptor;
+
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.executor.ExecutorException;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.ParameterMode;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.property.PropertyTokenizer;
+import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.persistence.dialect.Dialect;
+import com.jeeplus.common.utils.Reflections;
+import com.jeeplus.common.utils.StringUtils;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * SQL工具类
+ * @author poplar.yfyang / jeeplus
+ * @version 2013-8-28
+ */
+public class SQLHelper {
+	
+    /**
+     * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
+     *
+     * @param ps              表示预编译的 SQL 语句的对象。
+     * @param mappedStatement MappedStatement
+     * @param boundSql        SQL
+     * @param parameterObject 参数对象
+     * @throws java.sql.SQLException 数据库异常
+     */
+    @SuppressWarnings("unchecked")
+    public static void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
+        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
+        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
+        if (parameterMappings != null) {
+            Configuration configuration = mappedStatement.getConfiguration();
+            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
+            MetaObject metaObject = parameterObject == null ? null :
+                    configuration.newMetaObject(parameterObject);
+            for (int i = 0; i < parameterMappings.size(); i++) {
+                ParameterMapping parameterMapping = parameterMappings.get(i);
+                if (parameterMapping.getMode() != ParameterMode.OUT) {
+                    Object value;
+                    String propertyName = parameterMapping.getProperty();
+                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);
+                    if (parameterObject == null) {
+                        value = null;
+                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
+                        value = parameterObject;
+                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
+                        value = boundSql.getAdditionalParameter(propertyName);
+                    } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
+                        value = boundSql.getAdditionalParameter(prop.getName());
+                        if (value != null) {
+                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
+                        }
+                    } else {
+                        value = metaObject == null ? null : metaObject.getValue(propertyName);
+                    }
+                    @SuppressWarnings("rawtypes")
+					TypeHandler typeHandler = parameterMapping.getTypeHandler();
+                    if (typeHandler == null) {
+                        throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
+                    }
+                    typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 查询总纪录数
+     * @param sql             SQL语句
+     * @param connection      数据库连接
+     * @param mappedStatement mapped
+     * @param parameterObject 参数
+     * @param boundSql        boundSql
+     * @return 总记录数
+     * @throws SQLException sql查询错误
+     */
+    public static int getCount(final String sql, final Connection connection,
+    							final MappedStatement mappedStatement, final Object parameterObject,
+    							final BoundSql boundSql, Log log) throws SQLException {
+        final String countSql = "select count(1) from (" + removeOrders(sql) + ") tmp_count";
+//        final String countSql = "select count(1) " + removeSelect(removeOrders(sql));
+        Connection conn = connection;
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        try {
+        	if (log.isDebugEnabled()) {
+                log.debug("COUNT SQL: " + StringUtils.replaceEach(countSql, new String[]{"\n","\t"}, new String[]{" "," "}));
+            }
+        	if (conn == null){
+        		conn = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
+            }
+        	ps = conn.prepareStatement(countSql);
+            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
+                    boundSql.getParameterMappings(), parameterObject);
+            //解决MyBatis 分页foreach 参数失效 start
+			if (Reflections.getFieldValue(boundSql, "metaParameters") != null) {
+				MetaObject mo = (MetaObject) Reflections.getFieldValue(boundSql, "metaParameters");
+				Reflections.setFieldValue(countBS, "metaParameters", mo);
+			}
+			//解决MyBatis 分页foreach 参数失效 end 
+            SQLHelper.setParameters(ps, mappedStatement, countBS, parameterObject);
+            rs = ps.executeQuery();
+            int count = 0;
+            if (rs.next()) {
+                count = rs.getInt(1);
+            }
+            return count;
+        } finally {
+            if (rs != null) {
+                rs.close();
+            }
+            if (ps != null) {
+            	ps.close();
+            }
+            if (conn != null) {
+            	conn.close();
+            }
+        }
+    }
+
+
+    /**
+     * 根据数据库方言,生成特定的分页sql
+     * @param sql     Mapper中的Sql语句
+     * @param page    分页对象
+     * @param dialect 方言类型
+     * @return 分页SQL
+     */
+    public static String generatePageSql(String sql, Page<Object> page, Dialect dialect) {
+        if (dialect.supportsLimit()) {
+            return dialect.getLimitString(sql, page.getFirstResult(), page.getMaxResults());
+        } else {
+            return sql;
+        }
+    }
+    
+    /** 
+     * 去除qlString的select子句。 
+     * @param hql 
+     * @return 
+     */  
+    @SuppressWarnings("unused")
+	private static String removeSelect(String qlString){  
+        int beginPos = qlString.toLowerCase().indexOf("from");  
+        return qlString.substring(beginPos);  
+    }  
+      
+    /** 
+     * 去除hql的orderBy子句。 
+     * @param hql 
+     * @return 
+     */  
+    @SuppressWarnings("unused")
+	private static String removeOrders(String qlString) {  
+        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);  
+        Matcher m = p.matcher(qlString);  
+        StringBuffer sb = new StringBuffer();  
+        while (m.find()) {  
+            m.appendReplacement(sb, "");  
+        }
+        m.appendTail(sb);
+        return sb.toString();  
+    }
+    
+}

+ 37 - 0
src/main/java/com/jeeplus/common/persistence/proxy/PageConfiguration.java

@@ -0,0 +1,37 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.proxy;
+
+import org.apache.ibatis.binding.MapperRegistry;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+
+/**
+ * <p>
+ * 自定义Mybatis的配置,扩展.
+ * </p>
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2012-05-13 上午10:06
+ * @since JDK 1.5
+ */
+public class PageConfiguration extends Configuration {
+	
+    protected MapperRegistry mapperRegistry = new PaginationMapperRegistry(this);
+
+    @Override
+    public <T> void addMapper(Class<T> type) {
+        mapperRegistry.addMapper(type);
+    }
+
+    @Override
+    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
+        return mapperRegistry.getMapper(type, sqlSession);
+    }
+
+    @Override
+    public boolean hasMapper(Class<?> type) {
+        return mapperRegistry.hasMapper(type);
+    }
+}

+ 191 - 0
src/main/java/com/jeeplus/common/persistence/proxy/PaginationMapperMethod.java

@@ -0,0 +1,191 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.proxy;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.binding.BindingException;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+
+import com.jeeplus.common.persistence.Page;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 执行代理类,扩展Mybatis的方式来让其Mapper接口来支持.
+ * </p>
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2012-05-13 上午10:09
+ * @since JDK 1.5
+ */
+public class PaginationMapperMethod {
+
+    private final SqlSession sqlSession;
+    private final Configuration config;
+
+    private SqlCommandType type;
+    private String commandName;
+    private String commandCountName;
+
+    private final Class<?> declaringInterface;
+    private final Method method;
+
+    private Integer rowBoundsIndex;
+    private Integer paginationIndex;
+
+    private final List<String> paramNames;
+    private final List<Integer> paramPositions;
+
+    private boolean hasNamedParameters;
+
+    public PaginationMapperMethod(Class<?> declaringInterface, Method method,
+                                  SqlSession sqlSession) {
+        paramNames = new ArrayList<String>();
+        paramPositions = new ArrayList<Integer>();
+        this.sqlSession = sqlSession;
+        this.method = method;
+        this.config = sqlSession.getConfiguration();
+        this.declaringInterface = declaringInterface;
+        this.hasNamedParameters = false;
+        setupFields();
+        setupMethodSignature();
+        setupCommandType();
+        validateStatement();
+    }
+
+    /**
+     * 代理执行方法。
+     *
+     * @param args 参数信息
+     * @return 执行结果
+     */
+    @SuppressWarnings("unchecked")
+    public Object execute(Object[] args) {
+        final Object param = getParam(args);
+        Page<Object> page;
+        RowBounds rowBounds;
+        if (paginationIndex != null) {
+            page = (Page<Object>) args[paginationIndex];
+            rowBounds =  new RowBounds(page.getFirstResult(), page.getMaxResults());
+        } else if (rowBoundsIndex != null) {
+            rowBounds = (RowBounds) args[rowBoundsIndex];
+            page = new Page<Object>();
+        } else {
+            throw new BindingException("Invalid bound statement (not found rowBounds or pagination in paramenters)");
+        }
+        page.setCount(executeForCount(param));
+        page.setList(executeForList(param, rowBounds));
+        return page;
+    }
+
+    /**
+     * 执行总数的方法,调用方法执行计算总数,取得总结果
+     *
+     * @param param 参数信息
+     * @return 查询的总记录数
+     */
+    private long executeForCount(Object param) {
+        Number result = (Number) sqlSession.selectOne(commandCountName, param);
+        return result.longValue();
+    }
+
+    /**
+     * 取得分页的执行结果,返回的是纪录信息
+     *
+     * @param param     参数
+     * @param rowBounds row
+     * @return 纪录列表
+     */
+    private List<Object> executeForList(Object param, RowBounds rowBounds) {
+        return sqlSession.selectList(commandName, param, rowBounds);
+    }
+
+    /**
+     * 取得当前执行的参数信息
+     *
+     * @param args 参数
+     * @return 参数信息
+     */
+    private Object getParam(Object[] args) {
+        final int paramCount = paramPositions.size();
+        if (args == null || paramCount == 0) {
+            return null;
+        } else if (!hasNamedParameters && paramCount == 1) {
+            return args[paramPositions.get(0)];
+        } else {
+            Map<String, Object> param = new HashMap<String, Object>();
+            for (int i = 0; i < paramCount; i++) {
+                param.put(paramNames.get(i), args[paramPositions.get(i)]);
+            }
+            return param;
+        }
+    }
+
+    private void setupMethodSignature() {
+        final Class<?>[] argTypes = method.getParameterTypes();
+        for (int i = 0; i < argTypes.length; i++) {
+            if (Page.class.isAssignableFrom(argTypes[i])) {
+                paginationIndex = i;
+            } else if (RowBounds.class.isAssignableFrom(argTypes[i])) {
+                rowBoundsIndex = i;
+            } else {
+                String paramName = String.valueOf(paramPositions.size());
+                paramName = getParamNameFromAnnotation(i, paramName);
+                paramNames.add(paramName);
+                paramPositions.add(i);
+            }
+        }
+    }
+
+    private String getParamNameFromAnnotation(int i, String paramName) {
+        Object[] annotations = method.getParameterAnnotations()[i];
+        for (Object annotation : annotations) {
+            if (annotation instanceof Param) {
+                hasNamedParameters = true;
+                paramName = ((Param) annotation).value();
+            }
+        }
+        return paramName;
+    }
+
+    /**
+     * 设置当前的查询总记录数的ID
+     */
+    private void setupFields() {
+        commandName = declaringInterface.getName() + "." + method.getName();
+        commandCountName = commandName + "Count"; // 命名约定
+    }
+
+    /**
+     * 设置当前的参数的类型信息
+     */
+    private void setupCommandType() {
+        MappedStatement ms = config.getMappedStatement(commandName);
+        type = ms.getSqlCommandType();
+        if (type != SqlCommandType.SELECT) {
+            throw new BindingException("Unsupport execution method for: " + commandName);
+        }
+    }
+
+    /**
+     * 验证Statement
+     */
+    private void validateStatement() {
+        if (!config.hasStatement(commandName)) {
+            throw new BindingException("Invalid bound statement (not found): " + commandName);
+        }
+        if (!config.hasStatement(commandCountName)) {
+            throw new BindingException("Invalid bound statement (not found): " + commandCountName);
+        }
+    }
+}

+ 36 - 0
src/main/java/com/jeeplus/common/persistence/proxy/PaginationMapperRegistry.java

@@ -0,0 +1,36 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.persistence.proxy;
+
+import org.apache.ibatis.binding.BindingException;
+import org.apache.ibatis.binding.MapperRegistry;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+
+/**
+ * <p>
+ * .
+ * </p>
+ *
+ * @author poplar.yfyang
+ * @version 1.0 2012-05-13 上午10:06
+ * @since JDK 1.5
+ */
+public class PaginationMapperRegistry extends MapperRegistry {
+    public PaginationMapperRegistry(Configuration config) {
+        super(config);
+    }
+
+    @Override
+    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
+        if (!hasMapper(type)) {
+            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
+        }
+        try {
+            return PaginationMapperProxy.newMapperProxy(type, sqlSession);
+        } catch (Exception e) {
+            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
+        }
+    }
+}

+ 260 - 0
src/main/java/com/jeeplus/common/security/Cryptos.java

@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.jeeplus.common.security;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import com.jeeplus.common.utils.Encodes;
+import com.jeeplus.common.utils.Exceptions;
+
+/**
+ * 支持HMAC-SHA1消息签名 及 DES/AES对称加密的工具类.
+ * 
+ * 支持Hex与Base64两种编码方式.
+ * 
+ * @author calvin
+ */
+public class Cryptos {
+
+	private static final String AES = "AES";
+	private static final String AES_CBC = "AES/CBC/PKCS5Padding";
+	private static final String HMACSHA1 = "HmacSHA1";
+
+	private static final String DEFAULT_URL_ENCODING = "UTF-8";
+	private static final int DEFAULT_HMACSHA1_KEYSIZE = 160; //RFC2401
+	private static final int DEFAULT_AES_KEYSIZE = 128;
+	private static final int DEFAULT_IVSIZE = 16;
+	
+	private static final byte[] DEFAULT_KEY = new byte[]{-97,88,-94,9,70,-76,126,25,0,3,-20,113,108,28,69,125}; 
+
+	private static SecureRandom random = new SecureRandom();
+
+	//-- HMAC-SHA1 funciton --//
+	/**
+	 * 使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节.
+	 * 
+	 * @param input 原始输入字符数组
+	 * @param key HMAC-SHA1密钥
+	 */
+	public static byte[] hmacSha1(byte[] input, byte[] key) {
+		try {
+			SecretKey secretKey = new SecretKeySpec(key, HMACSHA1);
+			Mac mac = Mac.getInstance(HMACSHA1);
+			mac.init(secretKey);
+			return mac.doFinal(input);
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 校验HMAC-SHA1签名是否正确.
+	 * 
+	 * @param expected 已存在的签名
+	 * @param input 原始输入字符串
+	 * @param key 密钥
+	 */
+	public static boolean isMacValid(byte[] expected, byte[] input, byte[] key) {
+		byte[] actual = hmacSha1(input, key);
+		return Arrays.equals(expected, actual);
+	}
+
+	/**
+	 * 生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节).
+	 * HMAC-SHA1算法对密钥无特殊要求, RFC2401建议最少长度为160位(20字节).
+	 */
+	public static byte[] generateHmacSha1Key() {
+		try {
+			KeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1);
+			keyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE);
+			SecretKey secretKey = keyGenerator.generateKey();
+			return secretKey.getEncoded();
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	//-- AES funciton --//
+
+	/**
+	 * 使用AES加密原始字符串.
+	 * 
+	 * @param input 原始输入字符数组
+	 */
+	public static String aesEncrypt(String input) {
+		try {
+			return Encodes.encodeHex(aesEncrypt(input.getBytes(DEFAULT_URL_ENCODING), DEFAULT_KEY));
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+	
+	/**
+	 * 使用AES加密原始字符串.
+	 * 
+	 * @param input 原始输入字符数组
+	 * @param key 符合AES要求的密钥
+	 */
+	public static String aesEncrypt(String input, String key) {
+		try {
+			return Encodes.encodeHex(aesEncrypt(input.getBytes(DEFAULT_URL_ENCODING), Encodes.decodeHex(key)));
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+	
+	/**
+	 * 使用AES加密原始字符串.
+	 * 
+	 * @param input 原始输入字符数组
+	 * @param key 符合AES要求的密钥
+	 */
+	public static byte[] aesEncrypt(byte[] input, byte[] key) {
+		return aes(input, key, Cipher.ENCRYPT_MODE);
+	}
+
+	/**
+	 * 使用AES加密原始字符串.
+	 * 
+	 * @param input 原始输入字符数组
+	 * @param key 符合AES要求的密钥
+	 * @param iv 初始向量
+	 */
+	public static byte[] aesEncrypt(byte[] input, byte[] key, byte[] iv) {
+		return aes(input, key, iv, Cipher.ENCRYPT_MODE);
+	}
+
+	/**
+	 * 使用AES解密字符串, 返回原始字符串.
+	 * 
+	 * @param input Hex编码的加密字符串
+	 */
+	public static String aesDecrypt(String input) {
+		try {
+			return new String(aesDecrypt(Encodes.decodeHex(input), DEFAULT_KEY), DEFAULT_URL_ENCODING);
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+	
+	/**
+	 * 使用AES解密字符串, 返回原始字符串.
+	 * 
+	 * @param input Hex编码的加密字符串
+	 * @param key 符合AES要求的密钥
+	 */
+	public static String aesDecrypt(String input, String key) {
+		try {
+			return new String(aesDecrypt(Encodes.decodeHex(input), Encodes.decodeHex(key)), DEFAULT_URL_ENCODING);
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+	
+	/**
+	 * 使用AES解密字符串, 返回原始字符串.
+	 * 
+	 * @param input Hex编码的加密字符串
+	 * @param key 符合AES要求的密钥
+	 */
+	public static byte[] aesDecrypt(byte[] input, byte[] key) {
+		return aes(input, key, Cipher.DECRYPT_MODE);
+	}
+
+	/**
+	 * 使用AES解密字符串, 返回原始字符串.
+	 * 
+	 * @param input Hex编码的加密字符串
+	 * @param key 符合AES要求的密钥
+	 * @param iv 初始向量
+	 */
+	public static byte[] aesDecrypt(byte[] input, byte[] key, byte[] iv) {
+		return aes(input, key, iv, Cipher.DECRYPT_MODE);
+	}
+
+	/**
+	 * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
+	 * 
+	 * @param input 原始字节数组
+	 * @param key 符合AES要求的密钥
+	 * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
+	 */
+	private static byte[] aes(byte[] input, byte[] key, int mode) {
+		try {
+			SecretKey secretKey = new SecretKeySpec(key, AES);
+			Cipher cipher = Cipher.getInstance(AES);
+			cipher.init(mode, secretKey);
+			return cipher.doFinal(input);
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
+	 * 
+	 * @param input 原始字节数组
+	 * @param key 符合AES要求的密钥
+	 * @param iv 初始向量
+	 * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
+	 */
+	private static byte[] aes(byte[] input, byte[] key, byte[] iv, int mode) {
+		try {
+			SecretKey secretKey = new SecretKeySpec(key, AES);
+			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+			Cipher cipher = Cipher.getInstance(AES_CBC);
+			cipher.init(mode, secretKey, ivSpec);
+			return cipher.doFinal(input);
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 生成AES密钥,返回字节数组, 默认长度为128位(16字节).
+	 */
+	public static String generateAesKeyString() {
+		return Encodes.encodeHex(generateAesKey(DEFAULT_AES_KEYSIZE));
+	}
+	
+	/**
+	 * 生成AES密钥,返回字节数组, 默认长度为128位(16字节).
+	 */
+	public static byte[] generateAesKey() {
+		return generateAesKey(DEFAULT_AES_KEYSIZE);
+	}
+
+	/**
+	 * 生成AES密钥,可选长度为128,192,256位.
+	 */
+	public static byte[] generateAesKey(int keysize) {
+		try {
+			KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
+			keyGenerator.init(keysize);
+			SecretKey secretKey = keyGenerator.generateKey();
+			return secretKey.getEncoded();
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节.
+	 */
+	public static byte[] generateIV() {
+		byte[] bytes = new byte[DEFAULT_IVSIZE];
+		random.nextBytes(bytes);
+		return bytes;
+	}
+}

+ 148 - 0
src/main/java/com/jeeplus/common/security/Digests.java

@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.jeeplus.common.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+import org.apache.commons.lang3.Validate;
+
+import com.jeeplus.common.utils.Exceptions;
+
+/**
+ * 支持SHA-1/MD5消息摘要的工具类.
+ * 
+ * 返回ByteSource,可进一步被编码为Hex, Base64或UrlSafeBase64
+ * 
+ * @author calvin
+ */
+public class Digests {
+
+	private static final String SHA1 = "SHA-1";
+	private static final String MD5 = "MD5";
+
+	private static SecureRandom random = new SecureRandom();
+
+	/**
+	 * 对输入字符串进行md5散列.
+	 */
+	public static byte[] md5(byte[] input) {
+		return digest(input, MD5, null, 1);
+	}
+	public static byte[] md5(byte[] input, int iterations) {
+		return digest(input, MD5, null, iterations);
+	}
+	
+	/**
+	 * 对输入字符串进行sha1散列.
+	 */
+	public static byte[] sha1(byte[] input) {
+		return digest(input, SHA1, null, 1);
+	}
+
+	public static byte[] sha1(byte[] input, byte[] salt) {
+		return digest(input, SHA1, salt, 1);
+	}
+
+	public static byte[] sha1(byte[] input, byte[] salt, int iterations) {
+		return digest(input, SHA1, salt, iterations);
+	}
+
+	/**
+	 * 对字符串进行散列, 支持md5与sha1算法.
+	 */
+	private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {
+		try {
+			MessageDigest digest = MessageDigest.getInstance(algorithm);
+
+			if (salt != null) {
+				digest.update(salt);
+			}
+
+			byte[] result = digest.digest(input);
+
+			for (int i = 1; i < iterations; i++) {
+				digest.reset();
+				result = digest.digest(result);
+			}
+			return result;
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * 生成随机的Byte[]作为salt.
+	 * 
+	 * @param numBytes byte数组的大小
+	 */
+	public static byte[] generateSalt(int numBytes) {
+		Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", numBytes);
+
+		byte[] bytes = new byte[numBytes];
+		random.nextBytes(bytes);
+		return bytes;
+	}
+
+	/**
+	 * 对文件进行md5散列.
+	 */
+	public static byte[] md5(InputStream input) throws IOException {
+		return digest(input, MD5);
+	}
+
+	/**
+	 * 对文件进行sha1散列.
+	 */
+	public static byte[] sha1(InputStream input) throws IOException {
+		return digest(input, SHA1);
+	}
+
+	private static byte[] digest(InputStream input, String algorithm) throws IOException {
+		try {
+			MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+			int bufferLength = 8 * 1024;
+			byte[] buffer = new byte[bufferLength];
+			int read = input.read(buffer, 0, bufferLength);
+
+			while (read > -1) {
+				messageDigest.update(buffer, 0, read);
+				read = input.read(buffer, 0, bufferLength);
+			}
+
+			return messageDigest.digest();
+		} catch (GeneralSecurityException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+	
+	public static String string2MD5(String inStr){  
+        MessageDigest md5 = null;  
+        try{  
+            md5 = MessageDigest.getInstance("MD5");  
+        }catch (Exception e){  
+            System.out.println(e.toString());  
+            e.printStackTrace();  
+            return "";  
+        }  
+        char[] charArray = inStr.toCharArray();  
+        byte[] byteArray = new byte[charArray.length];  
+  
+        for (int i = 0; i < charArray.length; i++)  
+            byteArray[i] = (byte) charArray[i];  
+        byte[] md5Bytes = md5.digest(byteArray);  
+        StringBuffer hexValue = new StringBuffer();  
+        for (int i = 0; i < md5Bytes.length; i++){  
+            int val = ((int) md5Bytes[i]) & 0xff;  
+            if (val < 16)  
+                hexValue.append("0");  
+            hexValue.append(Integer.toHexString(val));  
+        }  
+        return hexValue.toString();  
+  
+    }  
+}

+ 40 - 0
src/main/java/com/jeeplus/common/security/shiro/HasAnyPermissionsTag.java

@@ -0,0 +1,40 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.tags.PermissionTag;
+
+/**
+ * Shiro HasAnyPermissions Tag.
+ * 
+ * @author calvin
+ */
+public class HasAnyPermissionsTag extends PermissionTag {
+
+	private static final long serialVersionUID = 1L;
+	private static final String PERMISSION_NAMES_DELIMETER = ",";
+
+	@Override
+	protected boolean showTagBody(String permissionNames) {
+		boolean hasAnyPermission = false;
+
+		Subject subject = getSubject();
+
+		if (subject != null) {
+			// Iterate through permissions and check to see if the user has one of the permissions
+			for (String permission : permissionNames.split(PERMISSION_NAMES_DELIMETER)) {
+
+				if (subject.isPermitted(permission.trim())) {
+					hasAnyPermission = true;
+					break;
+				}
+
+			}
+		}
+
+		return hasAnyPermission;
+	}
+
+}

+ 211 - 0
src/main/java/com/jeeplus/common/security/shiro/cache/JedisCacheManager.java

@@ -0,0 +1,211 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.CacheManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import redis.clients.jedis.Jedis;
+
+import com.google.common.collect.Sets;
+import com.jeeplus.common.utils.JedisUtils;
+import com.jeeplus.common.web.Servlets;
+
+/**
+ * 自定义授权缓存管理类
+ * @author jeeplus
+ * @version 2014-7-20
+ */
+public class JedisCacheManager implements CacheManager {
+
+	private String cacheKeyPrefix = "shiro_cache_";
+	
+	@Override
+	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
+		return new JedisCache<K, V>(cacheKeyPrefix + name);
+	}
+
+	public String getCacheKeyPrefix() {
+		return cacheKeyPrefix;
+	}
+
+	public void setCacheKeyPrefix(String cacheKeyPrefix) {
+		this.cacheKeyPrefix = cacheKeyPrefix;
+	}
+	
+	/**
+	 * 自定义授权缓存管理类
+	 * @author jeeplus
+	 * @version 2014-7-20
+	 */
+	public class JedisCache<K, V> implements Cache<K, V> {
+
+		private Logger logger = LoggerFactory.getLogger(getClass());
+
+		private String cacheKeyName = null;
+
+		public JedisCache(String cacheKeyName) {
+			this.cacheKeyName = cacheKeyName;
+//			if (!JedisUtils.exists(cacheKeyName)){
+//				Map<String, Object> map = Maps.newHashMap();
+//				JedisUtils.setObjectMap(cacheKeyName, map, 60 * 60 * 24);
+//			}
+//			logger.debug("Init: cacheKeyName {} ", cacheKeyName);
+		}
+		
+		@SuppressWarnings("unchecked")
+		@Override
+		public V get(K key) throws CacheException {
+			if (key == null){
+				return null;
+			}
+			
+			V v = null;
+			HttpServletRequest request = Servlets.getRequest();
+			if (request != null){
+				v = (V)request.getAttribute(cacheKeyName);
+				if (v != null){
+					return v;
+				}
+			}
+			
+			V value = null;
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				value = (V)JedisUtils.toObject(jedis.hget(JedisUtils.getBytesKey(cacheKeyName), JedisUtils.getBytesKey(key)));
+				logger.debug("get {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "");
+			} catch (Exception e) {
+				logger.error("get {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "", e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			
+			if (request != null && value != null){
+				request.setAttribute(cacheKeyName, value);
+			}
+			
+			return value;
+		}
+
+		@Override
+		public V put(K key, V value) throws CacheException {
+			if (key == null){
+				return null;
+			}
+			
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				jedis.hset(JedisUtils.getBytesKey(cacheKeyName), JedisUtils.getBytesKey(key), JedisUtils.toBytes(value));
+				logger.debug("put {} {} = {}", cacheKeyName, key, value);
+			} catch (Exception e) {
+				logger.error("put {} {}", cacheKeyName, key, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			return value;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public V remove(K key) throws CacheException {
+			V value = null;
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				value = (V)JedisUtils.toObject(jedis.hget(JedisUtils.getBytesKey(cacheKeyName), JedisUtils.getBytesKey(key)));
+				jedis.hdel(JedisUtils.getBytesKey(cacheKeyName), JedisUtils.getBytesKey(key));
+				logger.debug("remove {} {}", cacheKeyName, key);
+			} catch (Exception e) {
+				logger.warn("remove {} {}", cacheKeyName, key, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			return value;
+		}
+
+		@Override
+		public void clear() throws CacheException {
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				jedis.hdel(JedisUtils.getBytesKey(cacheKeyName));
+				logger.debug("clear {}", cacheKeyName);
+			} catch (Exception e) {
+				logger.error("clear {}", cacheKeyName, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+		}
+
+		@Override
+		public int size() {
+			int size = 0;
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				size = jedis.hlen(JedisUtils.getBytesKey(cacheKeyName)).intValue();
+				logger.debug("size {} {} ", cacheKeyName, size);
+				return size;
+			} catch (Exception e) {
+				logger.error("clear {}",  cacheKeyName, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			return size;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public Set<K> keys() {
+			Set<K> keys = Sets.newHashSet();
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				Set<byte[]> set = jedis.hkeys(JedisUtils.getBytesKey(cacheKeyName));
+				for(byte[] key : set){
+					keys.add((K)key);
+	        	}
+				logger.debug("keys {} {} ", cacheKeyName, keys);
+				return keys;
+			} catch (Exception e) {
+				logger.error("keys {}", cacheKeyName, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			return keys;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public Collection<V> values() {
+			Collection<V> vals = Collections.emptyList();;
+			Jedis jedis = null;
+			try {
+				jedis = JedisUtils.getResource();
+				Collection<byte[]> col = jedis.hvals(JedisUtils.getBytesKey(cacheKeyName));
+				for(byte[] val : col){
+					vals.add((V)val);
+	        	}
+				logger.debug("values {} {} ", cacheKeyName, vals);
+				return vals;
+			} catch (Exception e) {
+				logger.error("values {}",  cacheKeyName, e);
+			} finally {
+				JedisUtils.returnResource(jedis);
+			}
+			return vals;
+		}
+	}
+}

+ 144 - 0
src/main/java/com/jeeplus/common/security/shiro/cache/SessionCacheManager.java

@@ -0,0 +1,144 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro.cache;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.UnavailableSecurityManagerException;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Sets;
+import com.jeeplus.common.web.Servlets;
+
+/**
+ * 自定义授权缓存管理类
+ * @author jeeplus
+ * @version 2014-7-21
+ */
+public class SessionCacheManager implements CacheManager {
+
+	@Override
+	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
+		return new SessionCache<K, V>(name);
+	}
+
+	/**
+	 * SESSION缓存管理类
+	 */
+	public class SessionCache<K, V> implements Cache<K, V> {
+
+		private Logger logger = LoggerFactory.getLogger(getClass());
+		
+		private String cacheKeyName = null;
+
+		public SessionCache(String cacheKeyName) {
+			this.cacheKeyName = cacheKeyName;
+		}
+		
+		public Session getSession(){
+			Session session = null;
+			try{
+				Subject subject = SecurityUtils.getSubject();
+				session = subject.getSession(false);
+				if (session == null){
+					session = subject.getSession();
+				}
+			}catch (InvalidSessionException e){
+				logger.error("Invalid session error", e);
+			}catch (UnavailableSecurityManagerException e2){
+				logger.error("Unavailable SecurityManager error", e2);
+			}
+			return session;
+		}
+		
+		@SuppressWarnings("unchecked")
+		@Override
+		public V get(K key) throws CacheException {
+			if (key == null){
+				return null;
+			}
+			
+			V v = null;
+			HttpServletRequest request = Servlets.getRequest();
+			if (request != null){
+				v = (V)request.getAttribute(cacheKeyName);
+				if (v != null){
+					return v;
+				}
+			}
+			
+			V value = null;
+			value = (V)getSession().getAttribute(cacheKeyName);
+			logger.debug("get {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "");
+			
+			if (request != null && value != null){
+				request.setAttribute(cacheKeyName, value);
+			}
+			return value;
+		}
+
+		@Override
+		public V put(K key, V value) throws CacheException {
+			if (key == null){
+				return null;
+			}
+
+			getSession().setAttribute(cacheKeyName, value);
+			
+			if (logger.isDebugEnabled()){
+				HttpServletRequest request = Servlets.getRequest();
+				logger.debug("put {} {} {}", cacheKeyName, key, request != null ? request.getRequestURI() : "");
+			}
+			
+			return value;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public V remove(K key) throws CacheException {
+			
+			V value = null;
+			value = (V)getSession().removeAttribute(cacheKeyName);
+			logger.debug("remove {} {}", cacheKeyName, key);
+			
+			return value;
+		}
+
+		@Override
+		public void clear() throws CacheException {
+			getSession().removeAttribute(cacheKeyName);
+			logger.debug("clear {}", cacheKeyName);
+		}
+
+		@Override
+		public int size() {
+			logger.debug("invoke session size abstract size method not supported.");
+			return 0;
+		}
+
+		@Override
+		public Set<K> keys() {
+			logger.debug("invoke session keys abstract size method not supported.");
+			return Sets.newHashSet();
+		}
+
+		@Override
+		public Collection<V> values() {
+			logger.debug("invoke session values abstract size method not supported.");
+			return Collections.emptyList();
+		}
+	}
+}

+ 176 - 0
src/main/java/com/jeeplus/common/security/shiro/session/CacheSessionDAO.java

@@ -0,0 +1,176 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Sets;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.DateUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.common.web.Servlets;
+
+
+/**
+ * 系统安全认证实现类
+ * @author jeeplus
+ * @version 2014-7-24
+ */
+public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO {
+
+	private Logger logger = LoggerFactory.getLogger(getClass());
+	
+    public CacheSessionDAO() {
+        super();
+    }
+
+    @Override
+    protected void doUpdate(Session session) {
+    	if (session == null || session.getId() == null) {  
+            return;
+        }
+    	
+    	HttpServletRequest request = Servlets.getRequest();
+		if (request != null){
+			String uri = request.getServletPath();
+			// 如果是静态文件,则不更新SESSION
+			if (Servlets.isStaticFile(uri)){
+				return;
+			}
+			// 如果是视图文件,则不更新SESSION
+			if (StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
+					&& StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))){
+				return;
+			}
+			// 手动控制不更新SESSION
+			String updateSession = request.getParameter("updateSession");
+			if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)){
+				return;
+			}
+		}
+    	super.doUpdate(session);
+    	logger.debug("update {} {}", session.getId(), request != null ? request.getRequestURI() : "");
+    }
+
+    @Override
+    protected void doDelete(Session session) {
+    	if (session == null || session.getId() == null) {  
+            return;
+        }
+    	
+    	super.doDelete(session);
+    	logger.debug("delete {} ", session.getId());
+    }
+
+    @Override
+    protected Serializable doCreate(Session session) {
+		HttpServletRequest request = Servlets.getRequest();
+		if (request != null){
+			String uri = request.getServletPath();
+			// 如果是静态文件,则不创建SESSION
+			if (Servlets.isStaticFile(uri)){
+		        return null;
+			}
+		}
+		super.doCreate(session);
+		logger.debug("doCreate {} {}", session, request != null ? request.getRequestURI() : "");
+    	return session.getId();
+    }
+
+    @Override
+    protected Session doReadSession(Serializable sessionId) {
+		return super.doReadSession(sessionId);
+    }
+    
+    @Override
+    public Session readSession(Serializable sessionId) throws UnknownSessionException {
+    	try{
+    		Session s = null;
+    		HttpServletRequest request = Servlets.getRequest();
+    		if (request != null){
+    			String uri = request.getServletPath();
+    			// 如果是静态文件,则不获取SESSION
+    			if (Servlets.isStaticFile(uri)){
+    				return null;
+    			}
+    			s = (Session)request.getAttribute("session_"+sessionId);
+    		}
+    		if (s != null){
+    			return s;
+    		}
+
+    		Session session = super.readSession(sessionId);
+    		logger.debug("readSession {} {}", sessionId, request != null ? request.getRequestURI() : "");
+    		
+    		if (request != null && session != null){
+    			request.setAttribute("session_"+sessionId, session);
+    		}
+    		
+    		return session;
+    	}catch (UnknownSessionException e) {
+			return null;
+		}
+    }
+
+    /**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @return
+	 */
+	@Override
+	public Collection<Session> getActiveSessions(boolean includeLeave) {
+		return getActiveSessions(includeLeave, null, null);
+	}
+    
+    /**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @param principal 根据登录者对象获取活动会话
+	 * @param filterSession 不为空,则过滤掉(不包含)这个会话。
+	 * @return
+	 */
+	@Override
+	public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession) {
+		// 如果包括离线,并无登录者条件。
+		if (includeLeave && principal == null){
+			return getActiveSessions();
+		}
+		Set<Session> sessions = Sets.newHashSet();
+		for (Session session : getActiveSessions()){
+			boolean isActiveSession = false;
+			// 不包括离线并符合最后访问时间小于等于3分钟条件。
+			if (includeLeave || DateUtils.pastMinutes(session.getLastAccessTime()) <= 3){
+				isActiveSession = true;
+			}
+			// 符合登陆者条件。
+			if (principal != null){
+				PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+				if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY)){
+					isActiveSession = true;
+				}
+			}
+			// 过滤掉的SESSION
+			if (filterSession != null && filterSession.getId().equals(session.getId())){
+				isActiveSession = false;
+			}
+			if (isActiveSession){
+				sessions.add(session);
+			}
+		}
+		return sessions;
+	}
+	
+}

+ 275 - 0
src/main/java/com/jeeplus/common/security/shiro/session/JedisSessionDAO.java

@@ -0,0 +1,275 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.SimpleSession;
+import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import redis.clients.jedis.Jedis;
+
+import com.google.common.collect.Sets;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.DateUtils;
+import com.jeeplus.common.utils.JedisUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.common.web.Servlets;
+
+/**
+ * 自定义授权会话管理类
+ * @author jeeplus
+ * @version 2014-7-20
+ */
+public class JedisSessionDAO extends AbstractSessionDAO implements SessionDAO {
+
+	private Logger logger = LoggerFactory.getLogger(getClass());
+	
+	private String sessionKeyPrefix = "shiro_session_";
+
+	@Override
+	public void update(Session session) throws UnknownSessionException {
+		if (session == null || session.getId() == null) {  
+            return;
+        }
+		
+		HttpServletRequest request = Servlets.getRequest();
+		if (request != null){
+			String uri = request.getServletPath();
+			// 如果是静态文件,则不更新SESSION
+			if (Servlets.isStaticFile(uri)){
+				return;
+			}
+			// 如果是视图文件,则不更新SESSION
+			if (StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
+					&& StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))){
+				return;
+			}
+			// 手动控制不更新SESSION
+			if (Global.NO.equals(request.getParameter("updateSession"))){
+				return;
+			}
+		}
+		
+		Jedis jedis = null;
+		try {
+			
+			jedis = JedisUtils.getResource();
+			
+			// 获取登录者编号
+			PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+			String principalId = pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY;
+			
+			jedis.hset(sessionKeyPrefix, session.getId().toString(), principalId + "|" + session.getTimeout() + "|" + session.getLastAccessTime().getTime());
+			jedis.set(JedisUtils.getBytesKey(sessionKeyPrefix + session.getId()), JedisUtils.toBytes(session));
+			
+			// 设置超期时间
+			int timeoutSeconds = (int)(session.getTimeout() / 1000);
+			jedis.expire((sessionKeyPrefix + session.getId()), timeoutSeconds);
+
+			logger.debug("update {} {}", session.getId(), request != null ? request.getRequestURI() : "");
+		} catch (Exception e) {
+			logger.error("update {} {}", session.getId(), request != null ? request.getRequestURI() : "", e);
+		} finally {
+			JedisUtils.returnResource(jedis);
+		}
+	}
+
+	@Override
+	public void delete(Session session) {
+		if (session == null || session.getId() == null) {
+			return;
+		}
+		
+		Jedis jedis = null;
+		try {
+			jedis = JedisUtils.getResource();
+			
+			jedis.hdel(JedisUtils.getBytesKey(sessionKeyPrefix), JedisUtils.getBytesKey(session.getId().toString()));
+			jedis.del(JedisUtils.getBytesKey(sessionKeyPrefix + session.getId()));
+
+			logger.debug("delete {} ", session.getId());
+		} catch (Exception e) {
+			logger.error("delete {} ", session.getId(), e);
+		} finally {
+			JedisUtils.returnResource(jedis);
+		}
+	}
+	
+	@Override
+	public Collection<Session> getActiveSessions() {
+		return getActiveSessions(true);
+	}
+	
+	/**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @return
+	 */
+	@Override
+	public Collection<Session> getActiveSessions(boolean includeLeave) {
+		return getActiveSessions(includeLeave, null, null);
+	}
+	
+	/**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @param principal 根据登录者对象获取活动会话
+	 * @param filterSession 不为空,则过滤掉(不包含)这个会话。
+	 * @return
+	 */
+	@Override
+	public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession){
+		Set<Session> sessions = Sets.newHashSet();
+		
+		Jedis jedis = null;
+		try {
+			jedis = JedisUtils.getResource();
+
+			Map<String, String> map = jedis.hgetAll(sessionKeyPrefix);
+			for (Map.Entry<String, String> e : map.entrySet()){
+				if (StringUtils.isNotBlank(e.getKey()) && StringUtils.isNotBlank(e.getValue())){
+					
+					String[] ss = StringUtils.split(e.getValue(), "|");
+					if (ss != null && ss.length == 3){// jedis.exists(sessionKeyPrefix + e.getKey())){
+						// Session session = (Session)JedisUtils.toObject(jedis.get(JedisUtils.getBytesKey(sessionKeyPrefix + e.getKey())));
+						SimpleSession session = new SimpleSession();
+						session.setId(e.getKey());
+						session.setAttribute("principalId", ss[0]);
+						session.setTimeout(Long.valueOf(ss[1]));
+						session.setLastAccessTime(new Date(Long.valueOf(ss[2])));
+						try{
+							// 验证SESSION
+							session.validate();
+							
+							boolean isActiveSession = false;
+							// 不包括离线并符合最后访问时间小于等于3分钟条件。
+							if (includeLeave || DateUtils.pastMinutes(session.getLastAccessTime()) <= 3){
+								isActiveSession = true;
+							}
+							// 符合登陆者条件。
+							if (principal != null){
+								PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+								if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : StringUtils.EMPTY)){
+									isActiveSession = true;
+								}
+							}
+							// 过滤掉的SESSION
+							if (filterSession != null && filterSession.getId().equals(session.getId())){
+								isActiveSession = false;
+							}
+							if (isActiveSession){
+								sessions.add(session);
+							}
+							
+						}
+						// SESSION验证失败
+						catch (Exception e2) {
+							jedis.hdel(sessionKeyPrefix, e.getKey());
+						}
+					}
+					// 存储的SESSION不符合规则
+					else{
+						jedis.hdel(sessionKeyPrefix, e.getKey());
+					}
+				}
+				// 存储的SESSION无Value
+				else if (StringUtils.isNotBlank(e.getKey())){
+					jedis.hdel(sessionKeyPrefix, e.getKey());
+				}
+			}
+			logger.info("getActiveSessions size: {} ", sessions.size());
+		} catch (Exception e) {
+			logger.error("getActiveSessions", e);
+		} finally {
+			JedisUtils.returnResource(jedis);
+		}
+		return sessions;
+	}
+
+	@Override
+	protected Serializable doCreate(Session session) {
+		HttpServletRequest request = Servlets.getRequest();
+		if (request != null){
+			String uri = request.getServletPath();
+			// 如果是静态文件,则不创建SESSION
+			if (Servlets.isStaticFile(uri)){
+		        return null;
+			}
+		}
+		Serializable sessionId = this.generateSessionId(session);
+		this.assignSessionId(session, sessionId);
+		this.update(session);
+		return sessionId;
+	}
+
+	@Override
+	protected Session doReadSession(Serializable sessionId) {
+
+		Session s = null;
+		HttpServletRequest request = Servlets.getRequest();
+		if (request != null){
+			String uri = request.getServletPath();
+			// 如果是静态文件,则不获取SESSION
+			if (Servlets.isStaticFile(uri)){
+				return null;
+			}
+			s = (Session)request.getAttribute("session_"+sessionId);
+		}
+		if (s != null){
+			return s;
+		}
+
+		Session session = null;
+		Jedis jedis = null;
+		try {
+			jedis = JedisUtils.getResource();
+//			if (jedis.exists(sessionKeyPrefix + sessionId)){
+				session = (Session)JedisUtils.toObject(jedis.get(
+						JedisUtils.getBytesKey(sessionKeyPrefix + sessionId)));
+//			}
+			logger.debug("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "");
+		} catch (Exception e) {
+			logger.error("doReadSession {} {}", sessionId, request != null ? request.getRequestURI() : "", e);
+		} finally {
+			JedisUtils.returnResource(jedis);
+		}
+		
+		if (request != null && session != null){
+			request.setAttribute("session_"+sessionId, session);
+		}
+		
+		return session;
+	}
+	
+	@Override
+    public Session readSession(Serializable sessionId) throws UnknownSessionException {
+    	try{
+        	return super.readSession(sessionId);
+    	}catch (UnknownSessionException e) {
+			return null;
+		}
+    }
+
+	public String getSessionKeyPrefix() {
+		return sessionKeyPrefix;
+	}
+
+	public void setSessionKeyPrefix(String sessionKeyPrefix) {
+		this.sessionKeyPrefix = sessionKeyPrefix;
+	}
+
+}

+ 25 - 0
src/main/java/com/jeeplus/common/security/shiro/session/SessionDAO.java

@@ -0,0 +1,25 @@
+package com.jeeplus.common.security.shiro.session;
+
+import java.util.Collection;
+
+import org.apache.shiro.session.Session;
+
+public interface SessionDAO extends org.apache.shiro.session.mgt.eis.SessionDAO {
+
+	/**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @return
+	 */
+	public Collection<Session> getActiveSessions(boolean includeLeave);
+	
+	/**
+	 * 获取活动会话
+	 * @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
+	 * @param principal 根据登录者对象获取活动会话
+	 * @param filterSession 不为空,则过滤掉(不包含)这个会话。
+	 * @return
+	 */
+	public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession);
+	
+}

+ 207 - 0
src/main/java/com/jeeplus/common/security/shiro/session/SessionManager.java

@@ -0,0 +1,207 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.security.shiro.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SimpleSession;
+import org.apache.shiro.web.servlet.Cookie;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+import org.apache.shiro.web.util.WebUtils;
+
+import com.jeeplus.common.utils.StringUtils;
+
+/**
+ * 自定义WEB会话管理类
+ * @author jeeplus
+ * @version 2014-7-20
+ */
+public class SessionManager extends DefaultWebSessionManager {
+
+	public SessionManager() {
+		super();
+	}
+	
+	@Override
+	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
+		// 如果参数中包含“__sid”参数,则使用此sid会话。 例如:http://localhost/project?__sid=xxx&__cookie=true
+		String sid = request.getParameter("__sid");
+		if (StringUtils.isNotBlank(sid)) {
+			// 是否将sid保存到cookie,浏览器模式下使用此参数。
+			if (WebUtils.isTrue(request, "__cookie")){
+		        HttpServletRequest rq = (HttpServletRequest)request;
+		        HttpServletResponse rs = (HttpServletResponse)response;
+				Cookie template = getSessionIdCookie();
+		        Cookie cookie = new SimpleCookie(template);
+				cookie.setValue(sid); cookie.saveTo(rq, rs);
+			}
+			// 设置当前session状态
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                    ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session来源与url
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+        	return sid;
+		}else{
+			return super.getSessionId(request, response);
+		}
+	}
+	
+	@Override
+	public void validateSessions() {
+		super.validateSessions();
+	}
+	
+	protected Session retrieveSession(SessionKey sessionKey) {
+		try{
+			return super.retrieveSession(sessionKey);
+		}catch (UnknownSessionException e) {
+    		// 获取不到SESSION不抛出异常
+			return null;
+		}
+	}
+
+    public Date getStartTimestamp(SessionKey key) {
+    	try{
+    		return super.getStartTimestamp(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public Date getLastAccessTime(SessionKey key) {
+    	try{
+    		return super.getLastAccessTime(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public long getTimeout(SessionKey key){
+    	try{
+    		return super.getTimeout(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return 0;
+		}
+    }
+
+    public void setTimeout(SessionKey key, long maxIdleTimeInMillis) {
+    	try{
+    		super.setTimeout(key, maxIdleTimeInMillis);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+		}
+    }
+
+    public void touch(SessionKey key) {
+    	try{
+	    	super.touch(key);
+		}catch (InvalidSessionException e) {
+			// 获取不到SESSION不抛出异常
+		}
+    }
+
+    public String getHost(SessionKey key) {
+    	try{
+    		return super.getHost(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public Collection<Object> getAttributeKeys(SessionKey key) {
+    	try{
+    		return super.getAttributeKeys(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public Object getAttribute(SessionKey sessionKey, Object attributeKey) {
+    	try{
+    		return super.getAttribute(sessionKey, attributeKey);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) {
+    	try{
+    		super.setAttribute(sessionKey, attributeKey, value);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+		}
+    }
+
+    public Object removeAttribute(SessionKey sessionKey, Object attributeKey) {
+    	try{
+    		return super.removeAttribute(sessionKey, attributeKey);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+        	return null;
+		}
+    }
+
+    public void stop(SessionKey key) {
+    	try{
+    		super.stop(key);
+    	}catch (InvalidSessionException e) {
+    		// 获取不到SESSION不抛出异常
+		}
+    }
+    
+    public void checkValid(SessionKey key) {
+    	try{
+    		super.checkValid(key);
+		}catch (InvalidSessionException e) {
+			// 获取不到SESSION不抛出异常
+		}
+    }
+    
+    @Override
+    protected Session doCreateSession(SessionContext context) {
+    	try{
+    		return super.doCreateSession(context);
+		}catch (IllegalStateException e) {
+			return null;
+		}
+    }
+
+	@Override
+	protected Session newSessionInstance(SessionContext context) {
+		Session session = super.newSessionInstance(context);
+		session.setTimeout(getGlobalSessionTimeout());
+		return session;
+	}
+    
+    @Override
+    public Session start(SessionContext context) {
+    	try{
+    		return super.start(context);
+		}catch (NullPointerException e) {
+			SimpleSession session = new SimpleSession();
+			session.setId(0);
+			return session;
+		}
+    }
+}

+ 517 - 0
src/main/java/com/jeeplus/common/service/BaseService.java

@@ -0,0 +1,517 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.service;
+
+import com.google.common.collect.Lists;
+import com.jeeplus.common.persistence.BaseEntity;
+import com.jeeplus.common.utils.JedisUtils;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.sys.entity.Office;
+import com.jeeplus.modules.sys.entity.Role;
+import com.jeeplus.modules.sys.entity.User;
+import com.jeeplus.modules.sys.service.OfficeService;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import redis.clients.jedis.Jedis;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Service基类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+@Transactional(readOnly = true)
+public abstract class BaseService {
+	
+	/**
+	 * 日志对象
+	 */
+	protected Logger logger = LoggerFactory.getLogger(getClass());
+
+	/**
+	 * 数据范围过滤
+	 * @param user 当前用户对象,通过“entity.getCurrentUser()”获取
+	 * @param officeAlias 机构表别名,多个用“,”逗号隔开。
+	 * @param userAlias 用户表别名,多个用“,”逗号隔开,传递空,忽略此参数
+	 * @return 标准连接条件对象
+	 */
+	public static String dataScopeFilter(User user, String officeAlias, String userAlias,String sAlias,String menuId) {
+		Jedis jedis = null;
+        if (user.isAdmin()) {
+            return "";
+        }
+		try {
+			jedis = JedisUtils.getResource();
+            StringBuilder sqlString = new StringBuilder();
+            String sql = jedis.hget("menu1_"+user.getCompany().getId()+"_"+user.getId(), menuId);
+            if (StringUtils.isNotBlank(sql)) {
+                return sql;
+            } else {
+                List<Role> roleList = UserUtils.getRolesByMenu(user, menuId);
+                List<Role> fnlRoleList = new ArrayList<>();
+                // 进行权限过滤,多个岗位权限范围之间为或者关系。
+                // 超级管理员,跳过权限过滤
+                if (!user.isAdmin()) {
+                    boolean isDataScopeAll = false;
+                    if (roleList != null && roleList.size() > 0) {
+                        for (Role role : roleList) {
+                            if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(role.getDataScope())) {
+                                fnlRoleList.clear();
+                                fnlRoleList.add(role);
+                                break;
+                            }else if(!Role.DATA_SCOPE_SELF.equals(role.getDataScope())){
+                                fnlRoleList.add(role);
+                            }
+                        }
+                    }
+                    for (Role r : fnlRoleList) {
+                        int count = 0;
+                        sqlString.append(" ( (");
+                        for (String oa : StringUtils.split(officeAlias, ",")) {
+                            if (StringUtils.isNotBlank(oa)) {
+                                if (Role.DATA_SCOPE_ALL.equals(r.getDataScope())) {
+                                    isDataScopeAll = true;
+                                } else if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())) {
+                                    sqlString.append(oa + ".id = '" + r.getCompany().getId() + "'");
+                                    sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getCompany().getParentIds() + r.getCompany().getId() + ",%'");
+                                } else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())) {
+                                    if (r.getBranchCompany() != null && StringUtils.isNotBlank(r.getBranchCompany().getId())) {
+                                        sqlString.append(oa + ".branch_office = '" + r.getBranchCompany().getId() + "'");
+                                    } else {
+                                        sqlString.append(oa + ".branch_office is null ");
+                                    }
+                                    sqlString.append("and (" + oa + ".id = '" + r.getCompany().getId() + "'");
+                                    sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getCompany().getParentIds() + r.getCompany().getId() + ",%')");
+                                } else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())) {
+                                    sqlString.append(oa + ".id = '" + r.getOffice().getId() + "'");
+                                    sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getOffice().getParentIds() + r.getOffice().getId() + ",%'");
+                                } else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())) {
+                                    /*sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");*/
+                                    //查看分公司数据
+                                    sqlString.append(oa + ".id = '" + r.getBranchCompany().getId() + "'");
+                                    sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getBranchCompany().getParentIds() + r.getBranchCompany().getId() + ",%'");
+                                } else {
+                                    count = 1;
+                                }
+                            }
+                        }
+                        //}
+                        // 如果没有全部数据权限,并设置了用户别名,则当前权限为本人;如果未设置别名,当前无权限为已植入权限
+                        if (!isDataScopeAll) {
+                            if (StringUtils.isNotBlank(userAlias)) {
+                                if (count == 1) {
+                                    sqlString.append("a.create_by = '" + user.getId() + "'");
+                                } else {
+                                    sqlString.append(" OR " + "a.create_by = '" + user.getId() + "'");
+                                }
+                            } else {
+                                if (count == 1) {
+                                    sqlString.append("a.create_by IS NULL");
+                                } else {
+                                    sqlString.append(" OR " + "a.create_by IS NULL");
+                                }
+                            }
+                            if (StringUtils.isNotBlank(sAlias)) {
+                                for (String sa : StringUtils.split(sAlias, ",")) {
+                                    if (StringUtils.isNotBlank(sa)) {
+                                        if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                            sqlString.append(" OR find_in_set( '" + r.getCompany().getId() + "'," + sa + ".parent_ids)");
+                                        } else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                            // 包括本公司下的部门 (type=1:公司;type=2:部门)
+                                        } else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                        } else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                        } else if (Role.DATA_SCOPE_SELF.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                        } else if (Role.DATA_SCOPE_CUSTOM.equals(r.getDataScope())) {
+                                            sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+                                        }
+                                    }
+                                }
+                            }
+                        } else {
+                            // 如果包含全部权限,则去掉之前添加的所有条件,并跳出循环。
+                            sqlString = new StringBuilder();
+                        }
+                        sqlString.append(") ) OR");
+                    }
+                }
+
+                if (StringUtils.isNotBlank(sqlString.toString())) {
+                    if (roleList.size() > 1) {
+                        sql = "AND(" + sqlString.substring(0, sqlString.length() - 3) + ")";
+                    } else {
+                        sql = "AND" + sqlString.substring(0, sqlString.length() - 3);
+                    }
+                    jedis.hset("menu1_"+user.getCompany().getId()+"_"+user.getId(), menuId, sql);
+                    return sql;
+                }
+            }
+		}catch (Exception e){
+			System.out.println("------------------dataScopeFilter Exception e:"+e);
+		}finally {
+			JedisUtils.returnResource(jedis);
+		}
+		if (StringUtils.isNotBlank(sAlias)){
+			return "AND s.id = '" + user.getCompany().getId() + "' AND a.create_by = '" + user.getId() + "'";
+		}else {
+			return "AND 1=1";
+		}
+	}
+
+	/**
+	 * 数据范围过滤
+	 * @param user 当前用户对象,通过“entity.getCurrentUser()”获取
+	 * @param officeAlias 机构表别名,多个用“,”逗号隔开。
+	 * @param userAlias 用户表别名,多个用“,”逗号隔开,传递空,忽略此参数
+	 * @return 标准连接条件对象
+	 */
+	public static String dataScopeFilterOR(User user, String officeAlias, String userAlias,String sAlias,String menuId) {
+		Jedis jedis = null;
+		try {
+			jedis = JedisUtils.getResource();
+			StringBuilder sqlString = new StringBuilder();
+			String sql = jedis.hget("menu2_"+user.getCompany().getId()+"_"+user.getId(), menuId);
+			if (StringUtils.isNotBlank(sql)) {
+				return sql;
+			} else {
+				List<Role> roleList = UserUtils.getRolesByMenu(user, menuId);
+                List<Role> fnlRoleList = new ArrayList<>();
+				// 超级管理员,跳过权限过滤
+				if (!user.isAdmin()) {
+					boolean isDataScopeAll = false;
+                    if (roleList != null && roleList.size() > 0) {
+                        for (Role role : roleList) {
+                            if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(role.getDataScope())) {
+                                fnlRoleList.clear();
+                                fnlRoleList.add(role);
+                                break;
+                            }else if(!Role.DATA_SCOPE_SELF.equals(role.getDataScope())){
+                                fnlRoleList.add(role);
+                            }
+                        }
+                    }
+					for (Role r : fnlRoleList) {
+						int count = 0;
+						sqlString.append(" ( (");
+						for (String oa : StringUtils.split(officeAlias, ",")) {
+							if (StringUtils.isNotBlank(oa)) {
+								if (Role.DATA_SCOPE_ALL.equals(r.getDataScope())) {
+									isDataScopeAll = true;
+								} else if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())) {
+									sqlString.append(oa + ".id = '" + r.getCompany().getId() + "'");
+									sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getCompany().getParentIds() + r.getCompany().getId() + ",%'");
+								} else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())) {
+									if (r.getBranchCompany() != null && StringUtils.isNotBlank(r.getBranchCompany().getId())) {
+										sqlString.append(oa + ".branch_office = '" + r.getBranchCompany().getId() + "'");
+									} else {
+										sqlString.append(oa + ".branch_office is null ");
+									}
+									sqlString.append("and (" + oa + ".id = '" + r.getCompany().getId() + "'");
+									sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getCompany().getParentIds() + r.getCompany().getId() + ",%')");
+								/*sqlString.append(oa + ".id = '" + user.getCompany().getId() + "'");
+								// 包括本公司下的部门 (type=1:公司;type=2:部门)
+								sqlString.append(" OR (" + oa + ".parent_id = '" + user.getCompany().getId() + "' AND " + oa + ".type = '2')");
+								*/
+								} else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())) {
+									sqlString.append(oa + ".id = '" + r.getOffice().getId() + "'");
+									sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getOffice().getParentIds() + r.getOffice().getId() + ",%'");
+								} else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())) {
+									/*sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");*/
+									//查看分公司数据
+									sqlString.append(oa + ".id = '" + r.getBranchCompany().getId() + "'");
+									sqlString.append(" OR " + oa + ".parent_ids LIKE '" + r.getBranchCompany().getParentIds() + r.getBranchCompany().getId() + ",%'");
+								} else {
+									count = 1;
+								}
+							}
+						}
+						//}
+						// 如果没有全部数据权限,并设置了用户别名,则当前权限为本人;如果未设置别名,当前无权限为已植入权限
+						if (!isDataScopeAll) {
+							if (StringUtils.isNotBlank(userAlias)) {
+								if (count == 1) {
+									sqlString.append("a.create_by = '" + user.getId() + "'");
+								} else {
+									sqlString.append(" OR " + "a.create_by = '" + user.getId() + "'");
+								}
+							} else {
+								if (count == 1) {
+									sqlString.append("a.create_by IS NULL");
+								} else {
+									sqlString.append(" OR " + "a.create_by IS NULL");
+								}
+							}
+							if (StringUtils.isNotBlank(sAlias)) {
+								for (String sa : StringUtils.split(sAlias, ",")) {
+									if (StringUtils.isNotBlank(sa)) {
+										if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())) {
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+											sqlString.append(" OR find_in_set( '" + r.getCompany().getId() + "'," + sa + ".parent_ids)");
+										} else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())) {
+											// 包括本公司下的部门 (type=1:公司;type=2:部门)
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+										} else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())) {
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+										} else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())) {
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+										} else if (Role.DATA_SCOPE_SELF.equals(r.getDataScope())) {
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+										} else if (Role.DATA_SCOPE_CUSTOM.equals(r.getDataScope())) {
+											sqlString.append(" ) AND ( " + sa + ".id = '" + r.getCompany().getId() + "'");
+										}
+									}
+								}
+							}
+						} else {
+							// 如果包含全部权限,则去掉之前添加的所有条件,并跳出循环。
+							sqlString = new StringBuilder();
+						}
+						sqlString.append(") ) OR");
+					}
+				}
+				if (StringUtils.isNotBlank(sqlString.toString())) {
+					if (roleList.size() > 1) {
+						sql = "OR(" + sqlString.substring(0, sqlString.length() - 3) + ")";
+					} else {
+						sql = "OR" + sqlString.substring(0, sqlString.length() - 3);
+					}
+					jedis.hset("menu2_"+user.getCompany().getId()+"_"+user.getId(), menuId, sql);
+					return sql;
+				}
+			}
+		}catch (Exception e){
+			System.out.println("------------------dataScopeFilterOR Exception e:"+e);
+		}finally {
+			JedisUtils.returnResource(jedis);
+		}
+		if (StringUtils.isNotBlank(sAlias)){
+			return  "OR (s.id = '"+user.getCompany().getId()+"' AND a.create_by = '" + user.getId()+"')";
+		}else {
+			//return  "OR (s.id = '"+user.getCompany().getId()+"' AND a.create_by = '" + user.getId()+"')";
+			return "OR 1=1";
+		}
+	}
+
+
+	/**
+	 * 数据范围过滤 只显示当前企业下数据
+	 * @param user 当前用户对象,通过“entity.getCurrentUser()”获取
+	 * @param officeAlias 机构表别名,多个用“,”逗号隔开。
+	 * @param userAlias 用户表别名,多个用“,”逗号隔开,传递空,忽略此参数
+	 * @return 标准连接条件对象
+	 */
+	public static String dataScopeFilter2(User user, String officeAlias, String userAlias) {
+
+		StringBuilder sqlString = new StringBuilder();
+		
+		// 进行权限过滤,多个岗位权限范围之间为或者关系。
+		List<String> dataScope = Lists.newArrayList();
+		
+		// 超级管理员,跳过权限过滤
+		if (!user.isAdmin()){
+			boolean isDataScopeAll = false;
+			Role r =UserUtils.getSelectRole().get(0);
+			//for (Role r : user.getRoleList()){
+			user.setCompany(UserUtils.getSelectCompany());
+			user.setOffice(UserUtils.getSelectOffice());
+				for (String oa : StringUtils.split(officeAlias, ",")){
+					if (!dataScope.contains(r.getDataScope()) && StringUtils.isNotBlank(oa)){
+						if (Role.DATA_SCOPE_ALL.equals(r.getDataScope())){
+							isDataScopeAll = true;
+						}
+						else if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())){
+							sqlString.append(" OR " + oa + ".id = '" + user.getCompany().getId() + "'");
+							sqlString.append(" OR " + oa + ".parent_ids LIKE '" + user.getCompany().getParentIds() + user.getCompany().getId() + ",%'");
+						}
+						else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())){
+							sqlString.append(" OR " + oa + ".id = '" + user.getCompany().getId() + "'");
+							// 包括本公司下的部门 (type=1:公司;type=2:部门)
+							sqlString.append(" OR (" + oa + ".parent_id = '" + user.getCompany().getId() + "' AND " + oa + ".type = '2')");
+						}
+						else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())){
+							sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");
+							sqlString.append(" OR " + oa + ".parent_ids LIKE '" + user.getOffice().getParentIds() + user.getOffice().getId() + ",%'");
+						}
+						else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())){
+							//查看分公司数据
+							sqlString.append(" OR " + oa + ".id = '" + user.getBranchOffice().getId() + "'");
+							sqlString.append(" OR " + oa + ".parent_ids LIKE '" + user.getBranchOffice().getParentIds() + user.getBranchOffice().getId() + ",%'");
+
+							/*sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");*/
+						}
+						else if (Role.DATA_SCOPE_CUSTOM.equals(r.getDataScope())){
+//							String officeIds =  StringUtils.join(r.getOfficeIdList(), "','");
+//							if (StringUtils.isNotEmpty(officeIds)){
+//								sqlString.append(" OR " + oa + ".id IN ('" + officeIds + "')");
+//							}
+							sqlString.append(" OR EXISTS (SELECT 1 FROM sys_role_office WHERE role_id = '" + r.getId() + "'");
+							sqlString.append(" AND office_id = " + oa +".id)");
+						}
+						//else if (Role.DATA_SCOPE_SELF.equals(r.getDataScope())){
+						dataScope.add(r.getDataScope());
+					}
+				}
+			//}
+			// 如果没有全部数据权限,并设置了用户别名,则当前权限为本人;如果未设置别名,当前无权限为已植入权限
+			if (!isDataScopeAll){
+				if (StringUtils.isNotBlank(userAlias)){
+					for (String ua : StringUtils.split(userAlias, ",")){
+						sqlString.append(" OR " + ua + ".id = '" + user.getId() + "'");
+					}
+				}else {
+					for (String oa : StringUtils.split(officeAlias, ",")){
+						//sqlString.append(" OR " + oa + ".id  = " + user.getOffice().getId());
+						sqlString.append(" OR " + oa + ".id IS NULL");
+					}
+				}
+			}else{
+				// 如果包含全部权限,则去掉之前添加的所有条件,并跳出循环。
+				sqlString = new StringBuilder();
+			}
+		}
+		if (StringUtils.isNotBlank(sqlString.toString())){
+			return " AND (" + sqlString.substring(4) + ")";
+		}
+		return "";
+	}
+	
+	
+	/**
+	 * 数据范围过滤(符合业务表字段不同的时候使用,采用exists方法)
+	 * @param entity 当前过滤的实体类
+	 * @param sqlMapKey sqlMap的键值,例如设置“dsf”时,调用方法:${sqlMap.sdf}
+	 * @param officeWheres office表条件,组成:部门表字段=业务表的部门字段
+	 * @param userWheres user表条件,组成:用户表字段=业务表的用户字段
+	 * @example
+	 * 		dataScopeFilter(user, "dsf", "id=a.office_id", "id=a.create_by");
+	 * 		dataScopeFilter(entity, "dsf", "code=a.jgdm", "no=a.cjr"); // 适应于业务表关联不同字段时使用,如果关联的不是机构id是code。
+	 */
+	public static void dataScopeFilter(BaseEntity<?> entity, String sqlMapKey, String officeWheres, String userWheres) {
+
+		User user = entity.getCurrentUser();
+		
+		// 如果是超级管理员,则不过滤数据
+		if (user.isAdmin()) {
+			return;
+		}
+
+		// 数据范围(1:所有数据;2:所在公司及以下数据;3:所在公司数据;4:所在部门及以下数据;5:所在部门数据;8:仅本人数据;9:按明细设置)
+		StringBuilder sqlString = new StringBuilder();
+		
+		// 获取到最大的数据权限范围
+		String roleId = "";
+		int dataScopeInteger = 8;
+		//for (Role r : user.getRoleList()){
+		Role r =UserUtils.getSelectRole().get(0);
+		//for (Role r : user.getRoleList()){
+		user.setCompany(UserUtils.getSelectCompany());
+		user.setOffice(UserUtils.getSelectOffice());
+			int ds = Integer.valueOf(r.getDataScope());
+			if (ds == 9){
+				roleId = r.getId();
+				dataScopeInteger = ds;
+				//break;
+			}else if (ds < dataScopeInteger){
+				roleId = r.getId();
+				dataScopeInteger = ds;
+			}
+		//}
+		String dataScopeString = String.valueOf(dataScopeInteger);
+		
+		// 生成部门权限SQL语句
+		for (String where : StringUtils.split(officeWheres, ",")){
+			if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(dataScopeString)){
+				// 包括本公司下的部门 (type=1:公司;type=2:部门)
+				sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
+				sqlString.append(" WHERE type='2'");
+				sqlString.append(" AND (id = '" + user.getCompany().getId() + "'");
+				sqlString.append(" OR parent_ids LIKE '" + user.getCompany().getParentIds() + user.getCompany().getId() + ",%')");
+				sqlString.append(" AND " + where +")");
+			}
+			else if (Role.DATA_SCOPE_COMPANY.equals(dataScopeString)){
+				sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
+				sqlString.append(" WHERE type='2'");
+				sqlString.append(" AND id = '" + user.getCompany().getId() + "'");
+				sqlString.append(" AND " + where +")");
+			}
+			else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(dataScopeString)){
+				sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
+				sqlString.append(" WHERE (id = '" + user.getOffice().getId() + "'");
+				sqlString.append(" OR parent_ids LIKE '" + user.getOffice().getParentIds() + user.getOffice().getId() + ",%')");
+				sqlString.append(" AND " + where +")");
+			}
+			else if (Role.DATA_SCOPE_OFFICE.equals(dataScopeString)){
+				sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
+				sqlString.append(" WHERE id = '" + user.getOffice().getId() + "'");
+				sqlString.append(" AND " + where +")");
+			}
+			else if (Role.DATA_SCOPE_CUSTOM.equals(dataScopeString)){
+				sqlString.append(" AND EXISTS (SELECT 1 FROM sys_role_office ro123456, sys_office o123456");
+				sqlString.append(" WHERE ro123456.office_id = o123456.id");
+				sqlString.append(" AND ro123456.role_id = '" + roleId + "'");
+				sqlString.append(" AND o123456." + where +")");
+			}
+		}
+		// 生成个人权限SQL语句
+		for (String where : StringUtils.split(userWheres, ",")){
+			if (Role.DATA_SCOPE_SELF.equals(dataScopeString)){
+				sqlString.append(" AND EXISTS (SELECT 1 FROM sys_user");
+				sqlString.append(" WHERE id='" + user.getId() + "'");
+				sqlString.append(" AND " + where + ")");
+			}
+		}
+
+//		System.out.println("dataScopeFilter: " + sqlString.toString());
+
+		// 设置到自定义SQL对象
+		entity.getSqlMap().put(sqlMapKey, sqlString.toString());
+		
+	}
+
+    /**
+     * @param user
+     * @param sAlias 分公司表别名
+     * @return
+     */
+    public static String dataScopeBranchOfficeFilter(User user, String sAlias) {
+        if(user.isAdmin()){
+            return "";
+        }
+        if(StringUtils.isBlank(sAlias)){
+            return "";
+        }
+        List<Role> selectRoles = UserUtils.getSelectRole();
+        if(selectRoles==null||selectRoles.isEmpty()){
+            return "";
+        }
+
+        Set<String> branchIds = new HashSet<>();
+        for (Role role : selectRoles) {
+            branchIds.add(role.getBranchCompany()==null?null:role.getBranchCompany().getId());
+        }
+
+        StringBuilder sql = new StringBuilder("and "+ sAlias+".id in (");
+        for (String branchId : branchIds) {
+            if(StringUtils.isBlank(branchId)){
+                sql.append("'"+user.getComId()+"',");
+            }else {
+                sql.append("'"+branchId+"',");
+            }
+        }
+        sql.deleteCharAt(sql.length()-1);
+        sql.append(")");
+        return sql.toString();
+    }
+}

+ 115 - 0
src/main/java/com/jeeplus/common/service/CrudService.java

@@ -0,0 +1,115 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.service;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.common.persistence.Page;
+
+/**
+ * Service基类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+@Transactional(readOnly = true)
+public abstract class CrudService<D extends CrudDao<T>, T extends DataEntity<T>> extends BaseService {
+	
+	/**
+	 * 持久层对象
+	 */
+	@Autowired
+	protected D dao;
+	
+	/**
+	 * 获取单条数据
+	 * @param id
+	 * @return
+	 */
+	public T get(String id) {
+		return dao.get(id);
+	}
+	
+	/**
+	 * 获取单条数据
+	 * @param entity
+	 * @return
+	 */
+	public T get(T entity) {
+		return dao.get(entity);
+	}
+	
+	/**
+	 * 查询列表数据
+	 * @param entity
+	 * @return
+	 */
+	public List<T> findList(T entity) {
+		return dao.findList(entity);
+	}
+	
+	/**
+	 * 查询分页数据
+	 * @param page 分页对象
+	 * @param entity
+	 * @return
+	 */
+	public Page<T> findPage(Page<T> page, T entity) {
+		entity.setPage(page);
+		page.setList(dao.findList(entity));
+		return page;
+	}
+
+	/**
+	 * 保存数据(插入或更新)
+	 * @param entity
+	 */
+	@Transactional(readOnly = false)
+	public void save(T entity) {
+		if (entity.getIsNewRecord()){
+			entity.preInsert();
+			dao.insert(entity);
+		}else{
+			entity.preUpdate();
+			dao.update(entity);
+		}
+	}
+	
+	/**
+	 * 删除数据
+	 * @param entity
+	 */
+	@Transactional(readOnly = false)
+	public void delete(T entity) {
+		dao.delete(entity);
+	}
+	
+	
+	/**
+	 * 删除全部数据
+	 * @param entity
+	 */
+	@Transactional(readOnly = false)
+	public void deleteAll(Collection<T> entitys) {
+		for (T entity : entitys) {
+			dao.delete(entity);
+		}
+	}
+
+	
+	/**
+	 * 获取单条数据
+	 * @param id
+	 * @return
+	 */
+	public T findUniqueByProperty(String propertyName, Object value){
+		return dao.findUniqueByProperty(propertyName, value);
+	}
+	
+}

+ 29 - 0
src/main/java/com/jeeplus/common/service/ServiceException.java

@@ -0,0 +1,29 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.service;
+
+/**
+ * Service层公用的Exception, 从由Spring管理事务的函数中抛出时会触发事务回滚.
+ * @author jeeplus
+ */
+public class ServiceException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	public ServiceException() {
+		super();
+	}
+
+	public ServiceException(String message) {
+		super(message);
+	}
+
+	public ServiceException(Throwable cause) {
+		super(cause);
+	}
+
+	public ServiceException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}

+ 83 - 0
src/main/java/com/jeeplus/common/service/TreeService.java

@@ -0,0 +1,83 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.service;
+
+import java.util.List;
+
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeeplus.common.persistence.TreeDao;
+import com.jeeplus.common.persistence.TreeEntity;
+import com.jeeplus.common.utils.Reflections;
+import com.jeeplus.common.utils.StringUtils;
+
+/**
+ * Service基类
+ * @author jeeplus
+ * @version 2014-05-16
+ */
+@Transactional(readOnly = true)
+public abstract class TreeService<D extends TreeDao<T>, T extends TreeEntity<T>> extends CrudService<D, T> {
+	
+	@Transactional(readOnly = false)
+	public void save(T entity) {
+		
+		@SuppressWarnings("unchecked")
+		Class<T> entityClass = Reflections.getClassGenricType(getClass(), 1);
+		
+		// 如果没有设置父节点,则代表为跟节点,有则获取父节点实体
+		if (entity.getParent() == null || StringUtils.isBlank(entity.getParentId()) 
+				|| "0".equals(entity.getParentId())){
+			entity.setParent(null);
+		}else{
+			entity.setParent(super.get(entity.getParentId()));
+		}
+		if (entity.getParent() == null){
+			T parentEntity = null;
+			try {
+				parentEntity = entityClass.getConstructor(String.class).newInstance("0");
+			} catch (Exception e) {
+				throw new ServiceException(e);
+			}
+			entity.setParent(parentEntity);
+			entity.getParent().setParentIds(StringUtils.EMPTY);
+		}
+		
+		// 获取修改前的parentIds,用于更新子节点的parentIds
+		String oldParentIds = entity.getParentIds(); 
+		
+		// 设置新的父节点串
+		entity.setParentIds(entity.getParent().getParentIds()+entity.getParent().getId()+",");
+		
+		// 保存或更新实体
+		super.save(entity);
+		
+		// 更新子节点 parentIds
+		T o = null;
+		try {
+			o = entityClass.newInstance();
+		} catch (Exception e) {
+			throw new ServiceException(e);
+		}
+		o.setParentIds("%,"+entity.getId()+",%");
+		List<T> list = dao.findByParentIdsLike(o);
+		for (T e : list){
+			if (e.getParentIds() != null && oldParentIds != null){
+				e.setParentIds(e.getParentIds().replace(oldParentIds, entity.getParentIds()));
+				preUpdateChild(entity, e);
+				dao.updateParentIds(e);
+			}
+		}
+		
+	}
+	
+	/**
+	 * 预留接口,用户更新子节前调用
+	 * @param childEntity
+	 */
+	protected void preUpdateChild(T entity, T childEntity) {
+		
+	}
+
+}

+ 150 - 0
src/main/java/com/jeeplus/common/servlet/ValidateCodeServlet.java

@@ -0,0 +1,150 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.common.servlet;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 生成随机验证码
+ * @author jeeplus
+ * @version 2014-7-27
+ */
+@SuppressWarnings("serial")
+public class ValidateCodeServlet extends HttpServlet {
+	
+	public static final String VALIDATE_CODE = "validateCode";
+	
+	private int w = 70;
+	private int h = 26;
+	
+	public ValidateCodeServlet() {
+		super();
+	}
+	
+	public void destroy() {
+		super.destroy(); 
+	}
+	
+	public static boolean validate(HttpServletRequest request, String validateCode){
+		String code = (String)request.getSession().getAttribute(VALIDATE_CODE);
+		return validateCode.toUpperCase().equals(code); 
+	}
+
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		String validateCode = request.getParameter(VALIDATE_CODE); // AJAX验证,成功返回true
+		if (StringUtils.isNotBlank(validateCode)){
+			response.getOutputStream().print(validate(request, validateCode)?"true":"false");
+		}else{
+			this.doPost(request, response);
+		}
+	}
+
+	public void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		createImage(request,response);
+	}
+	
+	private void createImage(HttpServletRequest request,
+			HttpServletResponse response) throws IOException {
+		
+		response.setHeader("Pragma", "no-cache");
+		response.setHeader("Cache-Control", "no-cache");
+		response.setDateHeader("Expires", 0);
+		response.setContentType("image/jpeg");
+		
+		/*
+		 * 得到参数高,宽,都为数字时,则使用设置高宽,否则使用默认值
+		 */
+		String width = request.getParameter("width");
+		String height = request.getParameter("height");
+		if (StringUtils.isNumeric(width) && StringUtils.isNumeric(height)) {
+			w = NumberUtils.toInt(width);
+			h = NumberUtils.toInt(height);
+		}
+		
+		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+		Graphics g = image.getGraphics();
+
+		/*
+		 * 生成背景
+		 */
+		createBackground(g);
+
+		/*
+		 * 生成字符
+		 */
+		String s = createCharacter(g);
+		request.getSession().setAttribute(VALIDATE_CODE, s);
+
+		g.dispose();
+		OutputStream out = response.getOutputStream();
+		ImageIO.write(image, "JPEG", out);
+		out.close();
+
+	}
+	
+	private Color getRandColor(int fc,int bc) { 
+		int f = fc;
+		int b = bc;
+		Random random=new Random();
+        if(f>255) {
+        	f=255; 
+        }
+        if(b>255) {
+        	b=255; 
+        }
+        return new Color(f+random.nextInt(b-f),f+random.nextInt(b-f),f+random.nextInt(b-f)); 
+	}
+	
+	private void createBackground(Graphics g) {
+		// 填充背景
+		g.setColor(getRandColor(220,250)); 
+		g.fillRect(0, 0, w, h);
+		// 加入干扰线条
+		for (int i = 0; i < 8; i++) {
+			g.setColor(getRandColor(40,150));
+			Random random = new Random();
+			int x = random.nextInt(w);
+			int y = random.nextInt(h);
+			int x1 = random.nextInt(w);
+			int y1 = random.nextInt(h);
+			g.drawLine(x, y, x1, y1);
+		}
+	}
+
+	private String createCharacter(Graphics g) {
+		char[] codeSeq = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
+				'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+				'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };
+		String[] fontTypes = {"Arial","Arial Black","AvantGarde Bk BT","Calibri"}; 
+		Random random = new Random();
+		StringBuilder s = new StringBuilder();
+		for (int i = 0; i < 4; i++) {
+			String r = String.valueOf(codeSeq[random.nextInt(codeSeq.length)]);//random.nextInt(10));
+			g.setColor(new Color(50 + random.nextInt(100), 50 + random.nextInt(100), 50 + random.nextInt(100)));
+			g.setFont(new Font(fontTypes[random.nextInt(fontTypes.length)],Font.BOLD,26)); 
+			g.drawString(r, 15 * i + 5, 19 + random.nextInt(8));
+//			g.drawString(r, i*w/4, h-5);
+			s.append(r);
+		}
+		return s.toString();
+	}
+	
+}

+ 192 - 0
src/main/java/com/jeeplus/common/sms/SMSUtils.java

@@ -0,0 +1,192 @@
+package com.jeeplus.common.sms;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.security.Digests;
+import com.jeeplus.modules.esignature.utils.MD5;
+import net.sf.json.JSONObject;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+import sun.misc.BASE64Encoder;
+
+/*
+功能:		企信通PHP HTTP接口 发送短信
+修改日期:	2014-03-19
+说明:		http://api.cnsms.cn/?ac=send&uid=用户账号&pwd=MD5位32密码&mobile=号码&content=内容
+状态:
+	100 发送成功
+	101 验证失败
+	102 短信不足
+	103 操作失败
+	104 非法字符
+	105 内容过多
+	106 号码过多
+	107 频率过快
+	108 号码内容空
+	109 账号冻结
+	110 禁止频繁单条发送
+	111 系统暂定发送
+	112 号码不正确
+	120 系统升级
+*/
+public class SMSUtils {
+    //发送短信,uid,pwd,参数值请向企信通申请, tel:发送的手机号, content:发送的内容
+    public static String send(String mobile,String content,String sendTime,String checkcontent) throws IOException {
+        BufferedReader in = null;
+        String result = "";
+        try {
+            // 创建StringBuffer对象用来操作字符串
+            StringBuffer sb = new StringBuffer("http://www.lcqxt.com/sms.aspx?action=send");
+
+            // 向StringBuffer追加用户名
+            sb.append("&userid=" + Global.getSmsUserid());//在此申请企信通uid,并进行配置用户名
+
+            // 向StringBuffer追加密码(密码采用MD5 32位 小写)
+            sb.append("&account=" + Global.getSmsAccount());
+
+            // 向StringBuffer追加密码(密码采用MD5 32位 小写)
+            //sb.append("&pwd="+Digests.string2MD5(pwd));//在此申请企信通uid,并进行配置密码
+            sb.append("&password=" + Global.getSmsPassword());
+            // 向StringBuffer追加手机号码
+            sb.append("&mobile=" + mobile);
+            // 向StringBuffer追加消息内容转URL标准码
+            sb.append("&content=" + URLEncoder.encode(content, "utf8"));
+            sb.append("&sendTime=" + sendTime);
+            sb.append("&checkcontent=" + checkcontent);
+
+            // 创建url对象
+            URL url = new URL(sb.toString());
+
+            // 打开url连接
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+            // 设置url请求方式 ‘get’ 或者 ‘post’
+            connection.setRequestMethod("POST");
+
+            // 发送
+            in = new BufferedReader(new InputStreamReader(url.openStream()));
+
+            // 返回发送结果
+            String line;
+            while ((line = in.readLine()) != null) {
+                result += line;
+            }
+        }catch (Exception e) {
+            System.out.println("发送 POST 请求出现异常!"+e);
+            e.printStackTrace();
+        }
+        //使用finally块来关闭输出流、输入流
+        finally{
+            try{
+                if(in!=null){
+                    in.close();
+                }
+            }
+            catch(IOException ex){
+                ex.printStackTrace();
+            }
+            return result;
+        }
+    }
+    //容联云通讯(160040--超过同模板同号码上限)
+    public static String sends(String mobile,String randomCode) throws IOException {
+        BufferedReader reader = null;
+        StringBuffer result = new StringBuffer("");
+        try {
+            StringBuffer sb = new StringBuffer("https://app.cloopen.com:8883/2013-12-26/Accounts/"+Global.getRongUserid()+"/SMS/TemplateSMS?");
+            StringBuffer sbr = new StringBuffer();
+            String aid = Global.getRongUserid();
+            String atoken = Global.getRongToken();
+            SimpleDateFormat sdf =   new SimpleDateFormat( "yyyyMMddHHmmss" );
+            String time =sdf.format(new Date());
+            sbr.append(aid).append(atoken).append(time);
+            //模板指定参数
+            String [] s = new String[]{randomCode,"10"};
+            //sig
+            sb.append("sig="+ MD5.toMD5(sbr.toString()));
+
+            // 创建url对象
+            URL url = new URL(sb.toString());
+
+            // 打开url连接
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+            // 设置url请求方式 ‘get’ 或者 ‘post’
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Accept","application/json");
+            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
+            String an = Global.getRongUserid()+":"+time;
+            BASE64Encoder encoder = new BASE64Encoder();
+            an = encoder.encode(an.getBytes("UTF-8"));
+            connection.setRequestProperty("Authorization", an);
+            connection.setRequestProperty("Content-Length", "256");
+            connection.setDoOutput(true);
+            connection.connect();
+            //post请求
+            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+            JSONObject obj = new JSONObject();
+            obj.element("to", mobile);
+            obj.element("appId", Global.getAppId());
+            obj.element("templateId", Global.getTemplateId());
+            obj.element("datas", s);
+
+            out.writeBytes(obj.toString());
+            out.flush();
+            out.close();
+            // 发送
+            reader = new BufferedReader(new InputStreamReader(
+                    connection.getInputStream()));
+            String lines;
+            while ((lines = reader.readLine()) != null) {
+                lines = new String(lines.getBytes(), "utf-8");
+                result.append(lines);
+            }
+            System.out.println(result);
+            // 断开连接
+            connection.disconnect();
+        }catch (Exception e) {
+            System.out.println("发送 POST 请求出现异常!"+e);
+            e.printStackTrace();
+        }
+        //使用finally块来关闭输出流、输入流
+        finally{
+            try{
+                if(reader!=null){
+                    reader.close();
+                }
+            }
+            catch(IOException ex){
+                ex.printStackTrace();
+            }
+            return result.toString();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        String inputline =sends("15201428039","系统预警");
+        System.out.println(inputline);
+        long sys = System.currentTimeMillis();
+        System.out.println(sys);
+    }
+
+
+
+}

+ 128 - 0
src/main/java/com/jeeplus/common/tag/AceMenuTag.java

@@ -0,0 +1,128 @@
+package com.jeeplus.common.tag;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.SpringContextHolder;
+import com.jeeplus.modules.sys.entity.Menu;
+import com.jeeplus.modules.sys.utils.UserUtils;
+
+/**
+ * 
+ * 类描述:菜单标签
+ * 
+ * 刘高峰
+ * 
+ * @date: 日期:2015-1-23 时间:上午10:17:45
+ * @version 1.0
+ */
+public class AceMenuTag extends TagSupport {
+	private static final long serialVersionUID = 1L;
+	protected Menu menu;// 菜单Map
+
+	public Menu getMenu() {
+		return menu;
+	}
+
+	public void setMenu(Menu menu) {
+		this.menu = menu;
+	}
+
+	public int doStartTag() throws JspTagException {
+		return EVAL_PAGE;
+	}
+
+	public int doEndTag() throws JspTagException {
+		try {
+			JspWriter out = this.pageContext.getOut();
+			String menu = (String) this.pageContext.getSession().getAttribute(
+					"menu");
+			if (menu != null) {
+				out.print(menu);
+			} else {
+				menu = end().toString();
+				out.print(menu);
+
+			}
+
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return EVAL_PAGE;
+	}
+
+	public StringBuffer end() {
+		StringBuffer sb = new StringBuffer();
+		sb.append(getChildOfTree(menu, 0, UserUtils.getMenuList()));
+
+		System.out.println(sb);
+		return sb;
+
+	}
+
+	private static String getChildOfTree(Menu parent, int level,  List<Menu> menuList) {
+		StringBuffer menuString = new StringBuffer();
+		String href = "";
+		if (!parent.hasPermisson())return "";
+		ServletContext context = SpringContextHolder.getBean(ServletContext.class);
+		if (parent.getHref() != null && parent.getHref().length() > 0) {
+			if (parent.getHref().startsWith("http://")) {// 如果是互联网资源
+				href = parent.getHref();
+			} else if ((parent.getHref().endsWith(".html")) && !parent.getHref().endsWith("ckfinder.html")) {// 如果是静态资源并且不是ckfinder.html,直接访问不加adminPath
+				href = context.getContextPath() + parent.getHref();
+			} else {
+				href = context.getContextPath() + Global.getAdminPath()+ parent.getHref();
+			}
+		}
+		//修复Bug,判断根节点
+		boolean isLeaf = true;
+		for (Menu child : menuList) {
+			if (child.getParentId().equals(parent.getId())&&child.getIsShow().equals("1")) {
+				isLeaf = false;
+				break;
+			}
+		}
+		if(level > 0 && parent.getIsShow().equals("1")){
+			menuString.append("<li id=\""+parent.getId()+"\">");
+			if(isLeaf){
+				menuString.append("<a class=\"J_menuItem\" href=\"" + href + "\">");
+				menuString.append("<i class=\"menu-icon fa " + parent.getIcon() + "\"></i>");
+				menuString.append("<span class=\"menu-text\">"+parent.getName()+"</span>");
+				menuString.append("<b class=\"arrow\"></b>");
+				menuString.append("</a>");
+			}else{
+				menuString.append("<a  href=\"" + href + "\" class=\"dropdown-toggle\">");
+				menuString.append("<i class=\"menu-icon fa " + parent.getIcon() + "\"></i>");
+				menuString.append("<span class=\"menu-text\">"+parent.getName()+"</span>");
+				menuString.append("<b class=\"arrow fa fa-angle-down\"></b>");
+				menuString.append("</a>");
+				menuString.append("<b class=\"arrow\"></b>");
+			}
+		}
+		
+		if(!isLeaf && parent.getIsShow().equals("1")){
+			if (level == 0) {
+				menuString.append("<ul class=\"nav nav-list\">");
+			} else {
+				menuString.append("<ul class=\"submenu\">");
+			}
+			for (Menu child : menuList) {
+				if (child.getParentId().equals(parent.getId())&&child.getIsShow().equals("1")) {
+					menuString.append(getChildOfTree(child, level + 1, menuList));
+				}
+			}
+			menuString.append("</ul>");
+		}
+		if (level > 0 && parent.getIsShow().equals("1")) {
+			menuString.append("</li>");
+		}
+		return menuString.toString();
+	}
+
+}

+ 145 - 0
src/main/java/com/jeeplus/common/tag/MenuTag.java

@@ -0,0 +1,145 @@
+package com.jeeplus.common.tag;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.utils.SpringContextHolder;
+import com.jeeplus.modules.sys.entity.Menu;
+import com.jeeplus.modules.sys.utils.UserUtils;
+
+
+/**
+ * 
+ * 类描述:菜单标签
+ * 
+ * 刘高峰
+ * @date: 日期:2015-1-23 时间:上午10:17:45
+ * @version 1.0
+ */
+public class MenuTag extends TagSupport {
+	private static final long serialVersionUID = 1L;
+	protected Menu menu;//菜单Map
+	
+	
+
+	public Menu getMenu() {
+		return menu;
+	}
+
+	public void setMenu(Menu menu) {
+		this.menu = menu;
+	}
+
+	public int doStartTag() throws JspTagException {
+		return EVAL_PAGE;
+	}
+
+	public int doEndTag() throws JspTagException {
+		try {
+			JspWriter out = this.pageContext.getOut();
+			String menu = (String) this.pageContext.getSession().getAttribute("menu");
+			if(menu!=null){
+				out.print(menu);
+			}else{
+				menu=end().toString();
+				out.print(menu);
+				
+			}
+			
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return EVAL_PAGE;
+	}
+
+	public StringBuffer end() {
+		StringBuffer sb = new StringBuffer();
+		sb.append(getChildOfTree(menu,0, UserUtils.getMenuList()));
+		
+		//System.out.println(sb);
+		return sb;
+		
+	}
+	
+	private static String getChildOfTree(Menu parent, int level, List<Menu> menuList) {
+		StringBuffer menuString = new StringBuffer();
+		String href = "";
+		if (!parent.hasPermisson())
+			return "";
+		if (level > 0) {//level 为0是功能菜单
+
+			menuString.append("<li>");
+
+			ServletContext context = SpringContextHolder
+					.getBean(ServletContext.class);
+			if (parent.getHref() != null && parent.getHref().length() > 0) {
+				
+				
+				if (parent.getHref().startsWith("http://")) {// 如果是互联网资源
+					href =  parent.getHref();
+				} else if(parent.getHref().endsWith(".html")&&!parent.getHref().endsWith("ckfinder.html")){//如果是静态资源并且不是ckfinder.html,直接访问不加adminPath
+					href = context.getContextPath() + parent.getHref();
+				}else{
+					href = context.getContextPath() + Global.getAdminPath()
+					+ parent.getHref();
+				}
+			}
+		}
+
+
+		if ((parent.getHref() == null || parent.getHref().trim().equals("")) && parent.getIsShow().equals("1")) {//如果是父节点且显示
+			if (level > 0) {
+				if(level == 1){
+					menuString.append("<a title=\"" + parent.getName() +"\" href=\""
+							+ href
+							+ "\"><span class=\"fa arrow menu_more\"></span><i class=\"fa "+parent.getIcon()+"\"></i> <span class=\"nav-label\">"
+							+ parent.getName()
+							+ "</span></a>");
+				}
+				else
+				{
+					menuString.append("<a href=\""
+									+ href
+									+ "\"><span class=\"fa arrow menu_more\"></span><i class=\"fa "+parent.getIcon()+"\"></i> <span class=\"nav-label\">"
+									+ parent.getName()
+									+ "</span></a>");
+				}
+			}
+			if (level == 1) {
+				menuString.append("<ul class=\"nav nav-second-level\">");
+			} else if (level == 2) {
+				menuString.append("<ul class=\"nav nav-third-level\">");
+			} else if (level == 3) {
+				menuString.append("<ul class=\"nav nav-forth-level\">");
+			} else if (level == 4) {
+				menuString.append("<ul class=\"nav nav-fifth-level\">");
+			}
+			for (Menu child : menuList) {
+				if (child.getParentId().equals(parent.getId())&&child.getIsShow().equals("1")) {
+					menuString.append(getChildOfTree(child, level + 1, menuList));
+				}
+			}
+			if (level > 0) {
+			menuString.append("</ul>");
+			}
+		} else {
+			menuString.append("<a class=\"J_menuItem\"  href=\"" + href
+					+ "\" ><span class=\"menu_more\"></span><i class=\"fa "+parent.getIcon()+"\"></i> <span class=\"nav-label\">"+parent.getName()+"</span></a>");
+		}
+		if (level > 0) {
+			menuString.append("</li>");
+		}
+
+		return menuString.toString();
+	}
+	
+	
+
+}

+ 181 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsBarTag.java

@@ -0,0 +1,181 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.axis.CategoryAxis;
+import com.github.abel533.echarts.axis.ValueAxis;
+import com.github.abel533.echarts.code.AxisType;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Symbol;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+public class EChartsBarTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id;
+	private String title;
+	private String subtitle;
+	private String xAxisName;
+	private String yAxisName;
+	private List<String> xAxisData;
+	private Map<String, Integer> yAxisIndex;
+	private Map<String, List<Double>> yAxisData;
+
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/bar'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('" + id+ "'));");
+		// 创建GsonOption对象,即为json字符串
+		GsonOption option = new GsonOption();
+		option.tooltip().trigger(Trigger.axis);
+		option.title(title, subtitle);
+		// 工具栏
+		option.toolbox().show(true).feature(
+		// Tool.mark,
+		// Tool.dataView,
+				Tool.saveAsImage,
+				// new MagicType(Magic.line, Magic.bar,Magic.stack,Magic.tiled),
+				Tool.dataZoom, Tool.restore);
+		option.calculable(true);
+		option.dataZoom().show(true).realtime(true).start(0).end(100);
+
+		// X轴数据封装并解析
+		ValueAxis valueAxis = new ValueAxis();
+		for (String s : xAxisData) {
+			valueAxis.type(AxisType.category).data(s);
+		}
+		// X轴单位
+		valueAxis.name(xAxisName);
+		option.xAxis(valueAxis);
+		for (String key : yAxisData.keySet()) {
+			option.legend().data(key);
+		}
+		// Y轴数据封装并解析
+		String[] unitNameArray = yAxisName.split(",");
+		for (String s : unitNameArray) {
+			CategoryAxis categoryAxis = new CategoryAxis();
+			categoryAxis.type(AxisType.value);
+			option.yAxis(categoryAxis.name(s));
+		}
+		int i = 0;
+		for (String key : yAxisData.keySet()) {
+			// 遍历list得到数据
+			List<Double> list = yAxisData.get(key);
+			Line line = new Line().name(key);
+			for (Double d : list) {
+				// KW与MW单位的转换
+				// if(settingGlobal!=null&&settingGlobal.getIskw()==0){
+				// d = d/1000;
+				// }
+				// 数据为空的话会报错,为空则为零
+				if (d != null) {
+					line.type(SeriesType.bar).data(d);
+				} else {
+					line.type(SeriesType.bar).data(0);
+				}
+
+				if (yAxisIndex != null && yAxisIndex.get(key) != null) {
+					line.type(SeriesType.bar).yAxisIndex(yAxisIndex.get(key));
+					line.symbol(Symbol.none);
+				} else {
+					line.type(SeriesType.bar).yAxisIndex(0);
+					line.symbol(Symbol.none);
+				}
+
+			}
+			option.series(line);
+			i++;
+		}
+		sb.append("var option=" + option.toString() + ";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+		} catch (IOException e) {
+			System.err.print(e);
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public String getxAxisName() {
+		return xAxisName;
+	}
+
+	public void setxAxisName(String xAxisName) {
+		this.xAxisName = xAxisName;
+	}
+
+	public String getyAxisName() {
+		return yAxisName;
+	}
+
+	public void setyAxisName(String yAxisName) {
+		this.yAxisName = yAxisName;
+	}
+
+	public List<String> getxAxisData() {
+		return xAxisData;
+	}
+
+	public void setxAxisData(List<String> xAxisData) {
+		this.xAxisData = xAxisData;
+	}
+
+	public Map<String, Integer> getyAxisIndex() {
+		return yAxisIndex;
+	}
+
+	public void setyAxisIndex(Map<String, Integer> yAxisIndex) {
+		this.yAxisIndex = yAxisIndex;
+	}
+
+	public Map<String, List<Double>> getyAxisData() {
+		return yAxisData;
+	}
+
+	public void setyAxisData(Map<String, List<Double>> yAxisData) {
+		this.yAxisData = yAxisData;
+	}
+
+}

+ 184 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsLineDoubleNumTag.java

@@ -0,0 +1,184 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.axis.CategoryAxis;
+import com.github.abel533.echarts.axis.ValueAxis;
+import com.github.abel533.echarts.code.AxisType;
+import com.github.abel533.echarts.code.LineType;
+import com.github.abel533.echarts.code.PointerType;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Symbol;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+import com.github.abel533.echarts.style.LineStyle;
+
+public class EChartsLineDoubleNumTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id;
+	private String title;
+	private String subtitle;
+	private String xAxisName;
+	private String yAxisName;
+	private Map<String, Integer> yAxisIndex;
+	private Map<String, Double[][]> axisDataArr;
+
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/line'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('" + id
+				+ "'));");
+		// 创建GsonOption对象,即为json字符串
+		GsonOption option = new GsonOption();
+
+		/**
+		 * tooltip : { trigger: 'axis' }
+		 */
+		option.tooltip().trigger(Trigger.axis);
+		option.tooltip().axisPointer().show(true).type(PointerType.cross)
+				.lineStyle(new LineStyle().type(LineType.dashed).width(1));
+
+		/**
+		 * title : { 'text':'2002全国宏观经济关联分析(GDP vs 房地产)', 'subtext':'数据来自国家统计局'
+		 * }
+		 */
+		option.title(title, subtitle);
+
+		/**
+		 * toolbox: { show : true, feature : { mark : {show: true}, dataZoom :
+		 * {show: true}, dataView : {show: true}, magicType : {show: true, type:
+		 * ['line', 'bar', 'stack', 'tiled']}, restore : {show: true},
+		 * saveAsImage : {show: true} } }
+		 */
+		// 工具栏
+		option.toolbox().show(true).feature(
+		// Tool.mark,
+				Tool.dataZoom,
+				// Tool.dataView,
+				// new MagicType(Magic.line, Magic.bar,Magic.stack,Magic.tiled),
+				// Tool.restore,
+				Tool.saveAsImage);
+		option.calculable(true);
+		option.dataZoom().show(true).realtime(true).start(0).end(100);
+
+		/**
+		 * xAxis : [ { type: 'value' } ]
+		 */
+		// X轴数据设置类型
+		ValueAxis valueAxis = new ValueAxis();
+		valueAxis.type(AxisType.value);
+		valueAxis.name(xAxisName);
+		option.xAxis(valueAxis);
+
+		// Y轴数据设置类型
+		CategoryAxis categoryAxis = new CategoryAxis();
+		categoryAxis.type(AxisType.value);
+		categoryAxis.name(yAxisName);
+		option.yAxis(categoryAxis);
+
+		for (String xtitle : axisDataArr.keySet()) {
+			option.legend().data(xtitle);
+		}
+
+		for (String mapkey : axisDataArr.keySet()) {
+			Line line = new Line();
+			// 显示直线,而不是密密麻麻的点,一点都不好看
+			line.name(mapkey).type(SeriesType.line).symbol(Symbol.none);
+			Object[][] dataArr = (Double[][]) axisDataArr.get(mapkey);
+			for (int num = 0; num < dataArr.length; num++) {
+				line.data().add(dataArr[num]);
+			}
+
+			if (yAxisIndex != null && yAxisIndex.get(mapkey) != null) {
+				line.yAxisIndex(yAxisIndex.get(mapkey));
+			} else {
+				line.yAxisIndex(0);
+			}
+			option.series(line);
+		}
+		sb.append("var option="+option.toString()+";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public String getxAxisName() {
+		return xAxisName;
+	}
+
+	public void setxAxisName(String xAxisName) {
+		this.xAxisName = xAxisName;
+	}
+
+	public String getyAxisName() {
+		return yAxisName;
+	}
+
+	public void setyAxisName(String yAxisName) {
+		this.yAxisName = yAxisName;
+	}
+
+	public Map<String, Integer> getyAxisIndex() {
+		return yAxisIndex;
+	}
+
+	public void setyAxisIndex(Map<String, Integer> yAxisIndex) {
+		this.yAxisIndex = yAxisIndex;
+	}
+
+	public Map<String, Double[][]> getAxisDataArr() {
+		return axisDataArr;
+	}
+
+	public void setAxisDataArr(Map<String, Double[][]> axisDataArr) {
+		this.axisDataArr = axisDataArr;
+	}
+	
+}

+ 181 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsLineTag.java

@@ -0,0 +1,181 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.axis.CategoryAxis;
+import com.github.abel533.echarts.axis.ValueAxis;
+import com.github.abel533.echarts.code.AxisType;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Symbol;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+public class EChartsLineTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id ;
+	private String title;
+	private String subtitle;
+	private String xAxisName;
+	private String yAxisName;
+	private List<String> xAxisData;	
+	private Map<String, Integer> yAxisIndex;
+	private Map<String, List<Double>> yAxisData;
+	
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/line'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('"+id+"'));");
+		// 创建GsonOption对象,即为json字符串
+		GsonOption option = new GsonOption();
+		option.tooltip().trigger(Trigger.axis);
+		option.title(title, subtitle);
+		// 工具栏
+		option.toolbox().show(true).feature(
+		// Tool.mark,
+		// Tool.dataView,
+				Tool.saveAsImage,
+				// new MagicType(Magic.line, Magic.bar,Magic.stack,Magic.tiled),
+				Tool.dataZoom, Tool.restore);
+		option.calculable(true);
+		option.dataZoom().show(true).realtime(true).start(0).end(100);
+
+		// X轴数据封装并解析
+		ValueAxis valueAxis = new ValueAxis();
+		for (String s : xAxisData) {
+			valueAxis.type(AxisType.category).data(s);
+		}
+		// X轴单位
+		valueAxis.name(xAxisName);
+		option.xAxis(valueAxis);
+		for (String key : yAxisData.keySet()) {
+			option.legend().data(key);
+		}
+		// Y轴数据封装并解析
+		String[] unitNameArray = yAxisName.split(",");
+		for (String s : unitNameArray) {
+			CategoryAxis categoryAxis = new CategoryAxis();
+			categoryAxis.type(AxisType.value);
+			option.yAxis(categoryAxis.name(s));
+		}
+		int i = 0;
+		for (String key : yAxisData.keySet()) {
+			// 遍历list得到数据
+			List<Double> list = yAxisData.get(key);
+			Line line = new Line().name(key);
+			for (Double d : list) {
+				// KW与MW单位的转换
+				// if(settingGlobal!=null&&settingGlobal.getIskw()==0){
+				// d = d/1000;
+				// }
+				// 数据为空的话会报错,为空则为零
+				if (d != null) {
+					line.type(SeriesType.line).data(d);
+				} else {
+					line.type(SeriesType.line).data(0);
+				}
+
+				if (yAxisIndex != null && yAxisIndex.get(key) != null) {
+					line.type(SeriesType.line).yAxisIndex(yAxisIndex.get(key));
+					line.symbol(Symbol.none);
+				} else {
+					line.type(SeriesType.line).yAxisIndex(0);
+					//显示直线,而不是密密麻麻的点,一点都不好看
+					line.symbol(Symbol.none);
+				}
+			}
+			option.series(line);
+			i++;
+		}
+		sb.append("var option="+option.toString()+";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+		} catch (IOException e) {
+			System.err.print(e);
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public String getxAxisName() {
+		return xAxisName;
+	}
+
+	public void setxAxisName(String xAxisName) {
+		this.xAxisName = xAxisName;
+	}
+
+	public String getyAxisName() {
+		return yAxisName;
+	}
+
+	public void setyAxisName(String yAxisName) {
+		this.yAxisName = yAxisName;
+	}
+
+	public List<String> getxAxisData() {
+		return xAxisData;
+	}
+
+	public void setxAxisData(List<String> xAxisData) {
+		this.xAxisData = xAxisData;
+	}
+
+	public Map<String, Integer> getyAxisIndex() {
+		return yAxisIndex;
+	}
+
+	public void setyAxisIndex(Map<String, Integer> yAxisIndex) {
+		this.yAxisIndex = yAxisIndex;
+	}
+
+	public Map<String, List<Double>> getyAxisData() {
+		return yAxisData;
+	}
+
+	public void setyAxisData(Map<String, List<Double>> yAxisData) {
+		this.yAxisData = yAxisData;
+	}
+	
+}

+ 287 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsLineTimeLineTag.java

@@ -0,0 +1,287 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.axis.CategoryAxis;
+import com.github.abel533.echarts.axis.ValueAxis;
+import com.github.abel533.echarts.code.AxisType;
+import com.github.abel533.echarts.code.Orient;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Symbol;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.code.X;
+import com.github.abel533.echarts.code.Y;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+public class EChartsLineTimeLineTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id;
+	private String title;
+	private String subtitle;
+	private String xAxisName;
+	private String yAxisName;
+	private List<String> xAxisData;
+	private Map<String, Integer> yAxisIndex;
+	private Map<String, List<Double>> yAxisData;
+	private List<String> timelineData;
+	private List<Map<String, List<Double>>> timelineAxisData;
+
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/line'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('" + id
+				+ "'));");
+		GsonOption option = new GsonOption();
+
+		GsonOption options = new GsonOption();
+		/**
+		 * timeline:{ data:[
+		 * '2002-01-01','2003-01-01','2004-01-01','2005-01-01','2006-01-01',
+		 * '2007-01-01','2008-01-01','2009-01-01','2010-01-01','2011-01-01' ],
+		 * label : { formatter : function(s) { return s.slice(0, 4); } },
+		 * autoPlay : true, playInterval : 1000 },
+		 */
+		option.timeline().autoPlay(true).playInterval(1000).label()
+				.formatter("function(s){return s.slice(0, 4);}");
+		for (String key : timelineData) {
+			option.timeline().data(key);
+		}
+		/**
+		 * title : { 'text':'2002全国宏观经济指标', 'subtext':'数据来自国家统计局' },
+		 */
+		options.title(title, subtitle);
+		/**
+		 * tooltip : {'trigger':'axis'},
+		 */
+		options.tooltip().trigger(Trigger.axis);
+		/**
+		 * legend : { x:'right', 'data':['GDP','金融','房地产','第一产业','第二产业','第三产业'],
+		 * 'selected':{ 'GDP':true, '金融':false, '房地产':true, '第一产业':false,
+		 * '第二产业':false, '第三产业':false } },
+		 */
+		options.legend().x(X.right);
+		for (String key : yAxisData.keySet()) {
+			options.legend().data(key);
+		}
+		/**
+		 * toolbox : { 'show':true, orient : 'vertical', x: 'right', y:
+		 * 'center', 'feature':{ 'mark':{'show':true},
+		 * 'dataView':{'show':true,'readOnly':false},
+		 * 'magicType':{'show':true,'type':['line','bar','stack','tiled']},
+		 * 'restore':{'show':true}, 'saveAsImage':{'show':true} } }, calculable
+		 * : true,
+		 */
+		// 工具栏
+		options.toolbox().orient(Orient.vertical).x(X.right).y(Y.center)
+				.show(true).feature(
+				// Tool.mark,
+				// Tool.dataView,
+						Tool.saveAsImage,
+						// new MagicType(Magic.line,
+						// Magic.bar,Magic.stack,Magic.tiled),
+						Tool.dataZoom, Tool.restore);
+		// options.calculable(true);
+		// options.dataZoom().show(true).realtime(true).start(0).end(100);
+		/**
+		 * xAxis : [{ 'type':'category', 'axisLabel':{'interval':0}, 'data':[
+		 * '北京','\n天津','河北','\n山西','内蒙古','\n辽宁','吉林','\n黑龙江',
+		 * '上海','\n江苏','浙江','\n安徽','福建','\n江西','山东','\n河南',
+		 * '湖北','\n湖南','广东','\n广西','海南','\n重庆','四川','\n贵州',
+		 * '云南','\n西藏','陕西','\n甘肃','青海','\n宁夏','新疆' ] }],
+		 */
+		// X轴数据封装并解析
+		ValueAxis valueAxis = new ValueAxis();
+		for (String s : xAxisData) {
+			valueAxis.type(AxisType.category).data(s);
+		}
+		// X轴单位
+		valueAxis.name(xAxisName);
+		options.xAxis(valueAxis);
+		/**
+		 * yAxis : [ { 'type':'value', 'name':'GDP(亿元)', 'max':53500 }, {
+		 * 'type':'value', 'name':'其他(亿元)' } ],
+		 */
+		// Y轴数据封装并解析
+		String[] unitNameArray = yAxisName.split(",");
+		for (String s : unitNameArray) {
+			CategoryAxis categoryAxis = new CategoryAxis();
+			categoryAxis.type(AxisType.value);
+			options.yAxis(categoryAxis.name(s));
+		}
+
+		for (String key : yAxisData.keySet()) {
+			// 遍历list得到数据
+			List<Double> list = yAxisData.get(key);
+			Line line = new Line().name(key);
+			for (Double d : list) {
+				// KW与MW单位的转换
+				// if(settingGlobal!=null&&settingGlobal.getIskw()==0){
+				// d = d/1000;
+				// }
+				// 数据为空的话会报错,为空则为零
+				if (d != null) {
+					line.type(SeriesType.line).data(d);
+				} else {
+					line.type(SeriesType.line).data(0);
+				}
+
+				if (yAxisIndex != null && yAxisIndex.get(key) != null) {
+					line.type(SeriesType.line).yAxisIndex(yAxisIndex.get(key));
+					// 显示直线,而不是密密麻麻的点,一点都不好看
+					line.symbol(Symbol.none);
+				} else {
+					line.type(SeriesType.line).yAxisIndex(0);
+					line.symbol(Symbol.none);
+				}
+
+			}
+			options.series(line);
+		}
+		option.options(options);
+		for (int ii = 1; ii < timelineData.size(); ii++) {
+			Map<String, List<Double>> timelineAxisDataMap = timelineAxisData.get(ii - 1);
+			GsonOption timeLineOption = new GsonOption();
+
+			timeLineOption.title(timelineData.get(ii) + title.substring(4, title.length()),subtitle);
+			for (String key : timelineAxisDataMap.keySet()) {
+				// 遍历list得到数据
+				List<Double> list = timelineAxisDataMap.get(key);
+				Line line = new Line().name(key);
+				for (Double d : list) {
+					// KW与MW单位的转换
+					// if(settingGlobal!=null&&settingGlobal.getIskw()==0){
+					// d = d/1000;
+					// }
+					// 数据为空的话会报错,为空则为零
+					if (d != null) {
+						line.type(SeriesType.line).data(d);
+					} else {
+						line.type(SeriesType.line).data(0);
+					}
+
+					if (yAxisIndex != null && yAxisIndex.get(key) != null) {
+						line.type(SeriesType.line).yAxisIndex(
+								yAxisIndex.get(key));
+						// 显示直线,而不是密密麻麻的点,一点都不好看
+						line.symbol(Symbol.none);
+					} else {
+						line.type(SeriesType.line).yAxisIndex(0);
+						line.symbol(Symbol.none);
+					}
+
+				}
+				timeLineOption.series(line);
+			}
+			option.options(timeLineOption);
+		}
+		sb.append("var option=" + option.toString() + ";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+
+		} catch (IOException e) {
+			System.err.print(e);
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public String getxAxisName() {
+		return xAxisName;
+	}
+
+	public void setxAxisName(String xAxisName) {
+		this.xAxisName = xAxisName;
+	}
+
+	public String getyAxisName() {
+		return yAxisName;
+	}
+
+	public void setyAxisName(String yAxisName) {
+		this.yAxisName = yAxisName;
+	}
+
+	public List<String> getxAxisData() {
+		return xAxisData;
+	}
+
+	public void setxAxisData(List<String> xAxisData) {
+		this.xAxisData = xAxisData;
+	}
+
+	public Map<String, Integer> getyAxisIndex() {
+		return yAxisIndex;
+	}
+
+	public void setyAxisIndex(Map<String, Integer> yAxisIndex) {
+		this.yAxisIndex = yAxisIndex;
+	}
+
+	public Map<String, List<Double>> getyAxisData() {
+		return yAxisData;
+	}
+
+	public void setyAxisData(Map<String, List<Double>> yAxisData) {
+		this.yAxisData = yAxisData;
+	}
+
+	public List<String> getTimelineData() {
+		return timelineData;
+	}
+
+	public void setTimelineData(List<String> timelineData) {
+		this.timelineData = timelineData;
+	}
+
+	public List<Map<String, List<Double>>> getTimelineAxisData() {
+		return timelineAxisData;
+	}
+
+	public void setTimelineAxisData(
+			List<Map<String, List<Double>>> timelineAxisData) {
+		this.timelineAxisData = timelineAxisData;
+	}
+
+}

+ 113 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsPieTag.java

@@ -0,0 +1,113 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.code.Orient;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.code.X;
+import com.github.abel533.echarts.code.Y;
+import com.github.abel533.echarts.data.Data;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+public class EChartsPieTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id;
+	private String title;
+	private String subtitle;
+	private Map<String, Object> orientData;
+
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/pie'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('" + id+ "'));");
+		// 创建GsonOption对象,即为json字符串
+		GsonOption option = new GsonOption();
+		option.tooltip().trigger(Trigger.item).formatter("{a} <br/>{b} : {c} ({d}%)");
+		option.title(title, subtitle);
+		// 工具栏
+		option.toolbox().show(true).feature(
+		// Tool.mark,
+		// Tool.dataView,
+				Tool.saveAsImage
+				// new MagicType(Magic.line, Magic.bar,Magic.stack,Magic.tiled),
+		//		Tool.dataZoom, Tool.restore
+				);
+		option.calculable(true);
+		
+		// 数据轴封装并解析
+		for(String xdata : orientData.keySet()) {
+			option.legend().orient(Orient.horizontal).x(X.left).y(Y.bottom).data(xdata);
+		}
+		
+		if (orientData != null) {
+			Line line = new Line();
+			line.name(title).type(SeriesType.pie);
+			for (String title : orientData.keySet()) {
+				Object value = orientData.get(title);		
+				Data data = new Data().name(title);
+				data.value(value);
+				line.data(data);				
+			}
+			option.series(line);
+		}
+		sb.append("var option=" + option.toString() + ";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+		} catch (IOException e) {
+			System.err.print(e);
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public Map<String, Object> getOrientData() {
+		return orientData;
+	}
+
+	public void setOrientData(Map<String, Object> orientData) {
+		this.orientData = orientData;
+	}
+	
+}

+ 171 - 0
src/main/java/com/jeeplus/common/tag/echarts/EChartsRadarTag.java

@@ -0,0 +1,171 @@
+package com.jeeplus.common.tag.echarts;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+import com.github.abel533.echarts.Polar;
+import com.github.abel533.echarts.code.Orient;
+import com.github.abel533.echarts.code.SeriesType;
+import com.github.abel533.echarts.code.Tool;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.code.X;
+import com.github.abel533.echarts.code.Y;
+import com.github.abel533.echarts.data.Data;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+public class EChartsRadarTag extends BodyTagSupport {
+	private static final long serialVersionUID = 1L;
+	private String id;
+	private String title;
+	private String subtitle;
+	private Integer polarType;
+	private List<Map<String, Object>> orientData;
+
+	@Override
+	public int doStartTag() throws JspException {
+		return BodyTag.EVAL_BODY_BUFFERED;
+	}
+
+	@Override
+	public int doEndTag() throws JspException {
+		StringBuffer sb = new StringBuffer();
+		sb.append("<script type='text/javascript'>");
+		sb.append("require([ 'echarts', 'echarts/chart/radar'], function(ec) {");
+		sb.append("var myChart= ec.init(document.getElementById('" + id+ "'));");
+		// 创建GsonOption对象,即为json字符串
+		GsonOption option = new GsonOption();
+		/**
+		 * title: { text: '实时风向玫瑰图', subtext: '预测时间:' },
+		 */
+		option.title(title, subtitle);
+		/**
+		 * tooltip: { trigger: 'axis' },
+		 */
+		option.tooltip().trigger(Trigger.axis);
+		/**
+		 * polar: [ { indicator: [ { text: '正北(N)', max: 100 }, { text:
+		 * '西北(NW)', max: 100 }, { text: '正西(W)', max: 100 }, { text: '西南(SW)',
+		 * max: 100 }, { text: '正南(S)', max: 100 }, { text: '东南(SE)', max: 100
+		 * }, { text: '正东(E)', max: 100 }, { text: '东北(NE)', max: 100 } ] } ]
+		 */
+		// 工具栏
+		option.toolbox().show(true).feature(
+		// Tool.mark,
+		// Tool.dataView,
+				Tool.saveAsImage
+		// new MagicType(Magic.line, Magic.bar,Magic.stack,Magic.tiled),
+		// Tool.dataZoom,
+		// Tool.restore
+				);
+		Polar polar = new Polar();
+		if (polarType == 8) {
+			polar.indicator(new Data().text("正北(N)").max(100))
+					.indicator(new Data().text("西北(NW)").max(100))
+					.indicator(new Data().text("正西(W)").max(100))
+					.indicator(new Data().text("西南(SW)").max(100))
+					.indicator(new Data().text("正南(S)").max(100))
+					.indicator(new Data().text("东南(SE)").max(100))
+					.indicator(new Data().text("正东(E)").max(100))
+					.indicator(new Data().text("东北(NE)").max(100));
+
+		} else if (polarType == 16) {
+			polar.indicator(new Data().text("正北(N)").max(100))
+					.indicator(new Data().text("北西北(NNW)").max(100))
+					.indicator(new Data().text("西北(NW)").max(100))
+					.indicator(new Data().text("西北西(WNW)").max(100))
+					.indicator(new Data().text("正西(W)").max(100))
+					.indicator(new Data().text("西南西(WSW)").max(100))
+					.indicator(new Data().text("西南(SW)").max(100))
+					.indicator(new Data().text("南西南(SSW)").max(100))
+					.indicator(new Data().text("正南(S)").max(100))
+					.indicator(new Data().text("南东南(SSE)").max(100))
+					.indicator(new Data().text("东南(SE)").max(100))
+					.indicator(new Data().text("东南东(ESE)").max(100))
+					.indicator(new Data().text("正东(E)").max(100))
+					.indicator(new Data().text("东北东(ENE)").max(100))
+					.indicator(new Data().text("东北(NE)").max(100))
+					.indicator(new Data().text("北东北(NNE)").max(100));
+		}
+		option.polar(polar);
+		option.calculable(true);
+
+		/**
+		 * legend: { orient: 'horizontal', x: 'left', y: 'bottom', data: [
+		 * <c:forEach var="item" items="${towerList}" varStatus="status">
+		 * '${item.tower_mater}米风向', </c:forEach> ] },
+		 */
+		
+		if (orientData != null) {
+			for (Map<String, Object> legendMap : orientData) {
+				String title = legendMap.get("title").toString();
+				option.legend().orient(Orient.horizontal).x(X.left).y(Y.bottom).data(title);
+				Line line = new Line();
+				Data data = new Data().name(title);
+				Object[] dataArr = (Double[]) legendMap.get("dataArr");
+				data.value(dataArr);
+				line.type(SeriesType.radar).data(data);
+				option.series(line);
+			}
+		}
+		sb.append("var option="+option.toString()+";");
+		sb.append("myChart.setOption(option);");
+		sb.append("});");
+		sb.append("</script>");
+		try {
+			this.pageContext.getOut().write(sb.toString());
+
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return Tag.EVAL_PAGE;// 继续处理页面
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getSubtitle() {
+		return subtitle;
+	}
+
+	public void setSubtitle(String subtitle) {
+		this.subtitle = subtitle;
+	}
+
+	public Integer getPolarType() {
+		return polarType;
+	}
+
+	public void setPolarType(Integer polarType) {
+		this.polarType = polarType;
+	}
+
+	public List<Map<String, Object>> getOrientData() {
+		return orientData;
+	}
+
+	public void setOrientData(List<Map<String, Object>> orientData) {
+		this.orientData = orientData;
+	}
+	
+
+}

+ 27 - 0
src/main/java/com/jeeplus/common/test/SpringTransactionalContextTests.java

@@ -0,0 +1,27 @@
+package com.jeeplus.common.test;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
+
+/**
+ * Spring 单元测试基类
+ * @author jeeplus
+ * @version 2013-05-15
+ */
+@ActiveProfiles("production")
+@ContextConfiguration(locations = {"/spring-context.xml"})
+public class SpringTransactionalContextTests extends AbstractTransactionalJUnit4SpringContextTests {
+
+	protected DataSource dataSource;
+
+	@Autowired
+	public void setDataSource(DataSource dataSource) {
+		super.setDataSource(dataSource);
+		this.dataSource = dataSource;
+	}
+	
+}

+ 0 - 0
src/main/java/com/jeeplus/common/test/UUIDTest.java


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor