Browse Source

im-chat系统

779513719 5 years ago
parent
commit
acbeee8a6d
100 changed files with 11602 additions and 0 deletions
  1. 8 0
      .gitignore
  2. 191 0
      LICENSE
  3. 154 0
      README.md
  4. BIN
      docs/J-IM开发文档.pdf
  5. 6 0
      jim-common/.gitignore
  6. 71 0
      jim-common/jim-common.iml
  7. 94 0
      jim-common/pom.xml
  8. 86 0
      jim-common/src/main/java/org/jim/common/CommandStat.java
  9. 478 0
      jim-common/src/main/java/org/jim/common/ImAio.java
  10. 32 0
      jim-common/src/main/java/org/jim/common/ImClientGroupContext.java
  11. 52 0
      jim-common/src/main/java/org/jim/common/ImConfig.java
  12. 64 0
      jim-common/src/main/java/org/jim/common/ImConst.java
  13. 20 0
      jim-common/src/main/java/org/jim/common/ImDecoder.java
  14. 168 0
      jim-common/src/main/java/org/jim/common/ImPacket.java
  15. 92 0
      jim-common/src/main/java/org/jim/common/ImSessionContext.java
  16. 70 0
      jim-common/src/main/java/org/jim/common/ImStatus.java
  17. 60 0
      jim-common/src/main/java/org/jim/common/Protocol.java
  18. 21 0
      jim-common/src/main/java/org/jim/common/SessionContext.java
  19. 16 0
      jim-common/src/main/java/org/jim/common/Status.java
  20. 48 0
      jim-common/src/main/java/org/jim/common/cache/CacheChangeType.java
  21. 127 0
      jim-common/src/main/java/org/jim/common/cache/CacheChangedVo.java
  22. 65 0
      jim-common/src/main/java/org/jim/common/cache/ICache.java
  23. 12 0
      jim-common/src/main/java/org/jim/common/cache/IL2Cache.java
  24. 104 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineCache.java
  25. 81 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineCacheManager.java
  26. 56 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfig.java
  27. 64 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfiguration.java
  28. 123 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfigurationFactory.java
  29. 103 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineUtils.java
  30. 39 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/DefaultRemovalListener.java
  31. 6 0
      jim-common/src/main/java/org/jim/common/cache/caffeine/caffeine.properties
  32. 166 0
      jim-common/src/main/java/org/jim/common/cache/caffeineredis/CaffeineRedisCache.java
  33. 101 0
      jim-common/src/main/java/org/jim/common/cache/caffeineredis/CaffeineRedisCacheManager.java
  34. 78 0
      jim-common/src/main/java/org/jim/common/cache/caffeineredis/RedisAsyncRunnable.java
  35. 47 0
      jim-common/src/main/java/org/jim/common/cache/caffeineredis/RedisL2Vo.java
  36. 14 0
      jim-common/src/main/java/org/jim/common/cache/ehcache/EhcacheConst.java
  37. 89 0
      jim-common/src/main/java/org/jim/common/cache/j2cache/J2Cache.java
  38. 105 0
      jim-common/src/main/java/org/jim/common/cache/redis/ExpireVo.java
  39. 65 0
      jim-common/src/main/java/org/jim/common/cache/redis/JedisSubscriber.java
  40. 1295 0
      jim-common/src/main/java/org/jim/common/cache/redis/JedisTemplate.java
  41. 266 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisCache.java
  42. 51 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisCacheManager.java
  43. 94 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisConfiguration.java
  44. 124 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisConfigurationFactory.java
  45. 79 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisExpireUpdateTask.java
  46. 140 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedisLock.java
  47. 71 0
      jim-common/src/main/java/org/jim/common/cache/redis/RedissonTemplate.java
  48. 46 0
      jim-common/src/main/java/org/jim/common/cache/redis/SubRunnable.java
  49. 19 0
      jim-common/src/main/java/org/jim/common/cluster/ICluster.java
  50. 21 0
      jim-common/src/main/java/org/jim/common/cluster/ImCluster.java
  51. 77 0
      jim-common/src/main/java/org/jim/common/cluster/ImClusterConfig.java
  52. 118 0
      jim-common/src/main/java/org/jim/common/cluster/ImClusterVo.java
  53. 56 0
      jim-common/src/main/java/org/jim/common/cluster/redis/RedisCluster.java
  54. 174 0
      jim-common/src/main/java/org/jim/common/cluster/redis/RedisClusterConfig.java
  55. 12 0
      jim-common/src/main/java/org/jim/common/codec/Decoder.java
  56. 13 0
      jim-common/src/main/java/org/jim/common/codec/Encoder.java
  57. 172 0
      jim-common/src/main/java/org/jim/common/config/Config.java
  58. 33 0
      jim-common/src/main/java/org/jim/common/config/DefaultImConfigBuilder.java
  59. 96 0
      jim-common/src/main/java/org/jim/common/config/ImConfigBuilder.java
  60. 56 0
      jim-common/src/main/java/org/jim/common/config/PropertyImConfigBuilder.java
  61. 64 0
      jim-common/src/main/java/org/jim/common/exception/ImException.java
  62. 213 0
      jim-common/src/main/java/org/jim/common/http/Cookie.java
  63. 12 0
      jim-common/src/main/java/org/jim/common/http/GroupContextKey.java
  64. 267 0
      jim-common/src/main/java/org/jim/common/http/HttpConfig.java
  65. 188 0
      jim-common/src/main/java/org/jim/common/http/HttpConst.java
  66. 42 0
      jim-common/src/main/java/org/jim/common/http/HttpConvertPacket.java
  67. 345 0
      jim-common/src/main/java/org/jim/common/http/HttpMultiBodyDecoder.java
  68. 74 0
      jim-common/src/main/java/org/jim/common/http/HttpPacket.java
  69. 68 0
      jim-common/src/main/java/org/jim/common/http/HttpProtocol.java
  70. 349 0
      jim-common/src/main/java/org/jim/common/http/HttpRequest.java
  71. 363 0
      jim-common/src/main/java/org/jim/common/http/HttpRequestDecoder.java
  72. 222 0
      jim-common/src/main/java/org/jim/common/http/HttpResponse.java
  73. 147 0
      jim-common/src/main/java/org/jim/common/http/HttpResponseEncoder.java
  74. 216 0
      jim-common/src/main/java/org/jim/common/http/HttpResponseStatus.java
  75. 37 0
      jim-common/src/main/java/org/jim/common/http/HttpUuid.java
  76. 39 0
      jim-common/src/main/java/org/jim/common/http/KeyValue.java
  77. 26 0
      jim-common/src/main/java/org/jim/common/http/Method.java
  78. 1310 0
      jim-common/src/main/java/org/jim/common/http/MimeType.java
  79. 117 0
      jim-common/src/main/java/org/jim/common/http/RequestLine.java
  80. 62 0
      jim-common/src/main/java/org/jim/common/http/UploadFile.java
  81. 49 0
      jim-common/src/main/java/org/jim/common/http/handler/IHttpRequestHandler.java
  82. 37 0
      jim-common/src/main/java/org/jim/common/http/listener/IHttpServerListener.java
  83. 83 0
      jim-common/src/main/java/org/jim/common/http/session/HttpSession.java
  84. 45 0
      jim-common/src/main/java/org/jim/common/jim.properties
  85. 23 0
      jim-common/src/main/java/org/jim/common/listener/AbstractImBindListener.java
  86. 48 0
      jim-common/src/main/java/org/jim/common/listener/ImBindListener.java
  87. 23 0
      jim-common/src/main/java/org/jim/common/message/AbstractMessageHelper.java
  88. 136 0
      jim-common/src/main/java/org/jim/common/message/MessageHelper.java
  89. 24 0
      jim-common/src/main/java/org/jim/common/packets/AuthReqBody.java
  90. 24 0
      jim-common/src/main/java/org/jim/common/packets/AuthRespBody.java
  91. 165 0
      jim-common/src/main/java/org/jim/common/packets/ChatBody.java
  92. 59 0
      jim-common/src/main/java/org/jim/common/packets/ChatType.java
  93. 49 0
      jim-common/src/main/java/org/jim/common/packets/Client.java
  94. 21 0
      jim-common/src/main/java/org/jim/common/packets/CloseReqBody.java
  95. 199 0
      jim-common/src/main/java/org/jim/common/packets/Command.java
  96. 64 0
      jim-common/src/main/java/org/jim/common/packets/DeviceType.java
  97. 31 0
      jim-common/src/main/java/org/jim/common/packets/ExitGroupNotifyRespBody.java
  98. 144 0
      jim-common/src/main/java/org/jim/common/packets/Group.java
  99. 28 0
      jim-common/src/main/java/org/jim/common/packets/HandshakeBody.java
  100. 0 0
      jim-common/src/main/java/org/jim/common/packets/HeartbeatBody.java

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+.settings/
+/target/
+.idea/
+*.iml
+*.classpath
+*.project
+/jim-parent/
+/jim-common/

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2017 talent-tan
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

File diff suppressed because it is too large
+ 154 - 0
README.md


BIN
docs/J-IM开发文档.pdf


+ 6 - 0
jim-common/.gitignore

@@ -0,0 +1,6 @@
+.settings/
+/target/
+.idea/
+*.iml
+*.classpath
+*.project

+ 71 - 0
jim-common/jim-common.iml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.t-io:tio-core:2.4.0.v20180508-RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.t-io:tio-utils:2.4.0.v20180508-RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.3" level="project" />
+    <orderEntry type="library" name="Maven: cn.hutool:hutool-all:4.0.10" level="project" />
+    <orderEntry type="library" name="Maven: com.google.guava:guava:23.0" level="project" />
+    <orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
+    <orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.0.18" level="project" />
+    <orderEntry type="library" name="Maven: com.google.j2objc:j2objc-annotations:1.1" level="project" />
+    <orderEntry type="library" name="Maven: org.codehaus.mojo:animal-sniffer-annotations:1.14" level="project" />
+    <orderEntry type="library" name="Maven: org.jodd:jodd-core:4.3.2" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.14" level="project" />
+    <orderEntry type="library" name="Maven: org.redisson:redisson:3.7.0" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-common:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-resolver-dns:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-codec-dns:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.24.Final" level="project" />
+    <orderEntry type="library" name="Maven: javax.cache:cache-api:1.0.0" level="project" />
+    <orderEntry type="library" name="Maven: io.projectreactor:reactor-core:3.1.7.RELEASE" level="project" />
+    <orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.2" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.7.9" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.7.9" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.7.9.2" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.7.0" level="project" />
+    <orderEntry type="library" name="Maven: net.bytebuddy:byte-buddy:1.8.11" level="project" />
+    <orderEntry type="library" name="Maven: org.jodd:jodd-bean:3.7.1" level="project" />
+    <orderEntry type="library" name="Maven: nl.basjes.parse.useragent:yauaa:1.4" level="project" />
+    <orderEntry type="library" name="Maven: args4j:args4j:2.33" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.6" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-collections4:4.1" level="project" />
+    <orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.18" level="project" />
+    <orderEntry type="library" name="Maven: com.typesafe:config:1.3.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-io:commons-io:2.5" level="project" />
+    <orderEntry type="library" name="Maven: commons-codec:commons-codec:1.10" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-access:1.2.3" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:jcl-over-slf4j:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:log4j-over-slf4j:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: redis.clients:jedis:2.7.3" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.3" level="project" />
+    <orderEntry type="library" name="Maven: com.jfinal:jfinal:3.2" level="project" />
+    <orderEntry type="library" name="Maven: cglib:cglib-nodep:3.2.5" level="project" />
+    <orderEntry type="library" name="Maven: net.oschina.j2cache:j2cache-core:2.3.21-release" level="project" />
+    <orderEntry type="library" name="Maven: com.github.ben-manes.caffeine:caffeine:2.6.2" level="project" />
+    <orderEntry type="library" name="Maven: de.ruedigermoeller:fst:2.57" level="project" />
+    <orderEntry type="library" name="Maven: org.javassist:javassist:3.21.0-GA" level="project" />
+    <orderEntry type="library" name="Maven: org.objenesis:objenesis:2.5.1" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.25" level="project" />
+    <orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.35" level="project" />
+    <orderEntry type="library" name="Maven: jline:jline:2.14.2" level="project" />
+  </component>
+</module>

+ 94 - 0
jim-common/pom.xml

@@ -0,0 +1,94 @@
+<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>
+  <artifactId>jim-common</artifactId>
+  <packaging>jar</packaging>
+  <name>jim-common</name>
+  <parent>
+		<groupId>org.j-im</groupId>
+		<artifactId>jim-parent</artifactId>
+		<version>2.6.0.v20190114-RELEASE</version>
+		<relativePath>../jim-parent/pom.xml</relativePath>
+  </parent>
+  <dependencies>
+  		
+   		<dependency>
+			<groupId>org.t-io</groupId>
+			<artifactId>tio-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.redisson</groupId>
+			<artifactId>redisson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>nl.basjes.parse.useragent</groupId>
+			<artifactId>yauaa</artifactId>
+		</dependency>
+		
+		<dependency>
+				<groupId>com.typesafe</groupId>
+				<artifactId>config</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+			
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+		</dependency>
+		
+		<!-- slf4j-logback绑定 -->
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-access</artifactId>
+		</dependency>
+		<!-- redirect apache commons logging -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+		<!-- redirect jdk util logging -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jul-to-slf4j</artifactId>
+		</dependency>
+		<!-- redirect log4j -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>log4j-over-slf4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>redis.clients</groupId>
+			<artifactId>jedis</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.jfinal</groupId>
+			<artifactId>jfinal</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.oschina.j2cache</groupId>
+			<artifactId>j2cache-core</artifactId>
+		</dependency>
+    </dependencies>
+    <build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>   
+		        <artifactId>maven-compiler-plugin</artifactId>   
+		        <version>2.3.2</version>   
+		        <configuration>   
+		            <source>${jdk.version}</source>   
+		            <target>${jdk.version}</target>   
+		            <encoding>UTF-8</encoding>   
+		        </configuration> 
+			</plugin>
+		</plugins>
+  	</build>
+</project>

+ 86 - 0
jim-common/src/main/java/org/jim/common/CommandStat.java

@@ -0,0 +1,86 @@
+package org.jim.common;
+
+import org.jim.common.packets.Command;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 
+ * @author wchao 
+ *
+ */
+public class CommandStat
+{
+
+	public final static Map<Command, CommandStat> commandAndCount = new ConcurrentHashMap<>();
+	
+	public final AtomicLong received = new AtomicLong();
+	public final AtomicLong handled = new AtomicLong();
+	public final AtomicLong sent = new AtomicLong();
+
+	public static CommandStat getCount(Command command)
+	{
+		if (command == null) {
+			return null;
+		}
+		CommandStat ret = commandAndCount.get(command);
+		if (ret != null)
+		{
+			return ret;
+		}
+
+		synchronized (commandAndCount)
+		{
+			ret = commandAndCount.get(command);
+			if (ret != null)
+			{
+				return ret;
+			}
+			ret = new CommandStat();
+			commandAndCount.put(command, ret);
+		}
+		return ret;
+	}
+	
+
+	/**
+	 * 
+	 *
+	 * @author: wchao
+	 * 2016年12月6日 下午5:32:31
+	 * 
+	 */
+	public CommandStat()
+	{
+	}
+
+
+	/**
+	 * @return the receivedCount
+	 */
+	public AtomicLong getReceived()
+	{
+		return received;
+	}
+
+
+	/**
+	 * @return the handledCount
+	 */
+	public AtomicLong getHandled()
+	{
+		return handled;
+	}
+
+
+	/**
+	 * @return the sentCount
+	 */
+	public AtomicLong getSent()
+	{
+		return sent;
+	}
+
+}

+ 478 - 0
jim-common/src/main/java/org/jim/common/ImAio.java

@@ -0,0 +1,478 @@
+package org.jim.common;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jim.common.cluster.ImCluster;
+import org.jim.common.config.DefaultImConfigBuilder;
+import org.jim.common.listener.ImBindListener;
+import org.jim.common.packets.Client;
+import org.jim.common.packets.User;
+import org.jim.common.utils.ImKit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.core.Aio;
+import org.tio.core.ChannelContext;
+import org.tio.core.ChannelContextFilter;
+import org.tio.core.GroupContext;
+import org.tio.utils.lock.SetWithLock;
+
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * @author : WChao 创建时间: 2017年9月22日 上午9:07:18
+ */
+public class ImAio {
+	
+	public static ImConfig imConfig = null;
+	
+	private static Logger log = LoggerFactory.getLogger(ImAio.class);
+	/**
+	 * 功能描述:[根据用户ID获取当前用户]
+	 * @author:WChao 创建时间: 2017年9月18日 下午4:34:39
+	 * @param userId
+	 * @return
+	 */
+	public static User getUser(String userId){
+		SetWithLock<ChannelContext> userChannelContexts = ImAio.getChannelContextsByUserId(userId);
+		if(Objects.isNull(userChannelContexts)) {
+			return null;
+		}
+		ReadLock readLock = userChannelContexts.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> userChannels = userChannelContexts.getObj();
+			if(CollectionUtils.isEmpty(userChannels)) {
+				return null;
+			}
+			User user = null;
+			for(ChannelContext channelContext : userChannels){
+				ImSessionContext imSessionContext = (ImSessionContext)channelContext.getAttribute();
+				Client client = imSessionContext.getClient();
+				user = client.getUser();
+				if(user != null){
+					return user;
+				}
+			}
+			return user;
+		}finally {
+			readLock.unlock();
+		}
+	}
+	/**
+	 * 功能描述:[根据用户ID获取当前用户所在通道集合]
+	 * @author:WChao 创建时间: 2017年9月18日 下午4:34:39
+	 * @param userId
+	 * @return
+	 */
+	public static SetWithLock<ChannelContext> getChannelContextsByUserId(String userId){
+		SetWithLock<ChannelContext> channelContexts = Aio.getChannelContextsByUserid(imConfig.getGroupContext(), userId);
+		return channelContexts;
+	}
+	/**
+	 * 功能描述:[获取所有用户(在线+离线)]
+	 * @author:WChao 创建时间: 2017年9月18日 下午4:31:54
+	 * @return
+	 */
+	public static List<User> getAllUser(){
+		List<User> users = new ArrayList<User>();
+		SetWithLock<ChannelContext> allChannels = Aio.getAllChannelContexts(imConfig.getGroupContext());
+		if(allChannels == null) {
+			return users;
+		}
+		ReadLock readLock = allChannels.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> userChannels = allChannels.getObj();
+			if(CollectionUtils.isEmpty(userChannels)) {
+				return users;
+			}
+			for(ChannelContext channelContext : userChannels){
+				ImSessionContext imSessionContext = (ImSessionContext)channelContext.getAttribute();
+				Client client = imSessionContext.getClient();
+				if(client != null && client.getUser() != null){
+					User user = ImKit.copyUserWithoutUsers(client.getUser());
+					users.add(user);
+				}
+			}
+		}finally {
+			readLock.unlock();
+		}
+		return users;
+	}
+	/**
+	 * 功能描述:[获取所有在线用户]
+	 * @author:WChao 创建时间: 2017年9月18日 下午4:31:42
+	 * @return
+	 */
+	public static List<User> getAllOnlineUser(){
+		List<User> users = new ArrayList<User>();
+		SetWithLock<ChannelContext> onlineChannels = Aio.getAllConnectedsChannelContexts(imConfig.getGroupContext());
+		if(onlineChannels == null) {
+			return users;
+		}
+		ReadLock readLock = onlineChannels.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> userChannels = onlineChannels.getObj();
+			for(ChannelContext channelContext : userChannels){
+				ImSessionContext imSessionContext = (ImSessionContext)channelContext.getAttribute();
+				if(imSessionContext != null){
+					Client client = imSessionContext.getClient();
+					if(client != null && client.getUser() != null){
+						User user = ImKit.copyUserWithoutUsers(client.getUser());
+						users.add(user);
+					}
+				}
+			}
+		}finally {
+			readLock.unlock();
+		}
+		return users;
+	}
+	/**
+	 * 根据群组获取所有用户;
+	 * @param group
+	 * @return
+	 */
+	public static List<User> getAllUserByGroup(String group){
+		SetWithLock<ChannelContext> withLockChannels = Aio.getChannelContextsByGroup(imConfig.getGroupContext(), group);
+		if(withLockChannels == null){
+			return null;
+		}
+		ReadLock readLock = withLockChannels.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> channels = withLockChannels.getObj();
+			if(CollectionUtils.isEmpty(channels)){
+				return null;
+			}
+			List<User> users = new ArrayList<User>();
+			Map<String,User> userMaps = new HashMap<String,User>();
+			for(ChannelContext channelContext : channels){
+				String userId = channelContext.getUserid();
+				User user = getUser(userId);
+				if(user != null && userMaps.get(userId) == null){
+					userMaps.put(userId, user);
+					users.add(user);
+				}
+			}
+			userMaps = null;
+			return users;
+		}finally{
+			readLock.unlock();
+		}
+	}
+	/**
+	 * 功能描述:[发送到群组(所有不同协议端)]
+	 * @author:WChao 创建时间: 2017年9月21日 下午3:26:57
+	 * @param group
+	 * @param packet
+	 */
+	public static void sendToGroup(String group, ImPacket packet){
+		if(packet.getBody() == null) {
+			return;
+		}
+		SetWithLock<ChannelContext> withLockChannels = Aio.getChannelContextsByGroup(imConfig.getGroupContext(), group);
+		if(withLockChannels == null){
+			ImCluster cluster = imConfig.getCluster();
+			if (cluster != null && !packet.isFromCluster()) {
+				cluster.clusterToGroup(imConfig.getGroupContext(), group, packet);
+			}
+			return;
+		}
+		ReadLock readLock = withLockChannels.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> channels = withLockChannels.getObj();
+			if(CollectionUtils.isNotEmpty(channels)){
+				for(ChannelContext channelContext : channels){
+					send(channelContext,packet);
+				}
+			}
+		}finally{
+			readLock.unlock();
+			ImCluster cluster = imConfig.getCluster();
+			if (cluster != null && !packet.isFromCluster()) {
+				cluster.clusterToGroup(imConfig.getGroupContext(), group, packet);
+			}
+		}
+	}
+	/**
+	 * 发送到指定通道;
+	 * @param channelContext
+	 * @param packet
+	 */
+	public static boolean send(ChannelContext channelContext,ImPacket packet){
+		ImPacket respPacket = initAndSetConvertPacket(channelContext , packet);
+		if(respPacket == null){
+			return false;
+		}
+		return Aio.send(channelContext,respPacket);
+	}
+
+	/**
+	 * 阻塞发送(确认把packet发送到对端后再返回)
+	 * @param channelContext
+	 * @param packet
+	 * @return
+	 */
+	public static boolean bSend(ChannelContext channelContext , ImPacket packet){
+		ImPacket respPacket = initAndSetConvertPacket(channelContext , packet);
+		if(respPacket == null){
+			return false;
+		}
+		return Aio.bSend(channelContext,respPacket);
+	}
+	/**
+	 * 发送到指定用户;
+	 * @param userId
+	 * @param packet
+	 */
+	public static void sendToUser(String userId,ImPacket packet){
+		if(StringUtils.isEmpty(userId)) {
+			return;
+		}
+		SetWithLock<ChannelContext> toChannelContexts = getChannelContextsByUserId(userId);
+		if(toChannelContexts == null || toChannelContexts.size() < 1){
+			ImCluster cluster = imConfig.getCluster();
+			if (cluster != null && !packet.isFromCluster()) {
+				cluster.clusterToUser(imConfig.getGroupContext(), userId, packet);
+			}
+			return;
+		}
+		ReadLock readLock = toChannelContexts.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> channels = toChannelContexts.getObj();
+			for(ChannelContext channelContext : channels){
+				send(channelContext, packet);
+			}
+		}finally{
+			readLock.unlock();
+			ImCluster cluster = imConfig.getCluster();
+			if (cluster != null && !packet.isFromCluster()) {
+				cluster.clusterToUser(imConfig.getGroupContext(), userId, packet);
+			}
+		}
+	}
+	/**
+	 * 发送到指定ip对应的集合
+	 * @param groupContext
+	 * @param ip
+	 * @param packet
+	 */
+	public static void sendToIp(GroupContext groupContext, String ip, ImPacket packet) {
+		 sendToIp(groupContext, ip, packet, null);
+	}
+
+	public static void sendToIp(GroupContext groupContext, String ip, ImPacket packet, ChannelContextFilter channelContextFilter) {
+		 sendToIp(groupContext, ip, packet, channelContextFilter, false);
+	}
+
+	private static Boolean sendToIp(GroupContext groupContext, String ip, ImPacket packet, ChannelContextFilter channelContextFilter, boolean isBlock) {
+		try{
+			SetWithLock<ChannelContext> setWithLock = groupContext.ips.clients(groupContext, ip);
+			if (setWithLock == null) {
+				log.info("{}, 没有ip为[{}]的对端", groupContext.getName(), ip);
+				return false;
+			} else {
+				Boolean ret = sendToSet(groupContext, setWithLock, packet, channelContextFilter, isBlock);
+				return ret;
+			}
+		}finally{
+			ImCluster cluster = imConfig.getCluster();
+			if (cluster != null && !packet.isFromCluster()) {
+				cluster.clusterToIp(groupContext, ip, packet);
+			}
+		}
+	}
+
+	public static Boolean sendToSet(GroupContext groupContext, SetWithLock<ChannelContext> setWithLock, ImPacket packet, ChannelContextFilter channelContextFilter, boolean isBlock){
+		Lock lock = setWithLock.getLock().readLock();
+		lock.lock();
+		try {
+			Set<ChannelContext> sets = (Set<ChannelContext>) setWithLock.getObj();
+			for (ChannelContext channelContext : sets) {
+				SetWithLock<ChannelContext> convertSet = new SetWithLock<ChannelContext>(new HashSet<ChannelContext>());
+				convertSet.add(channelContext);
+				ImPacket resPacket = ImKit.ConvertRespPacket(packet, packet.getCommand(), channelContext);
+				Aio.sendToSet(groupContext, convertSet, resPacket, channelContextFilter);
+			}
+		}finally {
+			lock.unlock();
+		}
+		return true;
+	}
+	/**
+	 * 转换协议包同时设置Packet包信息;
+	 * @param channelContext
+	 * @param packet
+	 * @return
+	 */
+	private static ImPacket initAndSetConvertPacket(ChannelContext channelContext , ImPacket packet){
+		if(channelContext == null) {
+			return null;
+		}
+		ImPacket respPacket = ImKit.ConvertRespPacket(packet,packet.getCommand(),channelContext);
+		if(respPacket == null){
+			log.error("转换协议包为空,请检查协议!");
+			return null;
+		}
+		respPacket.setSynSeq(packet.getSynSeq());
+		if(imConfig == null){
+			imConfig = new DefaultImConfigBuilder().setGroupContext(channelContext.getGroupContext()).build();
+		}
+		return respPacket;
+	}
+	/**
+	 * 绑定用户;
+	 * @param channelContext
+	 * @param userId
+	 */
+	public static void bindUser(ChannelContext channelContext,String userId){
+		bindUser(channelContext, userId,null);
+	}
+	/**
+	 * 绑定用户,同时可传递监听器执行回调函数
+	 * @param channelContext
+	 * @param userId
+	 * @param bindListener(绑定监听器回调)
+	 */
+	public static void bindUser(ChannelContext channelContext,String userId,ImBindListener bindListener){
+		Aio.bindUser(channelContext, userId);
+		if(bindListener != null){
+			try {
+				bindListener.onAfterUserBind(channelContext, userId);
+			} catch (Exception e) {
+				log.error(e.toString(),e);
+			}
+		}
+	}
+	/**
+	 * 解绑用户
+	 * @param userId
+	 */
+	public static void unbindUser(String userId){
+		unbindUser(userId, null);
+	}
+	/**
+	 * 解除绑定用户,同时可传递监听器执行回调函数
+	 * @param userId
+	 * @param bindListener(解绑定监听器回调)
+	 */
+	public static void unbindUser(String userId,ImBindListener bindListener){
+		Aio.unbindUser(imConfig.getGroupContext(), userId);
+		if(bindListener != null){
+			try {
+				SetWithLock<ChannelContext> userChannelContexts = ImAio.getChannelContextsByUserId(userId);
+				if(userChannelContexts == null || userChannelContexts.size() == 0) {
+					return;
+				}
+				ReadLock readLock = userChannelContexts.getLock().readLock();
+				readLock.lock();
+				try{
+					Set<ChannelContext> channels = userChannelContexts.getObj();
+					for(ChannelContext channelContext : channels){
+						bindListener.onAfterUserBind(channelContext, userId);
+					}
+				}finally{
+					readLock.unlock();
+				}
+			} catch (Exception e) {
+				log.error(e.toString(),e);
+			}
+		}
+	}
+	/**
+	 * 绑定群组;
+	 * @param channelContext
+	 * @param group
+	 */
+	public static void bindGroup(ChannelContext channelContext,String group){
+		bindGroup(channelContext, group,null);
+	}
+	/**
+	 * 绑定群组,同时可传递监听器执行回调函数
+	 * @param channelContext
+	 * @param group
+	 * @param bindListener(绑定监听器回调)
+	 */
+	public static void bindGroup(ChannelContext channelContext,String group,ImBindListener bindListener){
+		Aio.bindGroup(channelContext, group);
+		if(bindListener != null){
+			try {
+				bindListener.onAfterGroupBind(channelContext, group);
+			} catch (Exception e) {
+				log.error(e.toString(),e);
+			}
+		}
+	}
+	/**
+	 * 与指定群组解除绑定
+	 * @param userId
+	 * @param group
+	 */
+	public static void unbindGroup(String userId,String group){
+		unbindGroup(userId, group, null);
+	}
+	/**
+	 * 与指定群组解除绑定,同时可传递监听器执行回调函数
+	 * @param userId
+	 * @param group
+	 * @param bindListener(解绑定监听器回调)
+	 */
+	public static void unbindGroup(String userId,String group,ImBindListener bindListener){
+		SetWithLock<ChannelContext> userChannelContexts = ImAio.getChannelContextsByUserId(userId);
+		if(userChannelContexts == null || userChannelContexts.size() == 0) {
+			return;
+		}
+		ReadLock readLock = userChannelContexts.getLock().readLock();
+		readLock.lock();
+		try{
+			Set<ChannelContext> channels = userChannelContexts.getObj();
+			for(ChannelContext channelContext : channels){
+				Aio.unbindGroup(group, channelContext);
+				if(bindListener != null){
+					try {
+						bindListener.onAfterGroupUnbind(channelContext, group);
+					} catch (Exception e) {
+						log.error(e.toString(),e);
+					}
+				}
+			}
+		}finally{
+			readLock.unlock();
+		}
+	}
+	/**
+	 * 移除用户, 和close方法一样,只不过不再进行重连等维护性的操作
+	 * @param userId
+	 * @param remark
+	 */
+	public static void remove(String userId,String remark){
+		SetWithLock<ChannelContext> userChannelContexts = getChannelContextsByUserId(userId);
+		if(userChannelContexts != null && userChannelContexts.size() > 0){
+			ReadLock readLock = userChannelContexts.getLock().readLock();
+			readLock.lock();
+			try{
+				Set<ChannelContext> channels = userChannelContexts.getObj();
+				for(ChannelContext channelContext : channels){
+					remove(channelContext, remark);
+				}
+			}finally{
+				readLock.unlock();
+			}
+		}
+	}
+	/**
+	 * 移除指定channel, 和close方法一样,只不过不再进行重连等维护性的操作
+	 * @param channelContext
+	 * @param remark
+	 */
+	public static void remove(ChannelContext channelContext,String remark){
+		Aio.remove(channelContext, remark);
+	}
+}

+ 32 - 0
jim-common/src/main/java/org/jim/common/ImClientGroupContext.java

@@ -0,0 +1,32 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.tio.client.ClientGroupContext;
+import org.tio.client.ReconnConf;
+import org.tio.client.intf.ClientAioHandler;
+import org.tio.client.intf.ClientAioListener;
+import org.tio.utils.thread.pool.SynThreadPoolExecutor;
+
+/**
+ * @author WChao
+ *
+ */
+public class ImClientGroupContext extends ClientGroupContext{
+
+	public ImClientGroupContext(ClientAioHandler aioHandler,ClientAioListener aioListener) {
+		super(aioHandler, aioListener);
+	}
+
+	public ImClientGroupContext(ClientAioHandler aioHandler,ClientAioListener aioListener, ReconnConf reconnConf,SynThreadPoolExecutor tioExecutor, ThreadPoolExecutor groupExecutor) {
+		super(aioHandler, aioListener, reconnConf, tioExecutor, groupExecutor);
+	}
+
+	public ImClientGroupContext(ClientAioHandler aioHandler,ClientAioListener aioListener, ReconnConf reconnConf) {
+		super(aioHandler, aioListener, reconnConf);
+	}
+
+}

+ 52 - 0
jim-common/src/main/java/org/jim/common/ImConfig.java

@@ -0,0 +1,52 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+import org.jim.common.config.Config;
+import org.jim.common.http.HttpConfig;
+import org.jim.common.ws.WsServerConfig;
+/**
+ * @author WChao
+ *
+ */
+public class ImConfig extends Config{
+	
+	/**
+	 * http相关配置;
+	 */
+	private HttpConfig httpConfig;
+	/**
+	 * WebSocket相关配置;
+	 */
+	private WsServerConfig wsServerConfig;
+	
+	public ImConfig() {
+		this.httpConfig = new HttpConfig();
+		this.wsServerConfig = new WsServerConfig();
+	}
+	
+	public ImConfig(String bindIp,Integer bindPort){
+		this.bindIp = bindIp;
+		this.bindPort = bindPort;
+		this.httpConfig = new HttpConfig();
+		this.wsServerConfig = new WsServerConfig();
+	}
+
+	public HttpConfig getHttpConfig() {
+		return httpConfig;
+	}
+
+	public void setHttpConfig(HttpConfig httpConfig) {
+		this.httpConfig = httpConfig;
+	}
+
+	public WsServerConfig getWsServerConfig() {
+		return wsServerConfig;
+	}
+
+	public void setWsServerConfig(WsServerConfig wsServerConfig) {
+		this.wsServerConfig = wsServerConfig;
+	}
+	
+}

+ 64 - 0
jim-common/src/main/java/org/jim/common/ImConst.java

@@ -0,0 +1,64 @@
+package org.jim.common;
+
+/**
+ * 
+ * @author wchao 
+ *
+ */
+public interface ImConst
+{
+	public static final String AUTH_KEY = "authKey";
+	
+	public static final int SERVER_PORT = 8888;
+	
+	public static final String CHARSET = "utf-8";
+	
+	public static final String TO = "to";
+	
+	public static final String CHANNEL = "channel";
+	
+	public static final String PACKET = "packet";
+	
+	public static final String STATUS = "status";
+	
+	public static final String HTTP_REQUEST = "httpRequest";
+
+	public static final String CHAT_QUEUE = "chat_queue";
+	
+	public static final String STORE = "store";
+	
+	public static final String PUSH = "push";
+	
+	public static final String CHAT = "chat";
+	
+	public static final String GROUP = "group";
+	
+	public static final String USER = "user";
+	
+	public static final String TERMINAL = "terminal";
+	
+	public static final String INFO = "info";
+	
+	public static final String FRIENDS = "friends";
+	
+	public static final String ONLINE = "online";
+	
+	public static final String OFFLINE = "offline";
+	
+	public static final String ON = "on";
+	
+	public static final String OFF = "off";
+	
+	public static final String JIM = "JIM";
+	
+	public static final String CONVERTER = "converter";
+
+	public static final String BASE_ASYNC_CHAT_MESSAGE_PROCESSOR = "base_async_chat_message_processor";
+
+	public static final String IM_CHAT_LOGIN_SERVICE_PROCESSOR = "im_chat_login_service_processor";
+
+	public static final String SEPARATOR = ":";
+
+	public static final String ADMIN = "ADMIN";
+	
+}

+ 20 - 0
jim-common/src/main/java/org/jim/common/ImDecoder.java

@@ -0,0 +1,20 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+import java.nio.ByteBuffer;
+
+import org.tio.core.ChannelContext;
+import org.tio.core.exception.AioDecodeException;
+
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年7月27日 下午5:25:13
+ */
+public interface ImDecoder {
+	
+	public ImPacket decode(ByteBuffer buffer, ChannelContext channelContext) throws AioDecodeException;
+}

+ 168 - 0
jim-common/src/main/java/org/jim/common/ImPacket.java

@@ -0,0 +1,168 @@
+package org.jim.common;
+
+import org.tio.core.intf.Packet;
+import org.jim.common.packets.Command;
+
+/**
+ * 
+ * @author WChao 
+ *
+ */
+public class ImPacket extends Packet
+{
+	private static final long serialVersionUID = 2000118564569232098L;
+	/**
+	 * 包状态码;
+	 */
+	protected Status status;
+	/**
+	 * 消息体;
+	 */
+	protected byte[] body;
+	/**
+	 * 消息命令;
+	 */
+	private Command command;
+	
+	public ImPacket(){}
+	
+	public ImPacket(byte[] body){
+		this.body = body;
+	}
+	
+	public ImPacket(Command command, byte[] body)
+	{
+		this(body);
+		this.setCommand(command);
+	}
+	
+	public ImPacket(Command command)
+	{
+		this(command,null);
+	}
+
+	public static byte encodeEncrypt(byte bs,boolean isEncrypt){
+		if(isEncrypt){
+			return (byte) (bs | Protocol.FIRST_BYTE_MASK_ENCRYPT);
+		}else{
+			return (byte)(Protocol.FIRST_BYTE_MASK_ENCRYPT & 0b01111111);
+		}
+	}
+	
+	public static boolean decodeCompress(byte version)
+	{
+		return (Protocol.FIRST_BYTE_MASK_COMPRESS & version) != 0;
+	}
+
+	public static byte encodeCompress(byte bs, boolean isCompress)
+	{
+		if (isCompress)
+		{
+			return (byte) (bs | Protocol.FIRST_BYTE_MASK_COMPRESS);
+		} else
+		{
+			return (byte) (bs & (Protocol.FIRST_BYTE_MASK_COMPRESS ^ 0b01111111));
+		}
+	}
+
+	public static boolean decodeHasSynSeq(byte maskByte)
+	{
+		return (Protocol.FIRST_BYTE_MASK_HAS_SYNSEQ & maskByte) != 0;
+	}
+
+	public static byte encodeHasSynSeq(byte bs, boolean hasSynSeq)
+	{
+		if (hasSynSeq)
+		{
+			return (byte) (bs | Protocol.FIRST_BYTE_MASK_HAS_SYNSEQ);
+		} else
+		{
+			return (byte) (bs & (Protocol.FIRST_BYTE_MASK_HAS_SYNSEQ ^ 0b01111111));
+		}
+	}
+
+	public static boolean decode4ByteLength(byte version)
+	{
+		return (Protocol.FIRST_BYTE_MASK_4_BYTE_LENGTH & version) != 0;
+	}
+
+	public static byte encode4ByteLength(byte bs, boolean is4ByteLength)
+	{
+		if (is4ByteLength)
+		{
+			return (byte) (bs | Protocol.FIRST_BYTE_MASK_4_BYTE_LENGTH);
+		} else
+		{
+			return (byte) (bs & (Protocol.FIRST_BYTE_MASK_4_BYTE_LENGTH ^ 0b01111111));
+		}
+	}
+
+	public static byte decodeVersion(byte version)
+	{
+		return (byte) (Protocol.FIRST_BYTE_MASK_VERSION & version);
+	}
+
+	/**
+	 * 计算消息头占用了多少字节数
+	 * @return
+	 * 2017年1月31日 下午5:32:26
+	 */
+	public int calcHeaderLength(boolean is4byteLength)
+	{
+		int ret = Protocol.LEAST_HEADER_LENGHT;
+		if (is4byteLength)
+		{
+			ret += 2;
+		}
+		if (this.getSynSeq() > 0)
+		{
+			ret += 4;
+		}
+		return ret;
+	}
+	public Command getCommand()
+	{
+		return command;
+	}
+
+	public void setCommand(Command type)
+	{
+		this.command = type;
+	}
+
+	/**
+	 * @return the body
+	 */
+	public byte[] getBody()
+	{
+		return body;
+	}
+
+	/**
+	 * @param body the body to set
+	 */
+	public void setBody(byte[] body)
+	{
+		this.body = body;
+	}
+
+	/** 
+	 * @see org.tio.core.intf.Packet#logstr()
+	 * @return
+	 * 2017年2月22日 下午3:15:18
+	 */
+	@Override
+	public String logstr()
+	{
+		return this.command == null ? Command.COMMAND_UNKNOW.name() : this.command.name();
+	}
+
+	public Status getStatus() {
+		return status;
+	}
+
+	public void setStatus(Status status) {
+		this.status = status;
+	}
+
+}

+ 92 - 0
jim-common/src/main/java/org/jim/common/ImSessionContext.java

@@ -0,0 +1,92 @@
+package org.jim.common;
+
+import org.jim.common.packets.Client;
+import org.tio.monitor.RateLimiterWrap;
+import org.tio.server.intf.ServerAioHandler;
+
+/**
+ * 
+ * @author wchao 
+ *
+ */
+public class ImSessionContext extends SessionContext
+{
+	/**
+	 * 消息请求频率控制器
+	 */
+	protected RateLimiterWrap requestRateLimiter = null;
+	
+	protected Client client = null;
+	
+	protected String token = null;
+	/**
+	 * 通道所属协议处理器;
+	 */
+	private ServerAioHandler protocolHandler;
+	
+	/**
+	 * 
+	 *
+	 * @author: wchao
+	 * 2017年2月21日 上午10:27:54
+	 * 
+	 */
+	public ImSessionContext()
+	{
+		
+	}
+	/**
+	 * @return the client
+	 */
+	public Client getClient()
+	{
+		return client;
+	}
+
+	/**
+	 * @param client the client to set
+	 */
+	public void setClient(Client client)
+	{
+		this.client = client;
+	}
+
+	/**
+	 * @return the token
+	 */
+	public String getToken()
+	{
+		return token;
+	}
+
+	/**
+	 * @param token the token to set
+	 */
+	public void setToken(String token)
+	{
+		this.token = token;
+	}
+
+	/**
+	 * @return the requestRateLimiter
+	 */
+	public RateLimiterWrap getRequestRateLimiter() {
+		return requestRateLimiter;
+	}
+
+	/**
+	 * @param requestRateLimiter the requestRateLimiter to set
+	 */
+	public void setRequestRateLimiter(RateLimiterWrap requestRateLimiter) {
+		this.requestRateLimiter = requestRateLimiter;
+	}
+
+	public ServerAioHandler getProtocolHandler() {
+		return protocolHandler;
+	}
+
+	public ImSessionContext setProtocolHandler(ServerAioHandler protocolHandler) {
+		this.protocolHandler = protocolHandler;
+		return this;
+	}
+}

+ 70 - 0
jim-common/src/main/java/org/jim/common/ImStatus.java

@@ -0,0 +1,70 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年7月27日 上午10:33:14
+ */
+public enum ImStatus implements Status{
+	
+	C10000(10000,"ok","发送成功"),
+	C10001(10001,"offline","用户不在线"),
+	C10019(10019,"online","用户在线"),
+	C10002(10002,"send failed","消息发送失败,数据格式不正确,请参考:{'from':来源ID,'to':目标ID,'cmd':'消息命令码','createTime':消息创建时间(Long型),'msgType':Integer消息类型,'content':内容}"),
+	C10003(10003,"ok","获取用户信息成功!"),
+	C10004(10004,"get user failed !","获取用户信息失败!"),
+	C10005(10005,"ok","获取所有在线用户信息成功!"),
+	C10006(10006,"ok","获取所有离线用户信息成功!"),
+	C10007(10007,"ok","登录成功!"),
+	C10008(10008,"login failed !","登录失败!"),
+	C10009(10009,"ok","鉴权成功!"),
+	C10010(10010,"auth failed!","鉴权失败!"),
+	C10011(10011,"join group ok!","加入群组成功!"),
+	C10012(10012,"join group failed!","加入群组失败!"),
+	C10013(10013,"Protocol version number does not match","协议版本号不匹配!"),
+	C10014(10014,"unsupported cmd command","不支持的cmd命令!"),
+	C10015(10015,"get user message failed!","获取用户消息失败!"),
+	C10016(10016,"get user message ok!","获取离线消息成功!"),
+	C10017(10017,"cmd failed!","未知的cmd命令!"),
+	C10018(10018,"get user message ok!","获取历史消息成功!"),
+	C10020(10020,"Invalid verification!","不合法校验"),
+	C10021(10021,"close ok!","关闭成功"),
+	C10022(10022,"online service account is zero!","当前平台部门下没有在线客服,请稍后!");
+
+	private int status;
+	
+	private String description;
+	
+	private String text;
+
+	private ImStatus(int status, String description, String text) {
+		this.status = status;
+		this.description = description;
+		this.text = text;
+	}
+	
+	public int getStatus() {
+		return status;
+	}
+	
+	public String getDescription() {
+		return description;
+	}
+	
+	public String getText() {
+		return text;
+	}
+	
+	@Override
+	public int getCode() {
+		return this.status;
+	}
+
+	@Override
+	public String getMsg() {
+		return this.getDescription()+" "+this.getText();
+	}
+}

+ 60 - 0
jim-common/src/main/java/org/jim/common/Protocol.java

@@ -0,0 +1,60 @@
+package org.jim.common;
+
+public interface Protocol {
+	/**
+	 * 心跳字节
+	 */
+	public static final byte HEARTBEAT_BYTE = -128;
+	
+	/**
+	 * 握手字节
+	 */
+	public static final byte HANDSHAKE_BYTE = -127;
+
+	/**
+	 * 协议版本号
+	 */
+	public final static byte VERSION = 0x01;
+	
+	public final static String WEBSOCKET = "ws";
+	
+	public final static String HTTP = "http";
+	
+	public final static String TCP = "tcp";
+	
+	public static final String COOKIE_NAME_FOR_SESSION = "jim-s";
+	/**
+	 * 消息体最多为多少
+	 */
+	public static final int MAX_LENGTH_OF_BODY = (int) (1024 * 1024 * 2.1); //只支持多少M数据
+
+	/**
+	 * 消息头最少为多少个字节
+	 */
+	public static final int LEAST_HEADER_LENGHT = 4;//1+1+2 + (2+4)
+	
+	/**
+	 * 加密标识位mask,1为加密,否则不加密
+	 */
+	public static final byte FIRST_BYTE_MASK_ENCRYPT = -128;
+
+	/**
+	 * 压缩标识位mask,1为压缩,否则不压缩
+	 */
+	public static final byte FIRST_BYTE_MASK_COMPRESS = 0B01000000;
+
+	/**
+	 * 是否有同步序列号标识位mask,如果有同步序列号,则消息头会带有同步序列号,否则不带
+	 */
+	public static final byte FIRST_BYTE_MASK_HAS_SYNSEQ = 0B00100000;
+
+	/**
+	 * 是否是用4字节来表示消息体的长度
+	 */
+	public static final byte FIRST_BYTE_MASK_4_BYTE_LENGTH = 0B00010000;
+
+	/**
+	 * 版本号mask
+	 */
+	public static final byte FIRST_BYTE_MASK_VERSION = 0B00001111;
+}

+ 21 - 0
jim-common/src/main/java/org/jim/common/SessionContext.java

@@ -0,0 +1,21 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年8月3日 上午9:15:37
+ */
+public class SessionContext {
+	private String id;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+}

+ 16 - 0
jim-common/src/main/java/org/jim/common/Status.java

@@ -0,0 +1,16 @@
+/**
+ * 
+ */
+package org.jim.common;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年7月26日 下午4:09:41
+ */
+public interface Status {
+	
+	public int getCode();
+	
+	public String getMsg();
+}

+ 48 - 0
jim-common/src/main/java/org/jim/common/cache/CacheChangeType.java

@@ -0,0 +1,48 @@
+package org.jim.common.cache;
+
+import java.util.Objects;
+
+/**
+ *
+ * @author WChao
+ * 2017年8月12日 下午9:33:02
+ */
+public enum CacheChangeType {
+	/**
+	 * key级别清空本地缓存
+	 */
+	REMOVE(1),
+	/**
+	 * key级别清空本地缓存
+	 */
+	UPDATE(2),
+	/**
+	 * key级别清空本地缓存
+	 */
+	PUT(3),
+	/**
+	 * cacheName级别清空本地缓存
+	 */
+	CLEAR(4);
+
+	public static CacheChangeType from(Integer method) {
+		CacheChangeType[] values = CacheChangeType.values();
+		for (CacheChangeType v : values) {
+			if (Objects.equals(v.value, method)) {
+				return v;
+			}
+		}
+		return null;
+	}
+
+    Integer value;
+
+	private CacheChangeType(Integer value) {
+		this.value = value;
+	}
+
+	public Integer getValue() {
+		return value;
+	}
+	
+}

+ 127 - 0
jim-common/src/main/java/org/jim/common/cache/CacheChangedVo.java

@@ -0,0 +1,127 @@
+package org.jim.common.cache;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * @author WChao
+ * 2017年8月12日 下午9:30:31
+ */
+public class CacheChangedVo implements Serializable {
+
+	private static final long serialVersionUID = 1546804469064012259L;
+
+	public static final String CLIENTID = UUID.randomUUID().toString();
+
+	/**
+	 * @param args
+	 * @author WChao
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	private String cacheName;
+
+	private String key;
+
+	private String clientId = CLIENTID;
+
+	private CacheChangeType type;
+
+	/**
+	 *
+	 * @author WChao
+	 */
+	public CacheChangedVo() {
+		super();
+	}
+
+	//	private
+
+	/**
+	 * @param cacheName
+	 * @param type
+	 * @author WChao
+	 */
+	public CacheChangedVo(String cacheName, CacheChangeType type) {
+		this();
+		this.cacheName = cacheName;
+		this.type = type;
+	}
+
+	/**
+	 * @param cacheName
+	 * @param key
+	 * @param type
+	 * @author WChao
+	 */
+	public CacheChangedVo(String cacheName, String key, CacheChangeType type) {
+		this();
+		this.cacheName = cacheName;
+		this.key = key;
+		this.type = type;
+	}
+
+	/**
+	 * @return the cacheName
+	 */
+	public String getCacheName() {
+		return cacheName;
+	}
+
+	/**
+	 * @return the clientId
+	 */
+	public String getClientId() {
+		return clientId;
+	}
+
+	/**
+	 * @return the key
+	 */
+	public String getKey() {
+		return key;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public CacheChangeType getType() {
+		return type;
+	}
+
+	/**
+	 * @param cacheName the cacheName to set
+	 */
+	public void setCacheName(String cacheName) {
+		this.cacheName = cacheName;
+	}
+
+	/**
+	 * @param clientId the clientId to set
+	 */
+	public void setClientId(String clientId) {
+		this.clientId = clientId;
+	}
+
+	/**
+	 * @param key the key to set
+	 */
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	/**
+	 * @param type the type to set
+	 */
+	public void setType(CacheChangeType type) {
+		this.type = type;
+	}
+
+	@Override
+	public String toString() {
+		return  cacheName + ":" + key + ":" + type.getValue()+":"+clientId;
+	}
+	
+}

+ 65 - 0
jim-common/src/main/java/org/jim/common/cache/ICache.java

@@ -0,0 +1,65 @@
+package org.jim.common.cache;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * @author wchao
+ * 2017年8月10日 上午11:38:26
+ */
+public interface ICache {
+
+	/**
+	 *
+	 * 清空所有缓存
+	 * @author wchao
+	 */
+	void clear();
+
+	/**
+	 * 根据key获取value
+	 * @param key
+	 * @return
+	 * @author wchao
+	 */
+	public Serializable get(String key);
+	
+	/**
+	 * 根据key获取value
+	 * @param key
+	 * @param clazz
+	 * @return
+	 * @author: wchao
+	 */
+	public <T> T get(String key, Class<T> clazz);
+
+	/**
+	 * 获取所有的key
+	 * @return
+	 * @author wchao
+	 */
+	Collection<String> keys();
+
+	/**
+	 * 将key value保存到缓存中
+	 * @param key
+	 * @param value
+	 * @author wchao
+	 */
+	public void put(String key, Serializable value);
+
+	/**
+	 * 删除一个key
+	 * @param key
+	 * @return
+	 * @author wchao
+	 */
+	public void remove(String key);
+
+	/**
+	 * 临时添加一个值,用于防止缓存穿透攻击
+	 * @param key
+	 * @param value
+	 */
+	void putTemporary(String key, Serializable value);
+}

+ 12 - 0
jim-common/src/main/java/org/jim/common/cache/IL2Cache.java

@@ -0,0 +1,12 @@
+package org.jim.common.cache;
+
+import java.io.Serializable;
+
+/**
+ * @author WChao
+ * @date 2018年3月13日 下午7:47:28
+ */
+public interface IL2Cache {
+	
+	public void putL2Async(String key, Serializable value);
+}

+ 104 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineCache.java

@@ -0,0 +1,104 @@
+/**
+ * 
+ */
+package org.jim.common.cache.caffeine;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jim.common.cache.ICache;
+
+import com.github.benmanes.caffeine.cache.LoadingCache;
+
+/**
+ * 
+ * @author WChao
+ * @date 2018年3月9日 上午12:53:53
+ */
+public class CaffeineCache  implements ICache {
+	
+	private LoadingCache<String, Serializable> loadingCache = null;
+	
+	private LoadingCache<String, Serializable> temporaryLoadingCache = null;
+
+	public CaffeineCache(LoadingCache<String, Serializable> loadingCache, LoadingCache<String, Serializable> temporaryLoadingCache) {
+		this.loadingCache = loadingCache;
+		this.temporaryLoadingCache = temporaryLoadingCache;
+	}
+
+	@Override
+	public void clear() {
+		loadingCache.invalidateAll();
+		temporaryLoadingCache.invalidateAll();
+	}
+
+	@Override
+	public Serializable get(String key) {
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		Serializable ret = loadingCache.getIfPresent(key);
+		if (ret == null) {
+			ret = temporaryLoadingCache.getIfPresent(key);
+		}
+		
+		return ret;
+	}
+
+	@Override
+	public Collection<String> keys() {
+		ConcurrentMap<String, Serializable> map = loadingCache.asMap();
+		return map.keySet();
+	}
+
+	@Override
+	public void put(String key, Serializable value) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		loadingCache.put(key, value);
+	}
+	
+	@Override
+	public void putTemporary(String key, Serializable value) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		temporaryLoadingCache.put(key, value);
+	}
+
+	@Override
+	public void remove(String key) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		loadingCache.invalidate(key);
+		temporaryLoadingCache.invalidate(key);
+	}
+
+	/**
+	 * 
+	 * @return
+	 * @author: wchao
+	 */
+	public ConcurrentMap<String, Serializable> asMap() {
+		return loadingCache.asMap();
+	}
+	
+	/**
+	 * 
+	 * @return
+	 * @author: wchao
+	 */
+	public long size() {
+		return loadingCache.estimatedSize();//.size();
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T get(String key, Class<T> clazz) {
+		return (T)get(key);
+	}
+}

+ 81 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineCacheManager.java

@@ -0,0 +1,81 @@
+/**
+ * 
+ */
+package org.jim.common.cache.caffeine;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+/**
+ * 
+ * @author WChao
+ * @date 2018年3月9日 上午12:54:07
+ */
+public class CaffeineCacheManager {
+	
+	private static Map<String, CaffeineCache> map = new HashMap<String,CaffeineCache>();
+	
+	
+	private static Logger log = LoggerFactory.getLogger(CaffeineCacheManager.class);
+	
+	private CaffeineCacheManager(){}
+	
+	static{
+		try {
+			List<CaffeineConfiguration> configurations = CaffeineConfigurationFactory.parseConfiguration();
+			for(CaffeineConfiguration configuration : configurations){
+				 register(configuration.getCacheName(), configuration.getTimeToLiveSeconds(),configuration.getTimeToIdleSeconds(),configuration.getMaximumSize(),null);
+			}
+		} catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+	}
+	
+	public static CaffeineCache getCache(String cacheName, boolean skipNull) {
+		CaffeineCache CaffeineCache = map.get(cacheName);
+		if (CaffeineCache == null && !skipNull) {
+			log.error("cacheName[{}]还没注册,请初始化时调用:{}.register(cacheName, timeToLiveSeconds, timeToIdleSeconds)", cacheName, CaffeineCache.class.getSimpleName());
+		}
+		return CaffeineCache;
+	}
+	
+	public static CaffeineCache getCache(String cacheName) {
+		return getCache(cacheName, false);
+	}
+
+	/**
+	 * timeToLiveSeconds和timeToIdleSeconds不允许同时为null
+	 * @param cacheName
+	 * @param timeToLiveSeconds
+	 * @param timeToIdleSeconds
+	 * @return
+	 * @author wchao
+	 */
+	public static CaffeineCache register(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds) {
+		CaffeineCache CaffeineCache = register(cacheName, timeToLiveSeconds, timeToIdleSeconds,5000000, null);
+		return CaffeineCache;
+	}
+
+	public static CaffeineCache register(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds, Integer maximumSize , RemovalListener<String, Serializable> removalListener) {
+		CaffeineCache caffeineCache = map.get(cacheName);
+		if (caffeineCache == null) {
+			synchronized (CaffeineCacheManager.class) {
+				caffeineCache = map.get(cacheName);
+				if (caffeineCache == null) {
+					Integer initialCapacity = 10;
+					boolean recordStats = false;
+					LoadingCache<String, Serializable> loadingCache = CaffeineUtils.createLoadingCache(cacheName, timeToLiveSeconds, timeToIdleSeconds, initialCapacity, maximumSize, recordStats, removalListener);
+					LoadingCache<String, Serializable> temporaryLoadingCache = CaffeineUtils.createLoadingCache(cacheName,null, 10, initialCapacity, maximumSize, recordStats, removalListener);
+					caffeineCache = new CaffeineCache(loadingCache, temporaryLoadingCache);
+					map.put(cacheName, caffeineCache);
+				}
+			}
+		}
+		return caffeineCache;
+	}
+}

+ 56 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfig.java

@@ -0,0 +1,56 @@
+/**
+ * 
+ */
+package org.jim.common.cache.caffeine;
+
+/**
+ * @author mobo-pc
+ *
+ */
+public class CaffeineConfig {
+	
+	private String cacheName;
+	private Long  timeToLiveSeconds = 60L;
+	private Long  timeToIdleSeconds = 60L;
+	private Integer  maximumSize = 5000000;
+	private Integer initialCapacity = 100;
+	private boolean recordStats = false;
+	
+	public String getCacheName() {
+		return cacheName;
+	}
+	public void setCacheName(String cacheName) {
+		this.cacheName = cacheName;
+	}
+	public Long getTimeToLiveSeconds() {
+		return timeToLiveSeconds;
+	}
+	public void setTimeToLiveSeconds(Long timeToLiveSeconds) {
+		this.timeToLiveSeconds = timeToLiveSeconds;
+	}
+	public Long getTimeToIdleSeconds() {
+		return timeToIdleSeconds;
+	}
+	public void setTimeToIdleSeconds(Long timeToIdleSeconds) {
+		this.timeToIdleSeconds = timeToIdleSeconds;
+	}
+	public Integer getMaximumSize() {
+		return maximumSize;
+	}
+	public void setMaximumSize(Integer maximumSize) {
+		this.maximumSize = maximumSize;
+	}
+	public Integer getInitialCapacity() {
+		return initialCapacity;
+	}
+	public void setInitialCapacity(Integer initialCapacity) {
+		this.initialCapacity = initialCapacity;
+	}
+	public boolean isRecordStats() {
+		return recordStats;
+	}
+	public void setRecordStats(boolean recordStats) {
+		this.recordStats = recordStats;
+	}
+	
+}

+ 64 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfiguration.java

@@ -0,0 +1,64 @@
+package org.jim.common.cache.caffeine;
+
+import java.util.Properties;
+/**
+ * @author WChao
+ * @date 2018年3月9日 上午1:09:03
+ */
+public class CaffeineConfiguration {
+	
+	private String cacheName;
+	private Integer  timeToLiveSeconds = 1800;
+	private Integer  timeToIdleSeconds = 1800;
+	private Integer  maximumSize = 5000000;
+	private Integer initialCapacity = 10;
+	private boolean recordStats = false;
+	
+	public CaffeineConfiguration(){}
+	
+	public CaffeineConfiguration(String cacheName,Properties prop){
+		this.cacheName = cacheName;
+		String[] values = prop.getProperty(cacheName,"5000000,1800").split(",");
+		this.maximumSize = Integer.valueOf(values[0]);
+		if(values.length>1){
+			this.timeToLiveSeconds = Integer.valueOf(values[1]);
+			this.timeToIdleSeconds = Integer.valueOf(values[1]);
+		}
+	}
+	public String getCacheName() {
+		return cacheName;
+	}
+	public void setCacheName(String cacheName) {
+		this.cacheName = cacheName;
+	}
+	public Integer getTimeToLiveSeconds() {
+		return timeToLiveSeconds;
+	}
+	public void setTimeToLiveSeconds(Integer timeToLiveSeconds) {
+		this.timeToLiveSeconds = timeToLiveSeconds;
+	}
+	public Integer getTimeToIdleSeconds() {
+		return timeToIdleSeconds;
+	}
+	public void setTimeToIdleSeconds(Integer timeToIdleSeconds) {
+		this.timeToIdleSeconds = timeToIdleSeconds;
+	}
+	public Integer getMaximumSize() {
+		return maximumSize;
+	}
+	public void setMaximumSize(Integer maximumSize) {
+		this.maximumSize = maximumSize;
+	}
+	public Integer getInitialCapacity() {
+		return initialCapacity;
+	}
+	public void setInitialCapacity(Integer initialCapacity) {
+		this.initialCapacity = initialCapacity;
+	}
+	public boolean isRecordStats() {
+		return recordStats;
+	}
+	public void setRecordStats(boolean recordStats) {
+		this.recordStats = recordStats;
+	}
+}

+ 123 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineConfigurationFactory.java

@@ -0,0 +1,123 @@
+package org.jim.common.cache.caffeine;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * @author WChao
+ * @date 2018年3月9日 上午1:06:33
+ */
+public class CaffeineConfigurationFactory {
+	
+    private static final Logger LOG = LoggerFactory.getLogger(CaffeineConfigurationFactory.class.getName());
+
+    private static final String DEFAULT_CLASSPATH_CONFIGURATION_FILE = "caffeine.properties";
+    
+    /**
+     * Constructor.
+     */
+    private CaffeineConfigurationFactory() {
+
+    }
+
+    /**
+     * Configures a bean from an property file.
+     */
+    public static List<CaffeineConfiguration> parseConfiguration(final File file) throws Exception {
+        if (file == null) {
+            throw new Exception("Attempt to configure caffeine from null file.");
+        }
+        LOG.debug("Configuring caffeine from file: {}", file);
+        List<CaffeineConfiguration> configurations  = null;
+        InputStream input = null;
+        try {
+            input = new BufferedInputStream(new FileInputStream(file));
+            configurations = parseConfiguration(input);
+        } catch (Exception e) {
+            throw new Exception("Error configuring from " + file + ". Initial cause was " + e.getMessage(), e);
+        } finally {
+            try {
+                if (input != null) {
+                    input.close();
+                }
+            } catch (IOException e) {
+                LOG.error("IOException while closing configuration input stream. Error was " + e.getMessage());
+            }
+        }
+        return configurations;
+    }
+    /**
+     * Configures a bean from an property file available as an URL.
+     */
+    public static List<CaffeineConfiguration> parseConfiguration(final URL url) throws Exception {
+        LOG.debug("Configuring caffeine from URL: {}", url);
+        List<CaffeineConfiguration> configurations;
+        InputStream input = null;
+        try {
+            input = url.openStream();
+            configurations = parseConfiguration(input);
+        } catch (Exception e) {
+            throw new Exception("Error configuring from " + url + ". Initial cause was " + e.getMessage(), e);
+        } finally {
+            try {
+                if (input != null) {
+                    input.close();
+                }
+            } catch (IOException e) {
+                LOG.error("IOException while closing configuration input stream. Error was " + e.getMessage());
+            }
+        }
+        return configurations;
+    }
+    /**
+     * Configures a bean from an property file in the classpath.
+     */
+    public static List<CaffeineConfiguration> parseConfiguration() throws Exception {
+        ClassLoader standardClassloader = Thread.currentThread().getContextClassLoader();
+        URL url = null;
+        if (standardClassloader != null) {
+            url = standardClassloader.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
+        }
+        if (url == null) {
+        	url = CaffeineConfigurationFactory.class.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
+        }
+        if (url != null) {
+            LOG.debug("Configuring caffeine from caffeine.properties found in the classpath: " + url);
+        } else {
+            LOG.warn("No configuration found. Configuring caffeine from caffeine.properties "
+                    + " found in the classpath: {}", url);
+
+        }
+        List<CaffeineConfiguration> configurations = parseConfiguration(url);
+        return configurations;
+    }
+    
+    /**
+     * Configures a bean from an property input stream.
+     */
+    public static List<CaffeineConfiguration> parseConfiguration(final InputStream inputStream) throws Exception {
+
+        LOG.debug("Configuring caffeine from InputStream");
+
+        List<CaffeineConfiguration> configurations = new ArrayList<CaffeineConfiguration>();
+        try {
+            Properties props = new Properties();
+            props.load(inputStream);
+			for(String key : props.stringPropertyNames()){
+    			configurations.add(new CaffeineConfiguration(key , props));
+    		}
+        } catch (Exception e) {
+            throw new Exception("Error configuring from input stream. Initial cause was " + e.getMessage(), e);
+        }
+        return configurations;
+    }
+}

+ 103 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/CaffeineUtils.java

@@ -0,0 +1,103 @@
+/**
+ * 
+ */
+package org.jim.common.cache.caffeine;
+
+import java.util.concurrent.TimeUnit;
+import org.tio.utils.cache.caffeine.DefaultRemovalListener;
+
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+
+/**
+ * @author wchao
+ *
+ */
+public class CaffeineUtils {
+
+	public CaffeineUtils() {
+	}
+
+
+	/**
+	 * @param cacheName
+	 * @param timeToLiveSeconds 设置写缓存后过期时间(单位:秒)
+	 * @param timeToIdleSeconds 设置读缓存后过期时间(单位:秒)
+	 * @param initialCapacity
+	 * @param maximumSize
+	 * @param recordStats
+	 * @return
+	 */
+	public static <K, V> LoadingCache<K, V> createLoadingCache(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds, Integer initialCapacity,
+			Integer maximumSize, boolean recordStats) {
+		return createLoadingCache(cacheName, timeToLiveSeconds, timeToIdleSeconds, initialCapacity, maximumSize, recordStats, null);
+	}
+
+	/**
+	 * @param cacheName
+	 * @param timeToLiveSeconds 设置写缓存后过期时间(单位:秒)
+	 * @param timeToIdleSeconds 设置读缓存后过期时间(单位:秒)
+	 * @param initialCapacity
+	 * @param maximumSize
+	 * @param recordStats
+	 * @param removalListener
+	 * @return
+	 */
+	public static <K, V> LoadingCache<K, V> createLoadingCache(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds, Integer initialCapacity,
+			Integer maximumSize, boolean recordStats, RemovalListener<K, V> removalListener) {
+
+		if (removalListener == null) {
+			removalListener = new DefaultRemovalListener<K, V>(cacheName);
+		}
+
+		Caffeine<K, V> cacheBuilder = Caffeine.newBuilder().removalListener(removalListener);
+
+		//设置并发级别为8,并发级别是指可以同时写缓存的线程数
+		//		cacheBuilder.concurrencyLevel(concurrencyLevel);
+		if (timeToLiveSeconds != null && timeToLiveSeconds > 0) {
+			//设置写缓存后8秒钟过期
+			cacheBuilder.expireAfterWrite(timeToLiveSeconds, TimeUnit.SECONDS);
+		}
+		if (timeToIdleSeconds != null && timeToIdleSeconds > 0) {
+			//设置访问缓存后8秒钟过期
+			cacheBuilder.expireAfterAccess(timeToIdleSeconds, TimeUnit.SECONDS);
+		}
+
+		//设置缓存容器的初始容量为10
+		cacheBuilder.initialCapacity(initialCapacity);
+		//设置缓存最大容量为100,超过100之后就会按照LRU最近最少使用算法来移除缓存项
+		cacheBuilder.maximumSize(maximumSize);
+
+		if (recordStats) {
+			//设置要统计缓存的命中率
+			cacheBuilder.recordStats();
+		}
+		//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
+		LoadingCache<K, V> loadingCache = cacheBuilder.build(new CacheLoader<K, V>() {
+			@Override
+			public V load(K key) throws Exception {
+				return null;
+			}
+		});
+		return loadingCache;
+	}
+
+	public static void main(String[] args) throws Exception {
+		Integer timeToLiveSeconds = 1;
+		Integer timeToIdleSeconds = null;
+		Integer initialCapacity = 10;
+		Integer maximumSize = 1000;
+		boolean recordStats = false;
+		LoadingCache<String, Object> loadingCache = CaffeineUtils.createLoadingCache("mycache", timeToLiveSeconds, timeToIdleSeconds, initialCapacity, maximumSize,recordStats);
+		loadingCache.put("1", "11111");
+		TimeUnit.SECONDS.sleep(3);
+		loadingCache.put("2", "2222");
+		Object o = loadingCache.getIfPresent("1");
+		System.out.println(o);
+		o = loadingCache.getIfPresent("2");
+		System.out.println(o);
+	}
+
+}

+ 39 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/DefaultRemovalListener.java

@@ -0,0 +1,39 @@
+/**
+ * 
+ */
+package org.jim.common.cache.caffeine;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+
+/**
+ * @author WChao
+ * @param <K>
+ * @param <V>
+ *
+ */
+public class DefaultRemovalListener<K, V> implements RemovalListener<K, V> {
+	private static Logger log = LoggerFactory.getLogger(DefaultRemovalListener.class);
+
+	private String cacheName = null;
+	/**
+	 * 
+	 */
+	public DefaultRemovalListener(String cacheName) {
+		this.cacheName = cacheName;
+	}
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {}
+
+	@Override
+	public void onRemoval(K key, V value, RemovalCause cause) {
+		log.debug("cacheName:{}, key:{}, value:{} was removed", cacheName, key, value);
+	}
+
+}

+ 6 - 0
jim-common/src/main/java/org/jim/common/cache/caffeine/caffeine.properties

@@ -0,0 +1,6 @@
+#########################################
+# Caffeine configuration
+# [name] = size, xxxx[s|m|h|d]
+#########################################
+
+default = 5000000,1800

+ 166 - 0
jim-common/src/main/java/org/jim/common/cache/caffeineredis/CaffeineRedisCache.java

@@ -0,0 +1,166 @@
+package org.jim.common.cache.caffeineredis;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.jim.common.cache.CacheChangeType;
+import org.jim.common.cache.CacheChangedVo;
+import org.jim.common.cache.ICache;
+import org.jim.common.cache.IL2Cache;
+import org.jim.common.cache.caffeine.CaffeineCache;
+import org.jim.common.cache.redis.JedisTemplate;
+import org.jim.common.cache.redis.RedisCache;
+import org.jim.common.cache.redis.RedisExpireUpdateTask;
+
+/**
+ * @author WChao
+ * 2017年8月12日 下午9:13:54
+ */
+public class CaffeineRedisCache implements ICache,IL2Cache {
+	
+	Logger log = LoggerFactory.getLogger(CaffeineRedisCache.class);
+	
+	CaffeineCache caffeineCache;
+
+	RedisCache redisCache;
+
+	String cacheName;
+
+	/**
+	 *
+	 * @author WChao
+	 */
+	public CaffeineRedisCache() {
+	}
+
+	/**
+	 * @param caffeineCache
+	 * @param redisCache
+	 * @author WChao
+	 */
+	public CaffeineRedisCache(String cacheName, CaffeineCache caffeineCache, RedisCache redisCache) {
+		super();
+		this.cacheName = cacheName;
+		this.caffeineCache = caffeineCache;
+		this.redisCache = redisCache;
+	}
+
+	/**
+	 *
+	 * @author WChao
+	 */
+	@Override
+	public void clear() {
+		try {
+			caffeineCache.clear();
+			redisCache.clear();
+			CacheChangedVo cacheChangedVo = new CacheChangedVo(cacheName, CacheChangeType.CLEAR);
+			JedisTemplate.me().publish(CaffeineRedisCacheManager.CACHE_CHANGE_TOPIC,cacheChangedVo.toString());
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+
+	/**
+	 * @param key
+	 * @return
+	 * @author WChao
+	 */
+	@Override
+	public Serializable get(String key) {
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		
+		Serializable ret = caffeineCache.get(key);
+		if (ret == null) {
+			ret = redisCache.get(key);
+			if (ret != null) {
+				log.debug("Cache L2 (redis) :{}={}",key,ret);
+				caffeineCache.put(key, ret);
+			}
+		} else {//在本地就取到数据了,那么需要在redis那定时更新一下过期时间
+			log.debug("Cache L1 (caffeine) :{}={}",key,ret);
+			Integer timeToIdleSeconds = redisCache.getTimeToIdleSeconds();
+			if (timeToIdleSeconds != null) {
+				RedisExpireUpdateTask.add(cacheName, key, ret , timeToIdleSeconds);
+			}
+		}
+		return ret;
+	}
+
+	/**
+	 * @return
+	 * @author WChao
+	 */
+	@Override
+	public Collection<String> keys() {
+		return redisCache.keys();
+	}
+
+	/**
+	 * @param key
+	 * @param value
+	 * @author WChao
+	 */
+	@Override
+	public void put(String key, Serializable value) {
+		try {
+			caffeineCache.put(key, value);
+			redisCache.put(key, value);
+			CacheChangedVo cacheChangedVo = new CacheChangedVo(cacheName, key, CacheChangeType.PUT);
+			JedisTemplate.me().publish(CaffeineRedisCacheManager.CACHE_CHANGE_TOPIC,cacheChangedVo.toString());
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	
+	@Override
+	public void putL2Async(String key, Serializable value) {
+		caffeineCache.put(key, value);
+		CaffeineRedisCacheManager.getAsyncRedisQueue().add(new RedisL2Vo(redisCache, key, value));
+	}
+	
+	@Override
+	public void putTemporary(String key, Serializable value) {
+		caffeineCache.putTemporary(key, value);
+		redisCache.putTemporary(key, value);
+	}
+
+	/**
+	 * @param key
+	 * @author WChao
+	 */
+	@Override
+	public void remove(String key) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try{
+			caffeineCache.remove(key);
+			redisCache.remove(key);
+			CacheChangedVo cacheChangedVo = new CacheChangedVo(cacheName, key, CacheChangeType.REMOVE);
+			JedisTemplate.me().publish(CaffeineRedisCacheManager.CACHE_CHANGE_TOPIC,cacheChangedVo.toString());
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T get(String key, Class<T> clazz) {
+		return (T)get(key);
+	}
+
+	public CaffeineCache getCaffeineCache() {
+		return caffeineCache;
+	}
+
+	public RedisCache getRedisCache() {
+		return redisCache;
+	}
+
+}

+ 101 - 0
jim-common/src/main/java/org/jim/common/cache/caffeineredis/CaffeineRedisCacheManager.java

@@ -0,0 +1,101 @@
+package org.jim.common.cache.caffeineredis;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.jim.common.cache.caffeine.CaffeineCache;
+import org.jim.common.cache.caffeine.CaffeineCacheManager;
+import org.jim.common.cache.caffeine.CaffeineConfiguration;
+import org.jim.common.cache.caffeine.CaffeineConfigurationFactory;
+import org.jim.common.cache.redis.RedisCache;
+import org.jim.common.cache.redis.RedisCacheManager;
+import org.jim.common.cache.redis.SubRunnable;
+
+/**
+ * @author WChao
+ * @date 2018年3月8日 下午2:28:14
+ */
+public class CaffeineRedisCacheManager {
+	
+	private static Logger log = LoggerFactory.getLogger(CaffeineRedisCacheManager.class);
+	
+	private static Map<String, CaffeineRedisCache> map = new HashMap<>();
+	
+	private static boolean inited = false;
+	
+	public static final String CACHE_CHANGE_TOPIC = "REDIS_CACHE_CHANGE_TOPIC_CAFFEINE";
+	//L2异步存储队列;
+	private static RedisAsyncRunnable asyncRedisQueue =  new RedisAsyncRunnable();
+	/**
+	 * 在本地最大的过期时间,这样可以防止内存爆掉,单位:秒
+	 */
+	public static int MAX_EXPIRE_IN_LOCAL = 1800;
+	
+	private CaffeineRedisCacheManager(){}
+
+	static{
+		try{
+			List<CaffeineConfiguration> configurations = CaffeineConfigurationFactory.parseConfiguration();
+			for(CaffeineConfiguration configuration : configurations){
+				 register(configuration.getCacheName(), configuration.getTimeToLiveSeconds(),configuration.getTimeToIdleSeconds());
+			}
+		}catch (Exception e) {
+			log.error(e.getMessage(),e);
+		}
+	}
+	
+	public static CaffeineRedisCache getCache(String cacheName) {
+		CaffeineRedisCache caffeineRedisCache = map.get(cacheName);
+		if (caffeineRedisCache == null) {
+			log.warn("cacheName[{}]还没注册,请初始化时调用:{}.register(cacheName, timeToLiveSeconds, timeToIdleSeconds)", cacheName, CaffeineRedisCache.class.getSimpleName());
+		}
+		return caffeineRedisCache;
+	}
+	
+	private static void init() {
+		if (!inited) {
+			synchronized (CaffeineRedisCacheManager.class) {
+				if (!inited) {
+					new Thread(new SubRunnable(CACHE_CHANGE_TOPIC)).start();
+					new Thread(asyncRedisQueue).start();
+					inited = true;
+				}
+			}
+		}
+	}
+	
+	public static CaffeineRedisCache register(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds) {
+		init();
+		CaffeineRedisCache caffeineRedisCache = map.get(cacheName);
+		if (caffeineRedisCache == null) {
+			synchronized (CaffeineRedisCacheManager.class) {
+				caffeineRedisCache = map.get(cacheName);
+				if (caffeineRedisCache == null) {
+					RedisCache redisCache = RedisCacheManager.register(cacheName, timeToLiveSeconds, timeToIdleSeconds);
+					
+					Integer timeToLiveSecondsForCaffeine = timeToLiveSeconds;
+					Integer timeToIdleSecondsForCaffeine = timeToIdleSeconds;
+					
+					if (timeToLiveSecondsForCaffeine != null) {
+						timeToLiveSecondsForCaffeine = Math.min(timeToLiveSecondsForCaffeine, MAX_EXPIRE_IN_LOCAL);
+					}
+					if (timeToIdleSecondsForCaffeine != null) {
+						timeToIdleSecondsForCaffeine = Math.min(timeToIdleSecondsForCaffeine, MAX_EXPIRE_IN_LOCAL);
+					}
+					CaffeineCache caffeineCache = CaffeineCacheManager.register(cacheName, timeToLiveSecondsForCaffeine, timeToIdleSecondsForCaffeine);
+
+					caffeineRedisCache = new CaffeineRedisCache(cacheName, caffeineCache, redisCache);
+					map.put(cacheName, caffeineRedisCache);
+				}
+			}
+		}
+		return caffeineRedisCache;
+	}
+
+	public static RedisAsyncRunnable getAsyncRedisQueue() {
+		return asyncRedisQueue;
+	}
+}

+ 78 - 0
jim-common/src/main/java/org/jim/common/cache/caffeineredis/RedisAsyncRunnable.java

@@ -0,0 +1,78 @@
+package org.jim.common.cache.caffeineredis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.jim.common.cache.CacheChangeType;
+import org.jim.common.cache.CacheChangedVo;
+import org.jim.common.cache.redis.JedisTemplate;
+import org.jim.common.cache.redis.JedisTemplate.Pair;
+import org.jim.common.cache.redis.RedisCacheManager;
+/**
+ * @author WChao
+ * @date 2018年3月13日 下午7:59:20
+ */
+@SuppressWarnings("static-access")
+public class RedisAsyncRunnable implements Runnable{
+	
+	private LinkedBlockingQueue<RedisL2Vo> redisL2VoQueue = new LinkedBlockingQueue<RedisL2Vo>();
+	private static boolean started = false;
+	private Logger LOG = LoggerFactory.getLogger(RedisAsyncRunnable.class);
+	
+	public void add(RedisL2Vo redisL2Vo){
+		this.redisL2VoQueue.offer(redisL2Vo);
+	}
+	@Override
+	public void run() {
+		if (started) {
+			return;
+		}
+		synchronized (RedisAsyncRunnable.class) {
+			if (started) {
+				return;
+			}
+			started = true;
+		}
+		Map<String,List<Pair<String,Serializable>>> pairMap = new HashMap<String,List<Pair<String,Serializable>>>();
+		List<String> cacheChangeVos  = new ArrayList<String>();
+		int count = 0;
+		while(true){
+			try {
+				RedisL2Vo redisL2Vo = redisL2VoQueue.poll();
+				if(redisL2Vo != null){
+					String cacheName = redisL2Vo.getRedisCache().getCacheName();
+					if(pairMap.get(cacheName) == null){
+						List<Pair<String,Serializable>> pairDatas = new ArrayList<Pair<String,Serializable>>();
+						pairMap.put(cacheName, pairDatas);
+					}
+					pairMap.get(cacheName).add(JedisTemplate.me().makePair(redisL2Vo.getKey(),redisL2Vo.getValue()));
+					cacheChangeVos.add(new CacheChangedVo(redisL2Vo.getRedisCache().getCacheName(), redisL2Vo.getKey(), CacheChangeType.PUT).toString());
+					count++;
+				}
+				if(count > 0 && redisL2Vo == null){//队列数据为空
+					for(String cacheName : pairMap.keySet()){
+						RedisCacheManager.getCache(cacheName).putAll(pairMap.get(cacheName));
+					}
+					JedisTemplate.me().publishAll(CaffeineRedisCacheManager.CACHE_CHANGE_TOPIC, cacheChangeVos);
+					pairMap.clear();
+					cacheChangeVos.clear();
+					count = 0;
+				}else if(count == 0 && redisL2Vo == null){
+					try {
+						Thread.sleep(1000L);
+					} catch (InterruptedException e) {
+						LOG.error(e.toString(),e);
+					}
+				}
+			} catch (Exception e) {
+				LOG.error(e.toString(),e);
+			}
+		}
+	}
+}

+ 47 - 0
jim-common/src/main/java/org/jim/common/cache/caffeineredis/RedisL2Vo.java

@@ -0,0 +1,47 @@
+package org.jim.common.cache.caffeineredis;
+
+import java.io.Serializable;
+
+import org.jim.common.cache.redis.RedisCache;
+
+/**
+ * @author WChao
+ * @date 2018年3月13日 下午8:05:50
+ */
+public class RedisL2Vo {
+	
+	private RedisCache redisCache;
+	private String key;
+	private Serializable value;
+	
+	public RedisL2Vo(RedisCache redisCache , String key , Serializable value){
+		this.redisCache = redisCache;
+		this.key = key;
+		this.value = value;
+	}
+
+	public RedisCache getRedisCache() {
+		return redisCache;
+	}
+
+	public void setRedisCache(RedisCache redisCache) {
+		this.redisCache = redisCache;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public Serializable getValue() {
+		return value;
+	}
+
+	public void setValue(Serializable value) {
+		this.value = value;
+	}
+	
+}

+ 14 - 0
jim-common/src/main/java/org/jim/common/cache/ehcache/EhcacheConst.java

@@ -0,0 +1,14 @@
+package org.jim.common.cache.ehcache;
+
+/**
+ * 
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年10月26日 下午6:00:03
+ */
+public interface EhcacheConst {
+	public interface CacheName {
+		String EH_API = "eh_api";
+	}
+
+}

+ 89 - 0
jim-common/src/main/java/org/jim/common/cache/j2cache/J2Cache.java

@@ -0,0 +1,89 @@
+/**
+ * 
+ */
+package org.jim.common.cache.j2cache;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import org.jim.common.cache.ICache;
+import net.oschina.j2cache.CacheChannel;
+import net.oschina.j2cache.CacheObject;
+
+/**
+ * 红薯大大的j2cache
+ * @author WChao
+ */
+public class J2Cache implements ICache {
+
+	private String cacheName = null;
+
+	/**
+	 * 
+	 */
+	public J2Cache(String cacheName) {
+		this.cacheName = cacheName;
+	}
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+	}
+
+	private static CacheChannel getChannel() {
+		CacheChannel cache = net.oschina.j2cache.J2Cache.getChannel();
+		return cache;
+	}
+
+	@Override
+	public void clear() {
+		CacheChannel cache = getChannel();
+		cache.clear(cacheName);
+	}
+
+	@Override
+	public Serializable get(String key) {
+		CacheChannel cache = getChannel();
+		CacheObject cacheObject = cache.get(cacheName, key);
+		if (cacheObject != null) {
+			return (Serializable) cacheObject.getValue();
+		}
+		return null;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T get(String key, Class<T> clazz) {
+		Serializable ret = get(key);
+		return (T) ret;
+	}
+
+	@Override
+	public Collection<String> keys() {
+		CacheChannel cache = getChannel();
+		return cache.keys(cacheName);
+	}
+
+	@Override
+	public void put(String key, Serializable value) {
+		CacheChannel cache = getChannel();
+		cache.set(cacheName, key, value);
+	}
+
+	@Override
+	public void remove(String key) {
+		CacheChannel cache = getChannel();
+		cache.evict(cacheName, key);
+	}
+
+	@Override
+	public void putTemporary(String key, Serializable value) {
+		throw new RuntimeException("不支持防缓存穿透");
+	}
+
+	public long ttl(String key) {
+		throw new RuntimeException("不支持ttl");
+	}
+
+}

+ 105 - 0
jim-common/src/main/java/org/jim/common/cache/redis/ExpireVo.java

@@ -0,0 +1,105 @@
+package org.jim.common.cache.redis;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @author WChao
+ * 2017年8月14日 下午1:40:14
+ */
+public class ExpireVo {
+
+	/**
+	 * @param args
+	 * @author WChao
+	 */
+	public static void main(String[] args) {
+
+		//		Set<ExpireVo> set = new HashSet<>();
+		//
+		//		ExpireVo expireVo1 = new ExpireVo("x", "1", 1000);
+		//		ExpireVo expireVo2 = new ExpireVo("x", "1", 1000);
+		//		ExpireVo expireVo3 = new ExpireVo("x", "2", 1000);
+		//
+		//		boolean x = set.add(expireVo1);
+		//		boolean y = set.add(expireVo2);
+		//		boolean z = set.add(expireVo3);
+		//
+		//		System.out.println(set.size());
+
+	}
+
+	private String cacheName;
+
+	private String key;
+	
+	private Serializable value ;
+
+	private long expire;
+
+	public ExpireVo(String cacheName, String key, Serializable value , long expire) {
+		super();
+		this.cacheName = cacheName;
+		this.key = key;
+		this.expire = expire;
+		this.value = value;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		ExpireVo other = (ExpireVo) obj;
+
+		return Objects.equals(cacheName, other.cacheName) && Objects.equals(key, other.key);
+	}
+
+	public String getCacheName() {
+		return cacheName;
+	}
+
+	public long getExpire() {
+		return expire;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (cacheName == null ? 0 : cacheName.hashCode());
+		result = prime * result + (key == null ? 0 : key.hashCode());
+		return result;
+	}
+
+	public void setCacheName(String cacheName) {
+		this.cacheName = cacheName;
+	}
+
+	public void setExpire(long expire) {
+		this.expire = expire;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public Serializable getValue() {
+		return value;
+	}
+
+	public void setValue(Serializable value) {
+		this.value = value;
+	}
+	
+}

+ 65 - 0
jim-common/src/main/java/org/jim/common/cache/redis/JedisSubscriber.java

@@ -0,0 +1,65 @@
+/**
+ * 
+ */
+package org.jim.common.cache.redis;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.jim.common.cache.CacheChangeType;
+import org.jim.common.cache.CacheChangedVo;
+import org.jim.common.cache.caffeineredis.CaffeineRedisCache;
+import org.jim.common.cache.caffeineredis.CaffeineRedisCacheManager;
+import redis.clients.jedis.JedisPubSub;
+
+/**
+ * 
+ * @author WChao
+ * @date 2018年3月8日 下午1:00:02
+ */
+public class JedisSubscriber extends JedisPubSub {
+	
+	private static Logger log = LoggerFactory.getLogger(JedisSubscriber.class);
+	
+	@Override
+	public void onMessage(String channel, String message) {
+		log.debug(String.format("接收到redis发布的消息,chanel %s,message %s",channel,message));
+		String[] cacheChanges = message.split(":");
+		String cacheName = cacheChanges[0];
+		String key = cacheChanges[1];
+		CacheChangeType type = CacheChangeType.from(Integer.valueOf(cacheChanges[2]));
+		String clientid = cacheChanges[3];
+		if (StringUtils.isBlank(clientid)) {
+			log.error("clientid is null");
+			return;
+		}
+		if (Objects.equals(CacheChangedVo.CLIENTID, clientid)) {
+			log.debug("自己发布的消息,{}", clientid);
+			return;
+		}
+		CaffeineRedisCache caffeineRedisCache = CaffeineRedisCacheManager.getCache(cacheName);
+		if (caffeineRedisCache == null) {
+			log.debug("不能根据cacheName[{}]找到CaffeineRedisCache对象", cacheName);
+			return;
+		}
+		if (type == CacheChangeType.PUT  || type == CacheChangeType.UPDATE || type == CacheChangeType.REMOVE) {
+			caffeineRedisCache.getCaffeineCache().remove(key);
+		} else if (type == CacheChangeType.CLEAR) {
+			caffeineRedisCache.getCaffeineCache().clear();
+		}
+	}
+
+	@Override
+	public void onSubscribe(String channel, int subscribedChannels) {
+		log.debug("订阅 redis 通道成功, channel {} , subscribedChannels {}",channel,subscribedChannels);
+	}
+
+	@Override
+	public void onUnsubscribe(String channel, int subscribedChannels) {
+		log.debug(String.format("取消订阅 redis通道 , channel %s , subscribeChannels %d",channel,subscribedChannels));
+	}
+	
+	
+}

File diff suppressed because it is too large
+ 1295 - 0
jim-common/src/main/java/org/jim/common/cache/redis/JedisTemplate.java


+ 266 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisCache.java

@@ -0,0 +1,266 @@
+package org.jim.common.cache.redis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jim.common.cache.ICache;
+import org.jim.common.cache.redis.JedisTemplate.Pair;
+import org.jim.common.cache.redis.JedisTemplate.PairEx;
+import org.jim.common.utils.JsonKit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.utils.SystemTimer;
+/**
+ *
+ * @author wchao
+ * 2017年8月10日 下午1:35:01
+ */
+public class RedisCache implements ICache {
+	
+	private Logger log = LoggerFactory.getLogger(RedisCache.class);
+	
+	public static String cacheKey(String cacheName, String key) {
+		return keyPrefix(cacheName) + key;
+	}
+
+	public static String keyPrefix(String cacheName) {
+		return cacheName + ":";
+	}
+
+	public static void main(String[] args) {
+	}
+
+	private String cacheName = null;
+
+	private Integer timeToLiveSeconds = null;
+
+	private Integer timeToIdleSeconds = null;
+
+	private Integer timeout = null;
+
+	public RedisCache(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds) {
+		this.cacheName = cacheName;
+		this.timeToLiveSeconds = timeToLiveSeconds;
+		this.timeToIdleSeconds = timeToIdleSeconds;
+		this.timeout = this.timeToLiveSeconds == null ? this.timeToIdleSeconds : this.timeToLiveSeconds;
+
+	}
+
+	@Override
+	public void clear() {
+		long start = SystemTimer.currentTimeMillis();
+		try {
+			JedisTemplate.me().delKeysLike(keyPrefix(cacheName));
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		long end = SystemTimer.currentTimeMillis();
+		long iv = end - start;
+		log.info("clear cache {}, cost {}ms", cacheName, iv);
+	}
+
+	@Override
+	public Serializable get(String key) {
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		Serializable value = null;
+		try {
+			value = JedisTemplate.me().get(cacheKey(cacheName, key), Serializable.class);
+			if (timeToIdleSeconds != null) {
+				if (value != null) {
+					RedisExpireUpdateTask.add(cacheName, key, value ,timeout);
+				}
+			}
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return value;
+	}
+	@Override
+	public <T> T get(String key, Class<T> clazz) {
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		T value = null;
+		try {
+			value = JedisTemplate.me().get(cacheKey(cacheName, key),clazz);
+			if (timeToIdleSeconds != null) {
+				if (value != null) {
+					RedisExpireUpdateTask.add(cacheName, key, (Serializable) value ,timeout);
+				}
+			}
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return value;
+	}
+	@Override
+	public Collection<String> keys() {
+		try {
+			return JedisTemplate.me().keys(keyPrefix(cacheName));
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return null;
+	}
+
+	@Override
+	public void put(String key, Serializable value) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try {
+			JedisTemplate.me().set(cacheKey(cacheName, key), value, Integer.parseInt(timeout+""));
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	public void putAll(List<Pair<String,Serializable>> values) {
+		if (values == null || values.size() < 1) {
+			return;
+		}
+		int expire = Integer.parseInt(timeout+"");
+		try {
+			List<PairEx<String,String,Integer>> pairDatas = new ArrayList<PairEx<String,String,Integer>>();
+			for(Pair<String,Serializable> pair : values){
+				pairDatas.add(JedisTemplate.me().makePairEx(cacheKey(cacheName, pair.getKey()),JsonKit.toJSONString(pair.getValue()),expire));
+			}
+			JedisTemplate.me().batchSetStringEx(pairDatas);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	public void listPushTail(String key, Serializable value) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try {
+			String jsonValue = value instanceof String? (String)value:JsonKit.toJSONString(value);
+			JedisTemplate.me().listPushTail(cacheKey(cacheName, key),jsonValue);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	public List<String> listGetAll(String key) {
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		try {
+			return JedisTemplate.me().listGetAll(cacheKey(cacheName, key));
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return null;
+	}
+	public Long listRemove(String key ,String value){
+		if(StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
+			return 0L;
+		}
+		try {
+			return JedisTemplate.me().listRemove(cacheKey(cacheName, key), 0, value);
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return 0L;
+	}
+	public void sortSetPush(String key ,double score , Serializable value){
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try {
+			String jsonValue = value instanceof String? (String)value:JsonKit.toJSONString(value);
+			JedisTemplate.me().sortSetPush(cacheKey(cacheName, key),score,jsonValue);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	public List<String> sortSetGetAll(String key){
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		try {
+			Set<String> dataSet = JedisTemplate.me().sorSetRangeByScore(cacheKey(cacheName, key),Double.MIN_VALUE,Double.MAX_VALUE);
+			if(dataSet == null) {
+				return null;
+			}
+			return new ArrayList<String>(dataSet);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return null;
+	}
+	public List<String> sortSetGetAll(String key,double min,double max){
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		try {
+			Set<String> dataSet = JedisTemplate.me().sorSetRangeByScore(cacheKey(cacheName, key),min,max);
+			if(dataSet == null) {
+				return null;
+			}
+			return new ArrayList<String>(dataSet);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return null;
+	}
+	public List<String> sortSetGetAll(String key,double min,double max,int offset ,int count){
+		if (StringUtils.isBlank(key)) {
+			return null;
+		}
+		try {
+			Set<String> dataSet = JedisTemplate.me().sorSetRangeByScore(cacheKey(cacheName, key),min,max,offset,count);
+			if(dataSet == null) {
+				return null;
+			}
+			return new ArrayList<String>(dataSet);
+		}catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+		return null;
+	}
+	@Override
+	public void putTemporary(String key, Serializable value) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try {
+			JedisTemplate.me().set(cacheKey(cacheName, key), value,10);
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+
+	@Override
+	public void remove(String key) {
+		if (StringUtils.isBlank(key)) {
+			return;
+		}
+		try {
+			JedisTemplate.me().delKey(cacheKey(cacheName, key));
+		} catch (Exception e) {
+			log.error(e.toString(),e);
+		}
+	}
+	
+	public String getCacheName() {
+		return cacheName;
+	}
+
+	public Integer getTimeout() {
+		return timeout;
+	}
+
+	public Integer getTimeToIdleSeconds() {
+		return timeToIdleSeconds;
+	}
+
+	public Integer getTimeToLiveSeconds() {
+		return timeToLiveSeconds;
+	}
+}

+ 51 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisCacheManager.java

@@ -0,0 +1,51 @@
+package org.jim.common.cache.redis;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author WChao
+ * @date 2018年3月8日 下午2:35:19
+ */
+public class RedisCacheManager {
+	
+	private static Logger log = LoggerFactory.getLogger(RedisCache.class);
+	private static Map<String, RedisCache> map = new HashMap<>();
+	
+	public static RedisCache getCache(String cacheName) {
+		RedisCache redisCache = map.get(cacheName);
+		if (redisCache == null) {
+			log.error("cacheName[{}]还没注册,请初始化时调用:{}.register(redisson, cacheName, timeToLiveSeconds, timeToIdleSeconds)", cacheName, RedisCache.class.getSimpleName());
+		}
+		return redisCache;
+	}
+	
+	/**
+	 * timeToLiveSeconds和timeToIdleSeconds不允许同时为null
+	 * @param cacheName
+	 * @param timeToLiveSeconds
+	 * @param timeToIdleSeconds
+	 * @return
+	 * @author wchao
+	 */
+	public static RedisCache register(String cacheName, Integer timeToLiveSeconds, Integer timeToIdleSeconds) {
+		RedisExpireUpdateTask.start();
+
+		RedisCache redisCache = map.get(cacheName);
+		if (redisCache == null) {
+			synchronized (RedisCacheManager.class) {
+				redisCache = map.get(cacheName);
+				if (redisCache == null) {
+					redisCache = new RedisCache(cacheName, timeToLiveSeconds, timeToIdleSeconds);
+					map.put(cacheName, redisCache);
+				}
+			}
+		}
+		return redisCache;
+	}
+
+	
+}

+ 94 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisConfiguration.java

@@ -0,0 +1,94 @@
+package org.jim.common.cache.redis;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+/**
+ * @author WChao
+ * @date 2018年3月9日 上午1:09:03
+ */
+public class RedisConfiguration {
+
+	private  int database = 0;
+	private  int retryNum =100;
+	private  int maxActive=100;
+	private  int maxIdle=20;
+	private  long maxWait=5000L;
+	private  int timeout=2000;
+	private  String auth;
+	private  String host = "";
+	private  int port= 0;
+	
+	public RedisConfiguration(){}
+	
+	public RedisConfiguration(Properties prop){
+		this.retryNum = Integer.valueOf(prop.getProperty("jim.redis.retrynum", "100"));
+		this.maxActive = Integer.valueOf(prop.getProperty("jim.redis.maxactive","100"));
+		this.maxIdle =  Integer.valueOf(prop.getProperty("jim.redis.maxidle", "20"));
+		this.maxWait =  Long.valueOf(prop.getProperty("jim.redis.maxwait","5000"));
+		this.timeout =  Integer.valueOf(prop.getProperty("jim.redis.timeout", "2000"));
+		//设置redis数据库
+		this.database = Integer.valueOf(prop.getProperty("jim.redis.database", "0"));
+		this.auth =  prop.getProperty("jim.redis.auth",null);
+		if(StringUtils.isEmpty(auth)) {
+			this.auth = null;
+		}
+		this.host = prop.getProperty("jim.redis.host","");
+		this.port =  Integer.valueOf(prop.getProperty("jim.redis.port","0"));
+	}
+
+	public int getDatabase() {
+		return database;
+	}
+	public void setDatabase(int database) {
+		this.database = database;
+	}
+	public int getRetryNum() {
+		return retryNum;
+	}
+	public void setRetryNum(int retryNum) {
+		this.retryNum = retryNum;
+	}
+	public int getMaxActive() {
+		return maxActive;
+	}
+	public void setMaxActive(int maxActive) {
+		this.maxActive = maxActive;
+	}
+	public int getMaxIdle() {
+		return maxIdle;
+	}
+	public void setMaxIdle(int maxIdle) {
+		this.maxIdle = maxIdle;
+	}
+	public long getMaxWait() {
+		return maxWait;
+	}
+	public void setMaxWait(long maxWait) {
+		this.maxWait = maxWait;
+	}
+	public int getTimeout() {
+		return timeout;
+	}
+	public void setTimeout(int timeout) {
+		this.timeout = timeout;
+	}
+	public int getPort() {
+		return port;
+	}
+	public void setPort(int port) {
+		this.port = port;
+	}
+	public String getAuth() {
+		return auth;
+	}
+	public void setAuth(String auth) {
+		this.auth = auth;
+	}
+	public String getHost() {
+		return host;
+	}
+	public void setHost(String host) {
+		this.host = host;
+	}
+	
+}

+ 124 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisConfigurationFactory.java

@@ -0,0 +1,124 @@
+package org.jim.common.cache.redis;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import org.jim.common.ImAio;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * @author WChao
+ * @date 2018年3月9日 上午1:06:33
+ */
+public class RedisConfigurationFactory {
+	
+    private static final Logger LOG = LoggerFactory.getLogger(RedisConfigurationFactory.class.getName());
+
+    private static final String DEFAULT_CLASSPATH_CONFIGURATION_FILE = "jim.properties";
+    
+    /**
+     * Constructor.
+     */
+    private RedisConfigurationFactory() {
+
+    }
+
+    /**
+     * Configures a bean from an property file.
+     */
+    public static RedisConfiguration parseConfiguration(final File file) throws Exception {
+        if (file == null) {
+            throw new Exception("Attempt to configure redis from null file.");
+        }
+        LOG.debug("Configuring redis from file: {}", file);
+        RedisConfiguration configuration = null;
+        InputStream input = null;
+        try {
+            input = new BufferedInputStream(new FileInputStream(file));
+            configuration = parseConfiguration(input);
+        } catch (Exception e) {
+            throw new Exception("Error configuring from " + file + ". Initial cause was " + e.getMessage(), e);
+        } finally {
+            try {
+                if (input != null) {
+                    input.close();
+                }
+            } catch (IOException e) {
+                LOG.error("IOException while closing configuration input stream. Error was " + e.getMessage());
+            }
+        }
+        return configuration;
+    }
+    /**
+     * Configures a bean from an property file available as an URL.
+     */
+    public static RedisConfiguration parseConfiguration(final URL url) throws Exception {
+        LOG.debug("Configuring redis from URL: {}", url);
+        RedisConfiguration configuration;
+        InputStream input = null;
+        try {
+            input = url.openStream();
+            configuration = parseConfiguration(input);
+        } catch (Exception e) {
+            throw new Exception("Error configuring from " + url + ". Initial cause was " + e.getMessage(), e);
+        } finally {
+            try {
+                if (input != null) {
+                    input.close();
+                }
+            } catch (IOException e) {
+                LOG.error("IOException while closing configuration input stream. Error was " + e.getMessage());
+            }
+        }
+        return configuration;
+    }
+    /**
+     * Configures a bean from an property file in the classpath.
+     */
+    public static RedisConfiguration parseConfiguration() throws Exception {
+        ClassLoader standardClassloader = Thread.currentThread().getContextClassLoader();
+        URL url = null;
+        if (standardClassloader != null) {
+            url = standardClassloader.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
+        }
+        if (url == null) {
+        	url = ImAio.class.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
+        }
+        if (url != null) {
+            LOG.debug("Configuring redis from jim.properties found in the classpath: " + url);
+        } else {
+            LOG.warn("No configuration found. Configuring redis from jim.properties "
+                    + " found in the classpath: {}", url);
+
+        }
+        RedisConfiguration configuration = parseConfiguration(url);
+        return configuration;
+    }
+    
+    /**
+     * Configures a bean from an property input stream.
+     */
+    public static RedisConfiguration parseConfiguration(final InputStream inputStream) throws Exception {
+
+        LOG.debug("Configuring redis from InputStream");
+
+        RedisConfiguration configuration = null;
+        try {
+            Properties prop = new Properties();
+            prop.load(inputStream);
+            configuration = new RedisConfiguration(prop);
+        } catch (Exception e) {
+            throw new Exception("Error configuring from input stream. Initial cause was " + e.getMessage(), e);
+        }
+        return configuration;
+    }
+    
+    public static void main(String[] args) throws Exception{
+		RedisConfigurationFactory.parseConfiguration();
+	}
+}

+ 79 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisExpireUpdateTask.java

@@ -0,0 +1,79 @@
+package org.jim.common.cache.redis;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.jim.common.cache.redis.JedisTemplate.PairEx;
+
+/**
+ * 定时更新redis的过期时间
+ * @author wchao
+ * 2017年8月14日 下午1:34:06
+ */
+public class RedisExpireUpdateTask {
+	private static Logger log = LoggerFactory.getLogger(RedisExpireUpdateTask.class);
+
+	private static boolean started = false;
+
+	private static LinkedBlockingQueue<ExpireVo> redisExpireVoQueue = new LinkedBlockingQueue<ExpireVo>();
+
+	public static void add(String cacheName, String key, Serializable value, long expire) {
+		ExpireVo expireVo = new ExpireVo(cacheName, key, value, expire);
+		redisExpireVoQueue.offer(expireVo);
+	}
+
+	public static void start() {
+		if (started) {
+			return;
+		}
+		synchronized (RedisExpireUpdateTask.class) {
+			if (started) {
+				return;
+			}
+			started = true;
+		}
+
+		new Thread(new Runnable() {
+			@Override
+			public void run() {
+				List<PairEx<String,Void,Integer>> l2Datas = new ArrayList<PairEx<String,Void,Integer>>();
+				int count = 0;
+				while (true) {
+					try {
+						ExpireVo expireVo = redisExpireVoQueue.poll();
+						if(expireVo != null){
+							l2Datas.add(JedisTemplate.me().makePairEx(expireVo.getKey(),null,Integer.parseInt(expireVo.getExpire()+"")));
+							count++;
+						}
+						if(count > 0 && expireVo == null){
+							log.debug("批量更新缓存过期时间,更新数量:"+l2Datas.size());
+							JedisTemplate.me().batchSetExpire(l2Datas);
+							l2Datas.clear();
+							count = 0;
+						}else if(count == 0 && expireVo == null){
+							try {
+								Thread.sleep(1000 * 5);
+							 } catch (InterruptedException e) {
+								log.error(e.toString(), e);
+							 }
+						}
+					} catch (Throwable e) {
+						log.error(e.getMessage(), e);
+					}
+				}
+			}
+		}, RedisExpireUpdateTask.class.getName()).start();
+	}
+
+	/**
+	 *
+	 * @author wchao
+	 */
+	private RedisExpireUpdateTask() {
+		
+	}
+}

+ 140 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedisLock.java

@@ -0,0 +1,140 @@
+package org.jim.common.cache.redis;
+
+import java.util.Random;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+@SuppressWarnings({"deprecation"})
+public class RedisLock {  
+    
+    /** 加锁标志 */  
+    public static final String LOCKED = "TRUE";  
+    /** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */  
+    public static final long MILLI_NANO_CONVERSION = 1000 * 1000L;  
+    /** 默认超时时间(毫秒) */  
+    public static final long DEFAULT_TIME_OUT = 1000;  
+    public static final Random RANDOM = new Random();  
+    /** 锁的超时时间(秒),过期删除 */  
+    public static final int EXPIRE = 3 * 60;  
+  
+    private JedisPool jedisPool;  
+    private Jedis jedis;  
+    private String key;  
+    // 锁状态标志  
+    private boolean locked = false;  
+  
+    /** 
+     * This creates a RedisLock 
+     * @param key key 
+     * @param shardedJedisPool 数据源 
+     */  
+    public RedisLock(String key, JedisPool shardedJedisPool) {  
+        this.key = key + "_lock";  
+        this.jedisPool = shardedJedisPool;  
+        this.jedis = this.jedisPool.getResource();  
+    }  
+  
+    /** 
+     * 加锁 
+     * 应该以: 
+     * lock(); 
+     * try { 
+     *      doSomething(); 
+     * } finally { 
+     *      unlock(); 
+     * } 
+     * 的方式调用  
+     * @param timeout 超时时间 
+     * @return 成功或失败标志 
+     */  
+    public boolean lock(long timeout) {  
+        long nano = System.nanoTime();  
+        timeout *= MILLI_NANO_CONVERSION;  
+        try {  
+            while ((System.nanoTime() - nano) < timeout) {  
+                if (this.jedis.setnx(this.key, LOCKED) == 1) {  
+                    this.jedis.expire(this.key, EXPIRE);  
+                    this.locked = true;  
+                    return this.locked;  
+                }  
+                // 短暂休眠,避免出现活锁  
+                Thread.sleep(3, RANDOM.nextInt(500));  
+            }  
+        } catch (Exception e) {  
+            throw new RuntimeException("Locking error", e);  
+        }  
+        return false;  
+    }  
+  
+    /** 
+     * 加锁 
+     * 应该以: 
+     * lock(); 
+     * try { 
+     *      doSomething(); 
+     * } finally { 
+     *      unlock(); 
+     * } 
+     * 的方式调用 
+     * @param timeout 超时时间 
+     * @param expire 锁的超时时间(秒),过期删除 
+     * @return 成功或失败标志 
+     */  
+    public boolean lock(long timeout, int expire) {  
+        long nano = System.nanoTime();  
+        timeout *= MILLI_NANO_CONVERSION;  
+        try {  
+            while ((System.nanoTime() - nano) < timeout) {  
+                if (this.jedis.setnx(this.key, LOCKED) == 1) {  
+                    this.jedis.expire(this.key, expire);  
+                    this.locked = true;  
+                    return this.locked;  
+                }  
+                // 短暂休眠,避免出现活锁  
+                Thread.sleep(3, RANDOM.nextInt(500));  
+            }  
+        } catch (Exception e) {  
+            throw new RuntimeException("Locking error", e);  
+        }  
+        return false;  
+    }  
+  
+    /** 
+     * 加锁 
+     * 应该以: 
+     * lock(); 
+     * try { 
+     *      doSomething(); 
+     * } finally { 
+     *      unlock(); 
+     * } 
+     * 的方式调用 
+     * @return 成功或失败标志 
+     */  
+    public boolean lock() {  
+        return lock(DEFAULT_TIME_OUT);  
+    }  
+  
+    /** 
+     * 解锁 
+     * 无论是否加锁成功,都需要调用unlock 
+     * 应该以: 
+     * lock(); 
+     * try { 
+     *      doSomething(); 
+     * } finally { 
+     *      unlock(); 
+     * } 
+     * 的方式调用 
+     */  
+    public void unlock() {  
+        try {  
+            if (this.locked) {  
+                this.jedis.del(this.key);  
+            }  
+        } finally {  
+            this.jedisPool.returnResource(this.jedis);  
+        }  
+    }  
+}  

+ 71 - 0
jim-common/src/main/java/org/jim/common/cache/redis/RedissonTemplate.java

@@ -0,0 +1,71 @@
+package org.jim.common.cache.redis;
+
+import java.io.Serializable;
+
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.SingleServerConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * @author WChao
+ * @date 2018年5月18日 下午2:46:55
+ */
+public class RedissonTemplate implements Serializable{
+
+	private static final long serialVersionUID = -4528751601700736437L;
+	private static final Logger logger = LoggerFactory.getLogger(RedissonTemplate.class);
+	private static volatile RedissonTemplate instance = null;
+	private static RedisConfiguration redisConfig = null;
+	private static final String REDIS = "redis";
+	private static RedissonClient redissonClient = null;
+	
+	private RedissonTemplate(){};
+	
+	public static RedissonTemplate me() throws Exception{
+		 if (instance == null) { 
+	        	synchronized (RedissonTemplate.class) {
+					if(instance == null){
+						redisConfig = RedisConfigurationFactory.parseConfiguration();
+						init();
+						instance = new RedissonTemplate();
+					}
+				}
+	     }
+		 return instance;
+	}
+	
+	private static final void init() throws Exception {
+			String host = redisConfig.getHost();
+			if(host == null) {
+				logger.error("the server ip of redis  must be not null!");
+				throw new Exception("the server ip of redis  must be not null!");
+			}	
+			int port = redisConfig.getPort();
+			String password = redisConfig.getAuth();
+			Config redissonConfig = new Config();
+			SingleServerConfig singleServerConfig = redissonConfig.useSingleServer();
+			singleServerConfig
+					.setAddress(REDIS+"://"+host+":"+port)
+					.setPassword(password)
+					//设置redis数据库
+					.setDatabase(redisConfig.getDatabase())
+					.setTimeout(redisConfig.getTimeout())
+					.setRetryAttempts(redisConfig.getRetryNum());
+			try {
+			   redissonClient = Redisson.create(redissonConfig);
+			} catch (Exception e) {
+				logger.error("cann't create RedissonClient for server"+redisConfig.getHost());
+				throw new Exception("cann't create RedissonClient for server"+redisConfig.getHost());
+			}
+			
+	}
+	/**
+	 * 获取RedissonClient客户端;
+	 * @return
+	 */
+	public final RedissonClient getRedissonClient(){
+		return redissonClient;
+	}
+}

+ 46 - 0
jim-common/src/main/java/org/jim/common/cache/redis/SubRunnable.java

@@ -0,0 +1,46 @@
+package org.jim.common.cache.redis;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import redis.clients.jedis.Jedis;
+
+/**
+ * @author WChao
+ * @date 2018年3月8日 下午1:07:55
+ */
+public class SubRunnable implements Runnable {
+	
+	Logger log = LoggerFactory.getLogger(SubRunnable.class);
+	private  String subChannel = null;
+	private Jedis jedis = null;
+	private JedisSubscriber subscriber = new JedisSubscriber();
+	public SubRunnable(String subChannel){
+		if(StringUtils.isEmpty(subChannel)){
+			throw new RuntimeException("chanel通道异常!");
+		}
+		this.subChannel = subChannel;
+	}
+	@Override
+	public void run() {
+		log.debug("订阅 redis , chanel {} , 线程将阻塞",subChannel);
+		while(true){
+			try {
+				jedis = JedisTemplate.me().getJedis();
+				if(jedis != null){
+					 jedis.subscribe(subscriber, subChannel);
+				}
+			} catch (Exception e) {
+				log.error(e.toString(),e);
+				log.error("订阅线程异常,重新获取订阅客户端连接...");
+			}finally {
+				try {
+					JedisTemplate.me().close(jedis);
+				} catch (Exception e) {
+					log.error(e.toString(),e);
+				}
+			}
+		}
+	}
+}

+ 19 - 0
jim-common/src/main/java/org/jim/common/cluster/ICluster.java

@@ -0,0 +1,19 @@
+/**
+ * 
+ */
+package org.jim.common.cluster;
+
+import org.jim.common.ImPacket;
+import org.tio.core.GroupContext;
+
+/**
+ * 
+ * @author WChao
+ *
+ */
+public interface ICluster {
+	public void clusterToUser(GroupContext groupContext, String userid,ImPacket packet);
+	public void clusterToGroup(GroupContext groupContext, String group,ImPacket packet);
+	public void clusterToIp(GroupContext groupContext, String ip,ImPacket packet);
+	public void clusterToChannelId(GroupContext groupContext, String channelId,ImPacket packet);
+}

+ 21 - 0
jim-common/src/main/java/org/jim/common/cluster/ImCluster.java

@@ -0,0 +1,21 @@
+/**
+ * 
+ */
+package org.jim.common.cluster;
+
+/**
+ * 
+ * @author WChao
+ *
+ */
+public abstract class ImCluster implements ICluster{
+	
+	protected ImClusterConfig clusterConfig;
+	
+	public ImCluster(ImClusterConfig clusterConfig){
+		this.clusterConfig = clusterConfig;
+	}
+	public ImClusterConfig getClusterConfig() {
+		return clusterConfig;
+	}
+}

+ 77 - 0
jim-common/src/main/java/org/jim/common/cluster/ImClusterConfig.java

@@ -0,0 +1,77 @@
+package org.jim.common.cluster;
+
+import org.tio.core.GroupContext;
+
+/**
+ * 
+ * @author WChao
+ * 2018年05月20日 下午1:09:16
+ */
+public abstract class ImClusterConfig {
+	
+	/**
+	 * 群组是否集群(同一个群组是否会分布在不同的机器上),false:不集群,默认不集群
+	 */
+	private boolean cluster4group = true;
+	/**
+	 * 用户是否集群(同一个用户是否会分布在不同的机器上),false:不集群,默认集群
+	 */
+	private boolean cluster4user = true;
+	/**
+	 * ip是否集群(同一个ip是否会分布在不同的机器上),false:不集群,默认集群
+	 */
+	private boolean cluster4ip = true;
+	/**
+	 * id是否集群(在A机器上的客户端是否可以通过channelId发消息给B机器上的客户端),false:不集群,默认集群<br>
+	 */
+	private boolean cluster4channelId = true;
+	/**
+	 * 所有连接是否集群(同一个ip是否会分布在不同的机器上),false:不集群,默认集群
+	 */
+	private boolean cluster4all = true;
+
+	protected GroupContext groupContext = null;
+	
+	public abstract void send(ImClusterVo imClusterVo);
+	public abstract void sendAsyn(ImClusterVo imClusterVo);
+	
+	public boolean isCluster4group() {
+		return cluster4group;
+	}
+
+	public void setCluster4group(boolean cluster4group) {
+		this.cluster4group = cluster4group;
+	}
+
+	public boolean isCluster4user() {
+		return cluster4user;
+	}
+
+	public void setCluster4user(boolean cluster4user) {
+		this.cluster4user = cluster4user;
+	}
+
+	public boolean isCluster4ip() {
+		return cluster4ip;
+	}
+
+	public void setCluster4ip(boolean cluster4ip) {
+		this.cluster4ip = cluster4ip;
+	}
+
+	public boolean isCluster4all() {
+		return cluster4all;
+	}
+
+	public void setCluster4all(boolean cluster4all) {
+		this.cluster4all = cluster4all;
+	}
+
+	public boolean isCluster4channelId() {
+		return cluster4channelId;
+	}
+
+	public void setCluster4channelId(boolean cluster4channelId) {
+		this.cluster4channelId = cluster4channelId;
+	}
+}

+ 118 - 0
jim-common/src/main/java/org/jim/common/cluster/ImClusterVo.java

@@ -0,0 +1,118 @@
+package org.jim.common.cluster;
+
+import java.util.UUID;
+
+import org.jim.common.ImPacket;
+/**
+ * 成员变量group, userid, ip谁有值就发给谁,toAll为true则发给所有<br>
+ * packet是不允许为null的
+ * @author WChao 
+ * 2018年05月20日 下午3:10:29
+ */
+public class ImClusterVo implements java.io.Serializable {
+	private static final long serialVersionUID = 6978027913776155664L;
+	
+	public static final String CLIENTID = UUID.randomUUID().toString();
+
+	private ImPacket packet;
+
+	private String clientId = CLIENTID;
+	
+	private String group;
+
+	private String userid;
+	
+	private String token;
+	
+	private String ip;
+	
+	/**
+	 * ChannelContext'id
+	 */
+	private String channelId;
+	
+	private boolean toAll = false;
+
+	public ImPacket getPacket() {
+		return packet;
+	}
+
+	public void setPacket(ImPacket packet) {
+		this.packet = packet;
+	}
+
+	public String getGroup() {
+		return group;
+	}
+
+	public void setGroup(String group) {
+		this.group = group;
+	}
+
+	public String getUserid() {
+		return userid;
+	}
+
+	public void setUserid(String userid) {
+		this.userid = userid;
+	}
+
+	public String getIp() {
+		return ip;
+	}
+
+	public void setIp(String ip) {
+		this.ip = ip;
+	}
+
+	/**
+	 * 
+	 * @author: WChao
+	 */
+	public ImClusterVo() {
+	}
+	
+	public ImClusterVo(ImPacket packet) {
+		this.packet = packet;
+	}
+
+	/**
+	 * @param args
+	 * @author: WChao
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	public boolean isToAll() {
+		return toAll;
+	}
+
+	public void setToAll(boolean toAll) {
+		this.toAll = toAll;
+	}
+
+	public String getClientId() {
+		return clientId;
+	}
+
+	public void setClientId(String clientId) {
+		this.clientId = clientId;
+	}
+
+	public String getChannelId() {
+		return channelId;
+	}
+
+	public void setChannelId(String channelId) {
+		this.channelId = channelId;
+	}
+
+	public String getToken() {
+		return token;
+	}
+
+	public void setToken(String token) {
+		this.token = token;
+	}
+}

+ 56 - 0
jim-common/src/main/java/org/jim/common/cluster/redis/RedisCluster.java

@@ -0,0 +1,56 @@
+/**
+ * 
+ */
+package org.jim.common.cluster.redis;
+
+import org.jim.common.ImPacket;
+import org.jim.common.cluster.ImCluster;
+import org.jim.common.cluster.ImClusterVo;
+import org.tio.core.GroupContext;
+/**
+ * @author WChao
+ *
+ */
+public class RedisCluster extends ImCluster{
+
+	public RedisCluster(RedisClusterConfig clusterConfig) {
+		super(clusterConfig);
+	}
+
+	@Override
+	public void clusterToUser(GroupContext groupContext, String userid,ImPacket packet) {
+		if (clusterConfig.isCluster4user()) {
+			ImClusterVo imClusterVo = new ImClusterVo(packet);
+			imClusterVo.setUserid(userid);
+			clusterConfig.sendAsyn(imClusterVo);
+		}
+	}
+
+	@Override
+	public void clusterToGroup(GroupContext groupContext, String group,ImPacket packet) {
+		if(clusterConfig.isCluster4group()){
+			ImClusterVo imClusterVo = new ImClusterVo(packet);
+			imClusterVo.setGroup(group);
+			clusterConfig.sendAsyn(imClusterVo);
+		}
+	}
+
+	@Override
+	public void clusterToIp(GroupContext groupContext, String ip,ImPacket packet) {
+		if(clusterConfig.isCluster4ip()){
+			ImClusterVo imClusterVo = new ImClusterVo(packet);
+			imClusterVo.setIp(ip);
+			clusterConfig.sendAsyn(imClusterVo);
+		}
+	}
+
+	@Override
+	public void clusterToChannelId(GroupContext groupContext, String channelId,ImPacket packet) {
+		if(clusterConfig.isCluster4channelId()){
+			ImClusterVo imClusterVo = new ImClusterVo(packet);
+			imClusterVo.setChannelId(channelId);
+			clusterConfig.sendAsyn(imClusterVo);
+		}
+	}
+
+}

+ 174 - 0
jim-common/src/main/java/org/jim/common/cluster/redis/RedisClusterConfig.java

@@ -0,0 +1,174 @@
+/**
+ * 
+ */
+package org.jim.common.cluster.redis;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jim.common.ImAio;
+import org.jim.common.ImPacket;
+import org.jim.common.cluster.ImClusterConfig;
+import org.jim.common.cluster.ImClusterVo;
+import org.redisson.api.RTopic;
+import org.redisson.api.RedissonClient;
+import org.redisson.api.listener.MessageListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.core.Aio;
+import org.tio.core.GroupContext;
+import org.tio.utils.json.Json;
+
+/**
+ * 
+ * @author WChao
+ *
+ */
+public class RedisClusterConfig extends ImClusterConfig {
+	
+	private static Logger log = LoggerFactory.getLogger(RedisClusterConfig.class);
+	
+	public static final String IM_CLUSTER_TOPIC = "JIM_CLUSTER";
+
+	private String topicSuffix;
+
+	private String topic;
+
+	private RedissonClient redisson;
+
+	public RTopic<ImClusterVo> rtopic;
+	
+	/**
+	 * 收到了多少次topic
+	 */
+	public static final AtomicLong RECEIVED_TOPIC_COUNT = new AtomicLong();
+	
+	/**
+	 * J-IM内置的集群是用redis的topic来实现的,所以不同groupContext就要有一个不同的topicSuffix
+	 * @param topicSuffix 不同类型的groupContext就要有一个不同的topicSuffix
+	 * @param redisson
+	 * @param groupContext
+	 * @return
+	 * @author: WChao
+	 */
+	public static RedisClusterConfig newInstance(String topicSuffix, RedissonClient redisson, GroupContext groupContext) {
+		if (redisson == null) {
+			throw new RuntimeException(RedissonClient.class.getSimpleName() + "不允许为空");
+		}
+		if (groupContext == null) {
+			throw new RuntimeException("GroupContext不允许为空");
+		}
+
+		RedisClusterConfig me = new RedisClusterConfig(topicSuffix, redisson, groupContext);
+		me.rtopic = redisson.getTopic(me.topic);
+		me.rtopic.addListener(new MessageListener<ImClusterVo>() {
+			@Override
+			public void onMessage(String channel, ImClusterVo imClusterVo) {
+				log.info("收到topic:{}, count:{}, ImClusterVo:{}", channel, RECEIVED_TOPIC_COUNT.incrementAndGet(), Json.toJson(imClusterVo));
+				String clientid = imClusterVo.getClientId();
+				if (StringUtils.isBlank(clientid)) {
+					log.error("clientid is null");
+					return;
+				}
+				if (Objects.equals(ImClusterVo.CLIENTID, clientid)) {
+					log.info("自己发布的消息,忽略掉,{}", clientid);
+					return;
+				}
+
+				ImPacket packet = imClusterVo.getPacket();
+				if (packet == null) {
+					log.error("packet is null");
+					return;
+				}
+				packet.setFromCluster(true);
+				
+				//发送给所有
+				boolean isToAll = imClusterVo.isToAll();
+				if (isToAll) {
+					//								for (GroupContext groupContext : me.groupContext) {
+					Aio.sendToAll(groupContext, packet);
+					//								}
+					//return;
+				}
+
+				//发送给指定组
+				String group = imClusterVo.getGroup();
+				if (StringUtils.isNotBlank(group)) {
+					ImAio.sendToGroup(group, packet);
+					//return;
+				}
+
+				//发送给指定用户
+				String userid = imClusterVo.getUserid();
+				if (StringUtils.isNotBlank(userid)) {
+					//								for (GroupContext groupContext : me.groupContext) {
+					ImAio.sendToUser(userid, packet);
+					//								}
+					//return;
+				}
+				
+				//发送给指定token
+				String token = imClusterVo.getToken();
+				if (StringUtils.isNotBlank(token)) {
+					//								for (GroupContext groupContext : me.groupContext) {
+					Aio.sendToToken(me.groupContext, token, packet);
+					//								}
+					//return;
+				}
+
+				//发送给指定ip
+				String ip = imClusterVo.getIp();
+				if (StringUtils.isNotBlank(ip)) {
+					//								for (GroupContext groupContext : me.groupContext) {
+					ImAio.sendToIp(me.groupContext, ip, packet);
+					//								}
+					//return;
+				}
+			}
+		});
+		return me;
+	}
+	private RedisClusterConfig(String topicSuffix, RedissonClient redisson, GroupContext groupContext) {
+		this.setTopicSuffix(topicSuffix);
+		this.setRedisson(redisson);
+		this.groupContext = groupContext;
+	}
+	public String getTopicSuffix() {
+		return topicSuffix;
+	}
+
+	public void setTopicSuffix(String topicSuffix) {
+		this.topicSuffix = topicSuffix;
+		this.topic = topicSuffix + IM_CLUSTER_TOPIC;
+
+	}
+
+	public String getTopic() {
+		return topic;
+	}
+	
+	public void publishAsyn(ImClusterVo imClusterVo) {
+		rtopic.publishAsync(imClusterVo);
+	}
+	
+	public void publish(ImClusterVo imClusterVo) {
+		rtopic.publish(imClusterVo);
+	}
+
+	public RedissonClient getRedisson() {
+		return redisson;
+	}
+
+	public void setRedisson(RedissonClient redisson) {
+		this.redisson = redisson;
+	}
+	@Override
+	public void send(ImClusterVo imClusterVo) {
+		rtopic.publish(imClusterVo);
+	}
+	@Override
+	public void sendAsyn(ImClusterVo imClusterVo) {
+		rtopic.publishAsync(imClusterVo);
+	}
+}

+ 12 - 0
jim-common/src/main/java/org/jim/common/codec/Decoder.java

@@ -0,0 +1,12 @@
+/**
+ * 
+ */
+package org.jim.common.codec;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年8月21日 下午3:12:18
+ */
+public interface Decoder {
+}

+ 13 - 0
jim-common/src/main/java/org/jim/common/codec/Encoder.java

@@ -0,0 +1,13 @@
+/**
+ * 
+ */
+package org.jim.common.codec;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年8月21日 下午3:12:35
+ */
+public interface Encoder {
+
+}

+ 172 - 0
jim-common/src/main/java/org/jim/common/config/Config.java

@@ -0,0 +1,172 @@
+/**
+ * 
+ */
+package org.jim.common.config;
+
+import org.jim.common.cluster.ImCluster;
+import org.jim.common.message.MessageHelper;
+import org.tio.core.GroupContext;
+import org.tio.core.intf.GroupListener;
+import org.tio.core.ssl.SslConfig;
+
+/**
+ * @author WChao
+ * 2018/08/26
+ */
+public class Config {
+	/**
+	 * IP地址
+	 */
+	protected String bindIp = null;
+	/**
+	 * 监听端口
+	 */
+	protected Integer bindPort = 80;
+	/**
+	 * 心跳包发送时长heartbeatTimeout/2
+	 */
+	protected long heartbeatTimeout = 0;
+	
+	/**
+	 * 全局群组上下文;
+	 */
+	protected GroupContext groupContext;
+	/**
+	 * 群组监听器;
+	 */
+	protected GroupListener imGroupListener;
+	/**
+	 * 用户消息持久化助手;
+	 */
+	protected MessageHelper messageHelper;
+	/**
+	 * 是否开启持久化;
+	 */
+	protected String isStore = "off";
+	/**
+	 * 是否开启集群;
+	 */
+	protected String isCluster = "off";
+	/**
+	 * 是否开启SSL加密
+	 */
+	protected String isSSL = "off";
+	/**
+	 * SSL配置
+	 */
+	protected SslConfig sslConfig;
+	/**
+	 * 集群配置
+	 * 如果此值不为null,就表示要集群
+	 */
+	protected ImCluster cluster;
+	/**
+	 *  默认的接收数据的buffer size
+	 */
+	protected long readBufferSize = 1024 * 2;
+	
+	public String getBindIp() {
+		return bindIp;
+	}
+	
+	public void setBindIp(String bindIp) {
+		this.bindIp = bindIp;
+	}
+	
+	public Integer getBindPort() {
+		return bindPort;
+	}
+	
+	public void setBindPort(Integer bindPort) {
+		this.bindPort = bindPort;
+	}
+
+	public long getHeartbeatTimeout() {
+		return heartbeatTimeout;
+	}
+
+	public void setHeartbeatTimeout(long heartbeatTimeout) {
+		this.heartbeatTimeout = heartbeatTimeout;
+	}
+
+	public GroupContext getGroupContext() {
+		return groupContext;
+	}
+
+	public void setGroupContext(GroupContext groupContext) {
+		this.groupContext = groupContext;
+	}
+
+	public GroupListener getImGroupListener() {
+		return imGroupListener;
+	}
+
+	public void setImGroupListener(GroupListener imGroupListener) {
+		this.imGroupListener = imGroupListener;
+	}
+
+	public MessageHelper getMessageHelper() {
+		return messageHelper;
+	}
+
+	public void setMessageHelper(MessageHelper messageHelper) {
+		this.messageHelper = messageHelper;
+	}
+
+	public String getIsStore() {
+		return isStore;
+	}
+
+	public void setIsStore(String isStore) {
+		this.isStore = isStore;
+	}
+
+	public String getIsCluster() {
+		return isCluster;
+	}
+
+	public void setIsCluster(String isCluster) {
+		this.isCluster = isCluster;
+	}
+
+	public String getIsSSL() {
+		return isSSL;
+	}
+
+	public void setIsSSL(String isSSL) {
+		this.isSSL = isSSL;
+	}
+
+	public SslConfig getSslConfig() {
+		return sslConfig;
+	}
+
+	public void setSslConfig(SslConfig sslConfig) {
+		this.sslConfig = sslConfig;
+	}
+
+	public ImCluster getCluster() {
+		return cluster;
+	}
+
+	public void setCluster(ImCluster cluster) {
+		this.cluster = cluster;
+	}
+
+	public long getReadBufferSize() {
+		return readBufferSize;
+	}
+
+	public void setReadBufferSize(long readBufferSize) {
+		this.readBufferSize = readBufferSize;
+	}
+	
+	public interface Builder {
+		/**
+		 * 配置构建接口
+		 * @return
+		 * @throws Exception
+		 */
+        Config build() throws Exception;
+    }
+}

+ 33 - 0
jim-common/src/main/java/org/jim/common/config/DefaultImConfigBuilder.java

@@ -0,0 +1,33 @@
+/**
+ * 
+ */
+package org.jim.common.config;
+
+import org.jim.common.http.HttpConfig;
+import org.jim.common.ws.WsServerConfig;
+
+/**
+ * @author WChao
+ *
+ */
+public class DefaultImConfigBuilder extends ImConfigBuilder {
+
+	/* (non-Javadoc)
+	 * @see org.jim.common.config.ImConfigBuilder#configHttp(org.jim.common.http.HttpConfig)
+	 */
+	@Override
+	public ImConfigBuilder configHttp(HttpConfig httpConfig) {
+		// TODO Auto-generated method stub
+		return this;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.jim.common.config.ImConfigBuilder#configWs(org.jim.common.ws.WsServerConfig)
+	 */
+	@Override
+	public ImConfigBuilder configWs(WsServerConfig wsServerConfig) {
+		// TODO Auto-generated method stub
+		return this;
+	}
+
+}

+ 96 - 0
jim-common/src/main/java/org/jim/common/config/ImConfigBuilder.java

@@ -0,0 +1,96 @@
+/**
+ * 
+ */
+package org.jim.common.config;
+
+import org.jim.common.ImConfig;
+import org.jim.common.cluster.ImCluster;
+import org.jim.common.http.HttpConfig;
+import org.jim.common.message.MessageHelper;
+import org.jim.common.ws.WsServerConfig;
+import org.tio.core.GroupContext;
+import org.tio.core.intf.GroupListener;
+import org.tio.core.ssl.SslConfig;
+
+/**
+ * @author WChao
+ * 2018/08/26
+ */
+public abstract class ImConfigBuilder implements Config.Builder {
+
+	protected ImConfig conf;
+	
+	public ImConfigBuilder() {
+		this.conf = new ImConfig();
+	}
+	
+	public abstract ImConfigBuilder configHttp(HttpConfig httpConfig);
+	public abstract ImConfigBuilder configWs(WsServerConfig wsServerConfig);
+	
+	public ImConfigBuilder setBindIp(String bindIp) {
+		this.conf.bindIp = bindIp;
+		return this;
+	}
+
+	public ImConfigBuilder setBindPort(Integer bindPort) {
+		this.conf.bindPort = bindPort;
+		return this;
+	}
+
+	public ImConfigBuilder setHeartbeatTimeout(long heartbeatTimeout) {
+		this.conf.heartbeatTimeout = heartbeatTimeout;
+		return this;
+	}
+
+	public ImConfigBuilder setGroupContext(GroupContext groupContext) {
+		this.conf.groupContext = groupContext;
+		return this;
+	}
+
+	public ImConfigBuilder setImGroupListener(GroupListener imGroupListener) {
+		this.conf.imGroupListener = imGroupListener;
+		return this;
+	}
+
+	public ImConfigBuilder setMessageHelper(MessageHelper messageHelper) {
+		this.conf.messageHelper = messageHelper;
+		return this;
+	}
+
+	public ImConfigBuilder setIsStore(String isStore) {
+		this.conf.isStore = isStore;
+		return this;
+	}
+
+	public ImConfigBuilder setIsCluster(String isCluster) {
+		this.conf.isCluster = isCluster;
+		return this;
+	}
+
+	public ImConfigBuilder setIsSSL(String isSSL) {
+		this.conf.isSSL = isSSL;
+		return this;
+	}
+
+	public ImConfigBuilder setSslConfig(SslConfig sslConfig) {
+		this.conf.sslConfig = sslConfig;
+		return this;
+	}
+
+	public ImConfigBuilder setCluster(ImCluster cluster) {
+		this.conf.cluster = cluster;
+		return this;
+	}
+
+	public ImConfigBuilder setReadBufferSize(long readBufferSize) {
+		this.conf.readBufferSize = readBufferSize;
+		return this;
+	}
+
+	@Override
+    public ImConfig build() {
+        this.configHttp(conf.getHttpConfig());
+        this.configWs(conf.getWsServerConfig());
+        return conf;
+	}
+}

+ 56 - 0
jim-common/src/main/java/org/jim/common/config/PropertyImConfigBuilder.java

@@ -0,0 +1,56 @@
+/**
+ * 
+ */
+package org.jim.common.config;
+
+import org.jim.common.ImConfig;
+import org.jim.common.http.HttpConfig;
+import org.jim.common.utils.Prop;
+import org.jim.common.utils.PropUtil;
+import org.jim.common.ws.WsServerConfig;
+/**
+ * @author WChao
+ * 2018/08/26
+ */
+public class PropertyImConfigBuilder extends ImConfigBuilder {
+
+	private Prop prop;
+
+	public PropertyImConfigBuilder(String file) {
+		prop = PropUtil.use(file);
+	}
+	
+	@Override
+	public ImConfigBuilder configHttp(HttpConfig httpConfig) {
+		//html/css/js等的根目录,支持classpath:,也支持绝对路径
+		//加载静态资源
+		String pageRoot = prop.get("jim.http.page");
+		//j-im mvc需要扫描的根目录包
+		String[] scanPackages = prop.get("jim.http.scan.packages").split(",");
+		httpConfig.setBindPort((prop.getInt("jim.port")));
+		//设置web访问路径;
+		httpConfig.setPageRoot(pageRoot);
+		//不缓存资源;
+		httpConfig.setMaxLiveTimeOfStaticRes(prop.getInt("jim.http.max.live.time"));
+		//设置j-im mvc扫描目录;
+		httpConfig.setScanPackages(scanPackages);
+		return this;
+	}
+
+	@Override
+	public ImConfigBuilder configWs(WsServerConfig wsServerConfig) {
+		
+		return this;
+	}
+
+	@Override
+	public ImConfig build() {
+		super.build();
+		this.setBindIp(prop.get("jim.bind.ip"));
+		this.setBindPort(prop.getInt("jim.port"));
+		this.setHeartbeatTimeout(prop.getLong("jim.heartbeat.timeout"));
+		this.setIsStore(prop.get("jim.store"));
+		this.setIsCluster(prop.get("jim.cluster"));
+		return conf;
+	}
+}

+ 64 - 0
jim-common/src/main/java/org/jim/common/exception/ImException.java

@@ -0,0 +1,64 @@
+package org.jim.common.exception;
+
+/**
+ * @ClassName ImException
+ * @Description Im异常类
+ * @Author WChao
+ * @Date 2019/6/13 3:28
+ * @Version 1.0
+ **/
+public class ImException extends Exception{
+
+    /**
+     * @Author WChao
+     * @Description //TODO
+     * @param []
+     * @return
+     **/
+    public ImException() {
+    }
+
+    /**
+     * @Author WChao
+     * @Description //TODO
+     * @param [message]
+     * @return
+     **/
+    public ImException(String message) {
+        super(message);
+
+    }
+
+    /**
+     * @Author WChao
+     * @Description //TODO
+     * @param [message, cause]
+     * @return
+     **/
+    public ImException(String message, Throwable cause) {
+        super(message, cause);
+
+    }
+
+    /**
+     * @Author WChao
+     * @Description //TODO
+     * @param [message, cause, enableSuppression, writableStackTrace]
+     * @return
+     **/
+    public ImException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+
+    }
+
+    /**
+     * @Author WChao
+     * @Description //TODO
+     * @param [cause]
+     * @return
+     **/
+    public ImException(Throwable cause) {
+        super(cause);
+
+    }
+}

+ 213 - 0
jim-common/src/main/java/org/jim/common/http/Cookie.java

@@ -0,0 +1,213 @@
+package org.jim.common.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 部分代码参考了: https://github.com/helyho/Voovan
+ * @author wchao
+ * 2017年5月29日 上午7:45:58
+ */
+public class Cookie {
+	private static Logger log = LoggerFactory.getLogger(Cookie.class);
+
+	/**
+	 * 通过 Map 构建一个 Cookie 对象
+	 * @param cookieMap Cookie 属性 Map
+	 * @return Cookie 对象
+	 */
+	public static Cookie buildCookie(Map<String, String> cookieMap) {
+		Cookie cookie = new Cookie();
+		for (Entry<String, String> cookieMapItem : cookieMap.entrySet()) {
+			switch (cookieMapItem.getKey().toLowerCase()) {
+			case "domain":
+				cookie.setDomain(cookieMapItem.getValue());
+				break;
+			case "path":
+				cookie.setPath(cookieMapItem.getValue());
+				break;
+			case "max-age":
+				cookie.setMaxAge(Long.parseLong(cookieMapItem.getValue()));
+				break;
+			case "secure":
+				cookie.setSecure(true);
+				break;
+			case "httponly":
+				cookie.setHttpOnly(true);
+				break;
+			case "expires":
+				cookie.setExpires(cookieMapItem.getValue());
+				break;
+			default:
+				cookie.setName(cookieMapItem.getKey());
+				try {
+					cookie.setValue(URLDecoder.decode(cookieMapItem.getValue(), HttpConst.CHARSET_NAME));
+				} catch (UnsupportedEncodingException e) {
+					log.error(e.toString(), e);
+				}
+				break;
+			}
+		}
+		return cookie;
+	}
+
+	public static Map<String, String> getEqualMap(String cookieline) {
+		Map<String, String> equalMap = new HashMap<>();
+		String[] searchedStrings = searchByRegex(cookieline, "([^ ;,]+=[^ ;,]+)");
+		for (String groupString : searchedStrings) {
+			//这里不用 split 的原因是有可能等号后的值字符串中出现等号
+			String[] equalStrings = new String[2];
+			int equalCharIndex = groupString.indexOf("=");
+			equalStrings[0] = groupString.substring(0, equalCharIndex);
+			equalStrings[1] = groupString.substring(equalCharIndex + 1, groupString.length());
+			if (equalStrings.length == 2) {
+				String key = equalStrings[0];
+				String value = equalStrings[1];
+				if (value.startsWith("\"") && value.endsWith("\"")) {
+					value = value.substring(1, value.length() - 1);
+				}
+				equalMap.put(key, value);
+			}
+		}
+		return equalMap;
+	}
+
+	public static String[] searchByRegex(String source, String regex) {
+		if (source == null) {
+			return null;
+		}
+
+		Map<Integer, Pattern> regexPattern = new HashMap<>();
+
+		Pattern pattern = null;
+		if (regexPattern.containsKey(regex.hashCode())) {
+			pattern = regexPattern.get(regex.hashCode());
+		} else {
+			pattern = Pattern.compile(regex);
+			regexPattern.put(regex.hashCode(), pattern);
+		}
+		Matcher matcher = pattern.matcher(source);
+		ArrayList<String> result = new ArrayList<>();
+		while (matcher.find()) {
+			result.add(matcher.group());
+		}
+		return result.toArray(new String[0]);
+	}
+
+	private String domain = null;
+	private String path = null;
+	private Long maxAge = null;
+
+	private String expires = null;
+	private boolean secure = false;
+
+	private boolean httpOnly = false;
+
+	private String name;
+
+	private String value;
+
+	/**
+	 *
+	 * @author wchao
+	 */
+	public Cookie() {
+	}
+
+	/**
+	 * 创建一个 Cookie
+	 * @param domain	cookie的受控域
+	 * @param name		名称
+	 * @param value		值
+	 * @param maxAge	失效时间,单位秒
+	 * @return Cookie 对象
+	 */
+	public Cookie(String domain, String name, String value, Long maxAge) {
+		setName(name);
+		setValue(value);
+		setPath("/");
+		setDomain(domain);
+		setMaxAge(maxAge);
+		setHttpOnly(false);
+	}
+
+	public String getDomain() {
+		return domain;
+	}
+
+	public String getExpires() {
+		return expires;
+	}
+
+	public Long getMaxAge() {
+		return maxAge;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getPath() {
+		return path;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public boolean isHttpOnly() {
+		return httpOnly;
+	}
+
+	public boolean isSecure() {
+		return secure;
+	}
+
+	public void setDomain(String domain) {
+		this.domain = domain;
+	}
+
+	public void setExpires(String expires) {
+		this.expires = expires;
+	}
+
+	public void setHttpOnly(boolean httpOnly) {
+		this.httpOnly = httpOnly;
+	}
+
+	public void setMaxAge(Long maxAge) {
+		this.maxAge = maxAge;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public void setPath(String path) {
+		this.path = path;
+	}
+
+	public void setSecure(boolean secure) {
+		this.secure = secure;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	@Override
+	public String toString() {
+		return (this.name != null || this.value != null ? this.name + "=" + this.value : "") + (this.domain != null ? "; Domain=" + this.domain : "")
+				+ (this.maxAge != null ? "; Max-Age=" + this.maxAge : "") + (this.path != null ? "; Path=" + this.path : " ") + (this.httpOnly ? "; httponly; " : "")
+				+ (this.secure ? "; Secure" : "");
+	}
+}

+ 12 - 0
jim-common/src/main/java/org/jim/common/http/GroupContextKey.java

@@ -0,0 +1,12 @@
+package org.jim.common.http;
+
+/**
+ * @author wchao
+ * 2017年8月18日 下午5:43:54
+ */
+public interface GroupContextKey {
+	/**
+	 * 存放HttpConfig
+	 */
+	String HTTP_SERVER_CONFIG = "TIO_HTTP_SERVER_CONFIG";
+}

+ 267 - 0
jim-common/src/main/java/org/jim/common/http/HttpConfig.java

@@ -0,0 +1,267 @@
+package org.jim.common.http;
+
+import org.jim.common.config.Config;
+import org.jim.common.http.handler.IHttpRequestHandler;
+import org.jim.common.http.listener.IHttpServerListener;
+import org.jim.common.session.id.ISessionIdGenerator;
+import org.tio.utils.cache.ICache;
+
+/**
+ * @author wchao
+ * 2017年8月15日 下午1:21:14
+ */
+public class HttpConfig extends Config{
+
+	//	private static Logger log = LoggerFactory.getLogger(HttpConfig.class);
+
+	/**
+	 * 存放HttpSession对象的cacheName
+	 */
+	public static final String SESSION_CACHE_NAME = "tio-h-s";
+
+	/**
+	 * 存放sessionId的cookie name
+	 */
+	public static final String SESSION_COOKIE_NAME = "TwIxO";
+
+	/**
+	 * session默认的超时时间,单位:秒
+	 */
+	public static final long DEFAULT_SESSION_TIMEOUT = 30 * 60;
+
+	/**
+	 * 默认的静态资源缓存时间,单位:秒
+	 */
+	public static final int MAX_LIVETIME_OF_STATICRES = 60 * 10;
+	
+	/**
+	 * 文件上传时,boundary值的最大长度
+	 */
+	public static final int MAX_LENGTH_OF_BOUNDARY = 256;
+	
+	/**
+	 * 文件上传时,头部的最大长度
+	 */
+	public static final int MAX_LENGTH_OF_MULTI_HEADER = 128;
+	
+	/**
+	 * 文件上传时,体的最大长度
+	 */
+	public static final int MAX_LENGTH_OF_MULTI_BODY = 1024 * 1024 * 20;
+
+	/**
+	 * @param args
+	 * @author wchao
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	private String serverInfo = HttpConst.SERVER_INFO;
+
+	private String charset = HttpConst.CHARSET_NAME;
+
+	private ICache sessionStore = null;
+
+	/**
+	 * 存放HttpSession对象的cacheName
+
+	 */
+	private String sessionCacheName = SESSION_CACHE_NAME;
+
+	/**
+	 * session超时时间,单位:秒
+	 */
+	private long sessionTimeout = DEFAULT_SESSION_TIMEOUT;
+
+	private String sessionCookieName = SESSION_COOKIE_NAME;
+
+	/**
+	 * 静态资源缓存时间,如果小于等于0则不缓存,单位:秒
+	 */
+	private int maxLiveTimeOfStaticRes = MAX_LIVETIME_OF_STATICRES;
+
+	private String page404 = "/404.html";
+
+	private String page500 = "/500.html";
+
+	private ISessionIdGenerator sessionIdGenerator;
+	
+	private IHttpRequestHandler httpRequestHandler;
+	
+	private IHttpServerListener httpServerListener;
+
+	/**
+	 * 示例:
+	 * 1、classpath中:page
+	 * 2、绝对路径:/page
+	 * //FileUtil.getAbsolutePath("page");//"/page";
+	 */
+	private String pageRoot = null;
+	/**
+	 * mvc扫描包路径;
+	 */
+	private String[] scanPackages = null;
+
+	
+	public HttpConfig() {}
+	
+	/**
+	 *
+	 * @author wchao
+	 */
+	public HttpConfig(Integer bindPort, Long sessionTimeout) {
+		this.bindPort = bindPort;
+		if (sessionTimeout != null) {
+			this.sessionTimeout = sessionTimeout;
+		}
+	}
+	
+	/**
+	 * @return the charset
+	 */
+	public String getCharset() {
+		return charset;
+	}
+
+	/**
+	 * @return the maxLiveTimeOfStaticRes
+	 */
+	public int getMaxLiveTimeOfStaticRes() {
+		return maxLiveTimeOfStaticRes;
+	}
+
+	public String getPage404() {
+		return page404;
+	}
+
+	public String getPage500() {
+		return page500;
+	}
+
+	/**
+	 * @return the pageRoot
+	 */
+	public String getPageRoot() {
+		return pageRoot;
+	}
+
+	/**
+	 * @return the serverInfo
+	 */
+	public String getServerInfo() {
+		return serverInfo;
+	}
+
+	/**
+	 * @return the sessionCacheName
+	 */
+	public String getSessionCacheName() {
+		return sessionCacheName;
+	}
+
+	public String getSessionCookieName() {
+		return sessionCookieName;
+	}
+
+	public ISessionIdGenerator getSessionIdGenerator() {
+		return sessionIdGenerator;
+	}
+
+	public ICache getSessionStore() {
+		return sessionStore;
+	}
+
+	public long getSessionTimeout() {
+		return sessionTimeout;
+	}
+
+	/**
+	 * @param charset the charset to set
+	 */
+	public void setCharset(String charset) {
+		this.charset = charset;
+	}
+
+	/**
+	 * @param maxLiveTimeOfStaticRes the maxLiveTimeOfStaticRes to set
+	 */
+	public void setMaxLiveTimeOfStaticRes(int maxLiveTimeOfStaticRes) {
+		this.maxLiveTimeOfStaticRes = maxLiveTimeOfStaticRes;
+	}
+
+	public void setPage404(String page404) {
+		this.page404 = page404;
+	}
+
+	public void setPage500(String page500) {
+		this.page500 = page500;
+	}
+
+	/**
+	 * 
+	 * @param pageRoot
+	 * @author wchao
+	 */
+	public void setPageRoot(String pageRoot) {
+		this.pageRoot = pageRoot;//FileUtil.getAbsolutePath(root);//"/page";;
+	}
+
+	/**
+	 * @param serverInfo the serverInfo to set
+	 */
+	public void setServerInfo(String serverInfo) {
+		this.serverInfo = serverInfo;
+	}
+
+	/**
+	 * @param sessionCacheName the sessionCacheName to set
+	 */
+	public void setSessionCacheName(String sessionCacheName) {
+		this.sessionCacheName = sessionCacheName;
+	}
+
+	public void setSessionCookieName(String sessionCookieName) {
+		this.sessionCookieName = sessionCookieName;
+	}
+
+	public void setSessionIdGenerator(ISessionIdGenerator sessionIdGenerator) {
+		this.sessionIdGenerator = sessionIdGenerator;
+	}
+
+	public void setSessionStore(ICache sessionStore) {
+		this.sessionStore = sessionStore;
+		//		this.httpSessionManager = HttpSessionManager.getInstance(sessionStore);
+	}
+
+	/**
+	 * @return the httpRequestHandler
+	 */
+	public IHttpRequestHandler getHttpRequestHandler() {
+		return httpRequestHandler;
+	}
+
+	/**
+	 * @param httpRequestHandler the httpRequestHandler to set
+	 */
+	public void setHttpRequestHandler(IHttpRequestHandler httpRequestHandler) {
+		this.httpRequestHandler = httpRequestHandler;
+	}
+
+	public String[] getScanPackages() {
+		return scanPackages;
+	}
+
+	public void setScanPackages(String[] scanPackages) {
+		this.scanPackages = scanPackages;
+	}
+
+	public IHttpServerListener getHttpServerListener() {
+		return httpServerListener;
+	}
+
+	public void setHttpServerListener(IHttpServerListener httpServerListener) {
+		this.httpServerListener = httpServerListener;
+	}
+
+}

+ 188 - 0
jim-common/src/main/java/org/jim/common/http/HttpConst.java

@@ -0,0 +1,188 @@
+package org.jim.common.http;
+
+/**
+ *
+ * @author wchao
+ *
+ */
+public interface HttpConst {
+
+	/**
+	 * 请求体的格式
+	 * @author wchao
+	 * 2017年6月28日 上午10:03:12
+	 */
+	public enum RequestBodyFormat {
+		URLENCODED, MULTIPART, TEXT
+	}
+
+	/**
+	 *         Accept-Language : zh-CN,zh;q=0.8
+	     Sec-WebSocket-Version : 13
+	  Sec-WebSocket-Extensions : permessage-deflate; client_max_window_bits
+	                   Upgrade : websocket
+	                      Host : t-io.org:9321
+	           Accept-Encoding : gzip, deflate, sdch
+	                User-Agent : Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
+	                    Origin : http://www.t-io.org:9292
+	         Sec-WebSocket-Key : kmCL2C7q9vtNSMyHpft7lw==
+	                Connection : Upgrade
+	             Cache-Control : no-cache
+	                    Pragma : no-cache
+	 *
+	 * @author wchao
+	 * 2017年5月27日 下午2:11:57
+	 */
+	public interface RequestHeaderKey {
+		String Cookie = "Cookie".toLowerCase();//Cookie: $Version=1; Skin=new;
+		String Origin = "Origin".toLowerCase(); //http://127.0.0.1
+		String Sec_WebSocket_Key = "Sec-WebSocket-Key".toLowerCase(); //2GFwqJ1Z37glm62YKKLUeA==
+		String Cache_Control = "Cache-Control".toLowerCase(); //no-cache
+		String Connection = "Connection".toLowerCase(); //Upgrade,  keep-alive
+		String User_Agent = "User-Agent".toLowerCase(); //Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3088.3 Safari/537.36
+		String Sec_WebSocket_Version = "Sec-WebSocket-Version".toLowerCase(); //13
+		String Host = "Host".toLowerCase(); //127.0.0.1:9321
+		String Pragma = "Pragma".toLowerCase(); //no-cache
+		String Accept_Encoding = "Accept-Encoding".toLowerCase(); //gzip, deflate, br
+		String Accept_Language = "Accept-Language".toLowerCase(); //zh-CN,zh;q=0.8,en;q=0.6
+		String Upgrade = "Upgrade".toLowerCase(); //websocket
+		String Sec_WebSocket_Extensions = "Sec-WebSocket-Extensions".toLowerCase(); //permessage-deflate; client_max_window_bits
+		String Content_Length = "Content-Length".toLowerCase(); //65
+		String Content_Type = "Content-Type".toLowerCase();// : 【application/x-www-form-urlencoded】【application/x-www-form-urlencoded; charset=UTF-8】【multipart/form-data; boundary=----WebKitFormBoundaryuwYcfA2AIgxqIxA0 】
+		String If_Modified_Since = "If-Modified-Since".toLowerCase(); //与Last-Modified配合
+
+		/**
+		 * 值为XMLHttpRequest则为Ajax
+		 */
+		String X_Requested_With = "X-Requested-With".toLowerCase();//XMLHttpRequest
+	}
+
+	//	Content-Type: text/html;charset:utf-8;
+
+	/**
+	 *
+	 * @author wchao
+	 * 2017年6月27日 下午8:23:58
+	 */
+	public interface RequestHeaderValue {
+		public interface Connection {
+			String keep_alive = "keep-alive".toLowerCase();
+			String Upgrade = "Upgrade".toLowerCase();
+			String close = "close".toLowerCase();
+		}
+
+		//application/x-www-form-urlencoded、multipart/form-data、text/plain
+		public interface Content_Type {
+			/**
+			 * 普通文本,一般会是json或是xml
+			 */
+			String text_plain = "text/plain".toLowerCase();
+			/**
+			 * 文件上传
+			 */
+			String multipart_form_data = "multipart/form-data".toLowerCase();
+			/**
+			 * 普通的key-value
+			 */
+			String application_x_www_form_urlencoded = "application/x-www-form-urlencoded".toLowerCase();
+		}
+	}
+
+	public interface ResponseHeaderKey {
+		//Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
+		String Set_Cookie = "Set-Cookie".toLowerCase(); //Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
+		String Content_Length = "Content-Length".toLowerCase(); //65
+
+		String Connection = "Connection".toLowerCase(); //Upgrade,  keep-alive
+		String Keep_Alive = "Keep-Alive".toLowerCase(); //Keep-Alive:timeout=20
+		String Sec_WebSocket_Accept = "Sec-WebSocket-Accept".toLowerCase();
+		String Upgrade = "Upgrade".toLowerCase();
+
+		/**
+		 * Content-Disposition: attachment;filename=FileName.txt
+		 * 文件下载
+		 */
+		String Content_disposition = "Content-disposition".toLowerCase();
+		/**
+		 * 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。
+		 * 利用gzip压缩文档能够显著地减少HTML文档的下载时间。
+		 * Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。
+		 * 因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,
+		 * 为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。
+		 */
+		String Content_Encoding = "Content-Encoding".toLowerCase();
+		/**
+		 * 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。
+		 * 由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentType。
+		 */
+		String Content_Type = "Content-Type".toLowerCase();
+		/**
+		 * 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
+		 */
+		String Date = "Date".toLowerCase();
+		/**
+		 * 应该在什么时候认为文档已经过期,从而不再缓存它?
+		 */
+		String Expires = "Expires".toLowerCase();
+		/**
+		 * 文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,
+		 * 只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。
+		 */
+		String Last_Modified = "Last-Modified".toLowerCase();
+		/**
+		 * 表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
+		 */
+		String Location = "Location".toLowerCase();
+		/**
+		 * 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
+		注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。
+		
+		注意Refresh的意义是"N秒之后刷新本页面或访问指定页面",而不是"每隔N秒刷新本页面或访问指定页面"。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。
+		
+		注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
+		 */
+		String Refresh = "Refresh".toLowerCase();
+		/**
+		 * 服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。
+		 */
+		String Server = "Server".toLowerCase();
+
+		/**
+		 *
+		 */
+		String Access_Control_Allow_Origin = "Access-Control-Allow-Origin".toLowerCase(); //value: *
+
+		/**
+		 *
+		 */
+		String Access_Control_Allow_Headers = "Access-Control-Allow-Headers".toLowerCase(); //value: x-requested-with,content-type
+
+		/**
+		 * 是否是从缓存中获取的数据,tio-httpserver特有的头部信息
+		 */
+		String tio_from_cache = "tio-from-cache";
+	}
+
+	/**
+	 *
+	 * @author wchao
+	 * 2017年6月27日 下午8:24:02
+	 */
+	public interface ResponseHeaderValue {
+		public interface Connection {
+			String keep_alive = "keep-alive".toLowerCase();
+			String Upgrade = "Upgrade".toLowerCase();
+			String close = "close".toLowerCase();
+		}
+	}
+
+	/**
+	 *
+	 */
+	String SERVER_INFO = "tio-httpserver/0.0.1";
+
+	/**
+	 * 默认规定连接到本服务器的客户端统一用utf-8
+	 */
+	String CHARSET_NAME = "utf-8";
+}

+ 42 - 0
jim-common/src/main/java/org/jim/common/http/HttpConvertPacket.java

@@ -0,0 +1,42 @@
+/**
+ * 
+ */
+package org.jim.common.http;
+
+import org.jim.common.ImConst;
+import org.jim.common.ImPacket;
+import org.jim.common.http.session.HttpSession;
+import org.jim.common.packets.Command;
+import org.jim.common.protocol.IConvertProtocolPacket;
+import org.tio.core.ChannelContext;
+
+/**
+ * HTTP协议消息转化包
+ * @author WChao
+ *
+ */
+public class HttpConvertPacket implements IConvertProtocolPacket {
+
+	/**
+	 * 转HTTP协议响应包;
+	 */
+	@Override
+	public ImPacket RespPacket(byte[] body, Command command,ChannelContext channelContext) {
+		Object sessionContext = channelContext.getAttribute();
+		if(sessionContext instanceof HttpSession){
+			HttpRequest request = (HttpRequest)channelContext.getAttribute(ImConst.HTTP_REQUEST);
+			HttpResponse response = new HttpResponse(request,request.getHttpConfig());
+			response.setBody(body, request);
+			response.setCommand(command);
+			return response;
+		}
+		return null;
+	}
+
+	@Override
+	public ImPacket ReqPacket(byte[] body, Command command,ChannelContext channelContext) {
+		
+		return null;
+	}
+
+}

+ 345 - 0
jim-common/src/main/java/org/jim/common/http/HttpMultiBodyDecoder.java

@@ -0,0 +1,345 @@
+package org.jim.common.http;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.core.ChannelContext;
+import org.tio.core.exception.AioDecodeException;
+import org.tio.core.exception.LengthOverflowException;
+import org.tio.core.utils.ByteBufferUtils;
+import org.jim.common.utils.HttpParseUtils;
+import org.tio.utils.SystemTimer;
+
+/**
+ * @author wchao
+ * 2017年7月26日 下午2:20:43
+ */
+public class HttpMultiBodyDecoder {
+	public static class Header {
+		private String contentDisposition = "form-data";
+		private String name = null;
+		private String filename = null;
+		private String contentType = null;
+
+		private Map<String, String> map = new HashMap<>();
+
+		public String getContentDisposition() {
+			return contentDisposition;
+		}
+
+		public String getContentType() {
+			return contentType;
+		}
+
+		public String getFilename() {
+			return filename;
+		}
+
+		public Map<String, String> getMap() {
+			return map;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setContentDisposition(String contentDisposition) {
+			this.contentDisposition = contentDisposition;
+		}
+
+		public void setContentType(String contentType) {
+			this.contentType = contentType;
+		}
+
+		public void setFilename(String filename) {
+			this.filename = filename;
+		}
+
+		public void setMap(Map<String, String> map) {
+			this.map = map;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+	}
+
+	/**
+	 * 【
+	 * Content-Disposition: form-data; name="uploadFile"; filename=""
+	 * Content-Type: application/octet-stream
+	 * 】
+	 *
+	 * 【
+	 * Content-Disposition: form-data; name="end"
+	 * 】
+	 * @author wchao
+	 * 2017年7月27日 上午10:18:01
+	 */
+	public static interface MultiBodyHeaderKey {
+		String Content_Disposition = "Content-Disposition".toLowerCase();
+		String Content_Type = "Content-Type".toLowerCase();
+	}
+
+	public static enum Step {
+		BOUNDARY, HEADER, BODY, END
+	}
+
+	private static Logger log = LoggerFactory.getLogger(HttpMultiBodyDecoder.class);
+
+	//    public static int processReadIndex(ByteBuffer buffer)
+	//    {
+	//        int newReaderIndex = buffer.readerIndex();
+	//        if (newReaderIndex < buffer.capacity())
+	//        {
+	//            buffer.readerIndex(newReaderIndex + 1);
+	//            return 1;
+	//        }
+	//        return 0;
+	//    }
+
+	public static void decode(HttpRequest request, RequestLine firstLine, byte[] bodyBytes, String initboundary, ChannelContext channelContext) throws AioDecodeException {
+		if (StringUtils.isBlank(initboundary)) {
+			throw new AioDecodeException("boundary is null");
+		}
+
+		long start = SystemTimer.currentTimeMillis();
+
+		ByteBuffer buffer = ByteBuffer.wrap(bodyBytes);
+		buffer.position(0);
+
+		String boundary = "--" + initboundary;
+		String endBoundary = boundary + "--";
+
+		//        int boundaryLength = boundary.getBytes().length;
+		Step step = Step.BOUNDARY;
+		//        int bufferLength = buffer.capacity();
+		try {
+			label1: while (true) {
+				if (step == Step.BOUNDARY) {
+					String line = ByteBufferUtils.readLine(buffer, request.getCharset(), HttpConfig.MAX_LENGTH_OF_BOUNDARY);
+					//                    int offset = HttpMultiBodyDecoder.processReadIndex(buffer);
+					if (boundary.equals(line)) {
+						step = Step.HEADER;
+					} else if (endBoundary.equals(line)) // 结束了
+					{
+						//                        int ss = buffer.readerIndex() + 2 - offset;
+						break;
+					} else {
+						throw new AioDecodeException("line need:" + boundary + ", but is: " + line + "");
+					}
+				}
+
+				Header multiBodyHeader = new Header();
+				if (step == Step.HEADER) {
+					List<String> lines = new ArrayList<>(2);
+					label2: while (true) {
+						String line = ByteBufferUtils.readLine(buffer, request.getCharset(), HttpConfig.MAX_LENGTH_OF_MULTI_HEADER);
+						if ("".equals(line)) {
+							break label2;
+						} else {
+							lines.add(line);
+						}
+					}
+
+					parseHeader(lines, multiBodyHeader, channelContext);
+					step = Step.BODY;
+				}
+
+				if (step == Step.BODY) {
+					Step newParseStep = parseBody(multiBodyHeader, request, buffer, boundary, endBoundary, channelContext);
+					step = newParseStep;
+
+					if (step == Step.END) {
+						break label1;
+					}
+				}
+
+			}
+		} catch (LengthOverflowException loe) {
+			throw new AioDecodeException(loe);
+		} catch (UnsupportedEncodingException e) {
+			log.error(channelContext.toString(), e);
+		} finally {
+			long end = SystemTimer.currentTimeMillis();
+			long iv = end - start;
+			log.info("解析耗时:{}ms", iv);
+		}
+
+	}
+
+	/**
+	 * 返回值不包括最后的\r\n
+	 * @param buffer
+	 * @param charset
+	 * @return
+	 * @throws UnsupportedEncodingException
+	 */
+	//	public static String getLine(ByteBuffer buffer, String charset) throws UnsupportedEncodingException {
+	//		char lastByte = 0; // 上一个字节
+	//		int initPosition = buffer.position();
+	//
+	//		while (buffer.hasRemaining()) {
+	//			char b = (char) buffer.get();
+	//
+	//			if (b == '\n') {
+	//				if (lastByte == '\r') {
+	//					int startIndex = initPosition;
+	//					int endIndex = buffer.position() - 2;
+	//					int length = endIndex - startIndex;
+	//					byte[] dst = new byte[length];
+	//
+	//					System.arraycopy(buffer.array(), startIndex, dst, 0, length);
+	//					String line = new String(dst, charset);
+	//					return line;
+	//				}
+	//			}
+	//			lastByte = b;
+	//		}
+	//		return null;
+	//	}
+
+	/**
+	 * @param args
+	 * @throws UnsupportedEncodingException
+	 * @throws LengthOverflowException 
+	 */
+	public static void main(String[] args) throws UnsupportedEncodingException, LengthOverflowException {
+		String testString = "hello\r\nddd\r\n";
+		ByteBuffer buffer = ByteBuffer.wrap(testString.getBytes());
+
+		String xString = ByteBufferUtils.readLine(buffer, "utf-8");
+		System.out.println(xString);
+		xString = ByteBufferUtils.readLine(buffer, "utf-8");
+		System.out.println(xString);
+	}
+
+	/**
+	 * 
+	 * @param header
+	 * @param request
+	 * @param buffer
+	 * @param boundary
+	 * @param endBoundary
+	 * @param channelContext
+	 * @return
+	 * @throws UnsupportedEncodingException
+	 * @throws LengthOverflowException
+	 * @author wchao
+	 */
+	public static Step parseBody(Header header, HttpRequest request, ByteBuffer buffer, String boundary, String endBoundary, ChannelContext channelContext)
+			throws UnsupportedEncodingException, LengthOverflowException {
+		int initPosition = buffer.position();
+
+		while (buffer.hasRemaining()) {
+			String line = ByteBufferUtils.readLine(buffer, request.getCharset(), HttpConfig.MAX_LENGTH_OF_MULTI_BODY);
+			boolean isEndBoundary = endBoundary.equals(line);
+			boolean isBoundary = boundary.equals(line);
+			if (isBoundary || isEndBoundary) {
+				int startIndex = initPosition;
+				int endIndex = buffer.position() - line.getBytes().length - 2 - 2;
+				int length = endIndex - startIndex;
+				byte[] dst = new byte[length];
+
+				System.arraycopy(buffer.array(), startIndex, dst, 0, length);
+				String filename = header.getFilename();
+				if (filename != null)//该字段类型是file
+				{
+					if (!"".equals(filename)) { //
+						UploadFile uploadFile = new UploadFile();
+						uploadFile.setName(filename);
+						uploadFile.setData(dst);
+						uploadFile.setSize(dst.length);
+						request.addParam(header.getName(), uploadFile);
+					}
+				} else { //该字段是普通的key-value
+					request.addParam(header.getName(), new String(dst, request.getCharset()));
+				}
+				if (isEndBoundary) {
+					return Step.END;
+				} else {
+					return Step.HEADER;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 【
+	 * Content-Disposition: form-data; name="uploadFile"; filename=""
+	 * Content-Type: application/octet-stream
+	 * 】
+	 *
+	 * 【
+	 * Content-Disposition: form-data; name="end"
+	 * 】
+	 * @param lines
+	 * @param header
+	 * @author wchao
+	 */
+	public static void parseHeader(List<String> lines, Header header, ChannelContext channelContext) throws AioDecodeException {
+		if (lines == null || lines.size() == 0) {
+			throw new AioDecodeException("multipart_form_data 格式不对,没有头部信息");
+		}
+
+		try {
+			for (String line : lines) {
+				String[] keyvalue = StringUtils.split(line, ":");
+				String key = StringUtils.lowerCase(StringUtils.trim(keyvalue[0]));//
+				String value = StringUtils.trim(keyvalue[1]);
+				header.map.put(key, value);
+			}
+
+			String contentDisposition = header.map.get(MultiBodyHeaderKey.Content_Disposition);
+			String name = HttpParseUtils.getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Disposition, "name");
+			String filename = HttpParseUtils.getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Disposition, "filename");
+			String contentType = header.map.get(MultiBodyHeaderKey.Content_Type);//.HttpParseUtils.getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Type, "filename");
+
+			header.setContentDisposition(contentDisposition);
+			header.setName(name);
+			header.setFilename(filename);
+			header.setContentType(contentType);
+
+		} catch (Exception e) {
+			log.error(channelContext.toString(), e);
+			throw new AioDecodeException(e.toString());
+		}
+
+		//		for (int i = 0; i < lines.size(); i++) {
+		//			String line = lines.get(i);
+		//			if (i == 0) {
+		//				String[] mapStrings = StringUtils.split(line, ";");
+		//				String s = mapStrings[0];//
+		//
+		//				String[] namekeyvalue = StringUtils.split(mapStrings[1], "=");
+		//				header.setName(namekeyvalue[1].substring(1, namekeyvalue[1].length() - 1));
+		//
+		//				if (mapStrings.length == 3) {
+		//					String[] finenamekeyvalue = StringUtils.split(mapStrings[2], "=");
+		//					String filename = finenamekeyvalue[1].substring(1, finenamekeyvalue[1].length() - 1);
+		//					header.setFilename(FilenameUtils.getName(filename));
+		//				}
+		//			} else if (i == 1) {
+		//				String[] map = StringUtils.split(line, ":");
+		//				String contentType = map[1].trim();//
+		//				header.setContentType(contentType);
+		//			}
+		//		}
+	}
+
+	/**
+	 *
+	 */
+	public HttpMultiBodyDecoder() {
+
+	}
+
+}

+ 74 - 0
jim-common/src/main/java/org/jim/common/http/HttpPacket.java

@@ -0,0 +1,74 @@
+package org.jim.common.http;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jim.common.ImPacket;
+
+/**
+ *
+ * @author wchao
+ *
+ */
+public class HttpPacket extends ImPacket {
+
+	//	private static Logger log = LoggerFactory.getLogger(HttpPacket.class);
+
+	private static final long serialVersionUID = 3903186670675671956L;
+
+	//	public static final int MAX_LENGTH_OF_BODY = (int) (1024 * 1024 * 5.1); //只支持多少M数据
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+
+	}
+	private String headerString;
+
+	protected Map<String, String> headers = new HashMap<>();
+
+	public HttpPacket() {
+
+	}
+
+	public void addHeader(String key, String value) {
+		headers.put(key, value);
+	}
+
+	public void addHeaders(Map<String, String> headers) {
+		if (headers != null) {
+			this.headers.putAll(headers);
+		}
+	}
+
+	public String getHeader(String key) {
+		return headers.get(key);
+	}
+
+	/**
+	 * @return the headers
+	 */
+	public Map<String, String> getHeaders() {
+		return headers;
+	}
+
+	public String getHeaderString() {
+		return headerString;
+	}
+
+	public void removeHeader(String key, String value) {
+		headers.remove(key);
+	}
+
+	/**
+	 * @param headers the headers to set
+	 */
+	public void setHeaders(Map<String, String> headers) {
+		this.headers = headers;
+	}
+
+	public void setHeaderString(String headerString) {
+		this.headerString = headerString;
+	}
+}

+ 68 - 0
jim-common/src/main/java/org/jim/common/http/HttpProtocol.java

@@ -0,0 +1,68 @@
+/**
+ * 
+ */
+package org.jim.common.http;
+
+import java.nio.ByteBuffer;
+
+import org.jim.common.ImPacket;
+import org.jim.common.ImSessionContext;
+import org.jim.common.Protocol;
+import org.jim.common.http.session.HttpSession;
+import org.jim.common.protocol.AbProtocol;
+import org.jim.common.protocol.IConvertProtocolPacket;
+import org.jim.common.utils.ImUtils;
+import org.tio.core.ChannelContext;
+
+/**
+ *
+ * Http协议校验器
+ * @author WChao
+ *
+ */
+public class HttpProtocol extends AbProtocol {
+
+	@Override
+	public String name() {
+		return Protocol.HTTP;
+	}
+
+	@Override
+	public boolean isProtocolByBuffer(ByteBuffer buffer,ChannelContext channelContext) throws Throwable {
+		ImSessionContext imSessionContext = (ImSessionContext)channelContext.getAttribute();
+		if(imSessionContext != null && imSessionContext instanceof HttpSession) {
+			return true;
+		}
+		if(buffer != null){
+			HttpRequest request = HttpRequestDecoder.decode(buffer, channelContext,false);
+			if(request.getHeaders().get(HttpConst.RequestHeaderKey.Sec_WebSocket_Key) == null)
+			{
+				channelContext.setAttribute(new HttpSession());
+				ImUtils.setClient(channelContext);
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public IConvertProtocolPacket converter() {
+		return new HttpConvertPacket();
+	}
+
+	@Override
+	public boolean isProtocol(ImPacket imPacket,ChannelContext channelContext) throws Throwable {
+		if(imPacket == null) {
+			return false;
+		}
+		if(imPacket instanceof HttpPacket){
+			Object sessionContext = channelContext.getAttribute();
+			if(sessionContext == null){
+				channelContext.setAttribute(new HttpSession());
+			}
+			return true;
+		}
+		return false;
+	}
+
+}

+ 349 - 0
jim-common/src/main/java/org/jim/common/http/HttpRequest.java

@@ -0,0 +1,349 @@
+package org.jim.common.http;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.StringUtils;
+import org.tio.core.ChannelContext;
+import org.tio.core.Node;
+import org.jim.common.http.HttpConst.RequestBodyFormat;
+import org.jim.common.http.session.HttpSession;
+
+import cn.hutool.core.util.ArrayUtil;
+/**
+ *
+ * @author wchao
+ *
+ */
+public class HttpRequest extends HttpPacket {
+
+	//	private static Logger log = LoggerFactory.getLogger(HttpRequest.class);
+
+	private static final long serialVersionUID = -3849253977016967211L;
+
+	/**
+	 * @param args
+	 *
+	 * @author wchao
+	 * 2017年2月22日 下午4:14:40
+	 *
+	 */
+	public static void main(String[] args) {
+	}
+
+	private RequestLine requestLine = null;
+	/**
+	 * 请求参数
+	 */
+	private Map<String, Object[]> params = new HashMap<>();;
+	private List<Cookie> cookies = null;
+	private Map<String, Cookie> cookieMap = null;
+	private int contentLength;
+	private String bodyString;
+	private RequestBodyFormat bodyFormat;
+	private String charset = HttpConst.CHARSET_NAME;
+	private Boolean isAjax = null;
+	private Boolean isSupportGzip = null;
+	private HttpSession httpSession;
+	private Node remote = null;
+	private ChannelContext channelContext;
+
+	private HttpConfig httpConfig;
+
+	/**
+	 *
+	 *
+	 * @author wchao
+	 * 2017年2月22日 下午4:14:40
+	 *
+	 */
+	public HttpRequest(Node remote) {
+		this.remote = remote;
+	}
+
+	public void addParam(String key, Object value) {
+		if (params == null) {
+			params = new HashMap<>();
+		}
+
+		Object[] existValue = params.get(key);
+		if (existValue != null) {
+			Object[] newExistValue = new Object[existValue.length + 1];
+			System.arraycopy(existValue, 0, newExistValue, 0, existValue.length);
+			newExistValue[newExistValue.length - 1] = value;
+			params.put(key, newExistValue);
+		} else {
+			Object[] newExistValue = new Object[] { value };
+			params.put(key, newExistValue);
+		}
+	}
+
+	/**
+	 * @return the bodyFormat
+	 */
+	public RequestBodyFormat getBodyFormat() {
+		return bodyFormat;
+	}
+
+	/**
+	 * @return the bodyString
+	 */
+	public String getBodyString() {
+		return bodyString;
+	}
+
+	/**
+	 * @return the channelContext
+	 */
+	public ChannelContext getChannelContext() {
+		return channelContext;
+	}
+
+	/**
+	 * @return the charset
+	 */
+	public String getCharset() {
+		return charset;
+	}
+
+	/**
+	 * @return the bodyLength
+	 */
+	public int getContentLength() {
+		return contentLength;
+	}
+
+	public Cookie getCookie(String cooieName) {
+		if (cookieMap == null) {
+			return null;
+		}
+		return cookieMap.get(cooieName);
+	}
+
+	/**
+	 * @return the cookieMap
+	 */
+	public Map<String, Cookie> getCookieMap() {
+		return cookieMap;
+	}
+
+	/**
+	 * @return the cookies
+	 */
+	public List<Cookie> getCookies() {
+		return cookies;
+	}
+
+	/**
+	 * @return the httpConfig
+	 */
+	public HttpConfig getHttpConfig() {
+		return httpConfig;
+	}
+
+	/**
+	 * @return the httpSession
+	 */
+	public HttpSession getHttpSession() {
+		return httpSession;
+	}
+
+	/**
+	 * @return the isAjax
+	 */
+	public Boolean getIsAjax() {
+		if (isAjax == null) {
+			String X_Requested_With = this.getHeader(HttpConst.RequestHeaderKey.X_Requested_With);
+			if (X_Requested_With != null && "XMLHttpRequest".equalsIgnoreCase(X_Requested_With)) {
+				isAjax = true;
+			} else {
+				isAjax = false;
+			}
+		}
+
+		return isAjax;
+	}
+
+	/**
+	 * @return the isSupportGzip
+	 */
+	public Boolean getIsSupportGzip() {
+		if (isSupportGzip == null) {
+			String Accept_Encoding = getHeader(HttpConst.RequestHeaderKey.Accept_Encoding);
+			if (StringUtils.isNoneBlank(Accept_Encoding)) {
+				String[] ss = StringUtils.split(Accept_Encoding, ",");
+				if (ArrayUtil.contains(ss, "gzip")) {
+					isSupportGzip = true;
+				} else {
+					isSupportGzip = false;
+				}
+			} else {
+				isSupportGzip = true;
+			}
+		}
+		return isSupportGzip;
+	}
+
+	/**
+	 * @return the params
+	 */
+	public Map<String, Object[]> getParams() {
+		return params;
+	}
+
+	public Node getRemote() {
+		return remote;
+	}
+
+	/**
+	 * @return the firstLine
+	 */
+	public RequestLine getRequestLine() {
+		return requestLine;
+	}
+
+	/**
+	 * @return
+	 * @author wchao
+	 */
+	@Override
+	public String logstr() {
+		String str = "\r\n请求ID_" + getId() + "\r\n" + getHeaderString();
+		if (null != getBodyString()) {
+			str += getBodyString();
+		}
+		return str;
+	}
+
+	public void parseCookie() {
+		String cookieLine = headers.get(HttpConst.RequestHeaderKey.Cookie);
+		if (StringUtils.isNotBlank(cookieLine)) {
+			cookies = new ArrayList<>();
+			cookieMap = new HashMap<>();
+			Map<String, String> _cookieMap = Cookie.getEqualMap(cookieLine);
+			List<Map<String, String>> cookieListMap = new ArrayList<>();
+			for (Entry<String, String> cookieMapEntry : _cookieMap.entrySet()) {
+				HashMap<String, String> cookieOneMap = new HashMap<>();
+				cookieOneMap.put(cookieMapEntry.getKey(), cookieMapEntry.getValue());
+				cookieListMap.add(cookieOneMap);
+
+				Cookie cookie = Cookie.buildCookie(cookieOneMap);
+				cookies.add(cookie);
+				cookieMap.put(cookie.getName(), cookie);
+				//log.error("{}, 收到cookie:{}", channelContext, cookie.toString());
+			}
+		}
+	}
+
+	/**
+	 * @param bodyFormat the bodyFormat to set
+	 */
+	public void setBodyFormat(RequestBodyFormat bodyFormat) {
+		this.bodyFormat = bodyFormat;
+	}
+
+	/**
+	 * @param bodyString the bodyString to set
+	 */
+	public void setBodyString(String bodyString) {
+		this.bodyString = bodyString;
+	}
+
+	/**
+	 * @param channelContext the channelContext to set
+	 */
+	public void setChannelContext(ChannelContext channelContext) {
+		this.channelContext = channelContext;
+	}
+
+	/**
+	 * @param charset the charset to set
+	 */
+	public void setCharset(String charset) {
+		this.charset = charset;
+	}
+
+	/**
+	 * @param bodyLength the bodyLength to set
+	 */
+	public void setContentLength(int contentLength) {
+		this.contentLength = contentLength;
+	}
+
+	/**
+	 * @param cookieMap the cookieMap to set
+	 */
+	public void setCookieMap(Map<String, Cookie> cookieMap) {
+		this.cookieMap = cookieMap;
+	}
+
+	/**
+	 * @param cookies the cookies to set
+	 */
+	public void setCookies(List<Cookie> cookies) {
+		this.cookies = cookies;
+	}
+
+	/**
+	 * 设置好header后,会把cookie等头部信息也设置好
+	 * @param headers the headers to set
+	 * @param channelContext
+	 */
+	@Override
+	public void setHeaders(Map<String, String> headers) {
+		this.headers = headers;
+		if (headers != null) {
+			parseCookie();
+		}
+	}
+
+	/**
+	 * @param httpConfig the httpConfig to set
+	 */
+	public void setHttpConfig(HttpConfig httpConfig) {
+		this.httpConfig = httpConfig;
+	}
+
+	/**
+	 * @param httpSession the httpSession to set
+	 */
+	public void setHttpSession(HttpSession httpSession) {
+		this.httpSession = httpSession;
+	}
+
+	/**
+	 * @param isAjax the isAjax to set
+	 */
+	public void setIsAjax(Boolean isAjax) {
+		this.isAjax = isAjax;
+	}
+
+	/**
+	 * @param isSupportGzip the isSupportGzip to set
+	 */
+	public void setIsSupportGzip(Boolean isSupportGzip) {
+		this.isSupportGzip = isSupportGzip;
+	}
+
+	/**
+	 * @param params the params to set
+	 */
+	public void setParams(Map<String, Object[]> params) {
+		this.params = params;
+	}
+
+	public void setRemote(Node remote) {
+		this.remote = remote;
+	}
+
+	/**
+	 * @param requestLine the requestLine to set
+	 */
+	public void setRequestLine(RequestLine requestLine) {
+		this.requestLine = requestLine;
+	}
+
+}

+ 363 - 0
jim-common/src/main/java/org/jim/common/http/HttpRequestDecoder.java

@@ -0,0 +1,363 @@
+package org.jim.common.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jim.common.http.HttpConst.RequestBodyFormat;
+import org.jim.common.utils.HttpParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.core.ChannelContext;
+import org.tio.core.exception.AioDecodeException;
+import org.tio.core.exception.LengthOverflowException;
+import org.tio.core.utils.ByteBufferUtils;
+
+import cn.hutool.core.util.StrUtil;
+
+/**
+ *
+ * @author WChao
+ *
+ */
+public class HttpRequestDecoder {
+	public static enum Step {
+		firstLine, header, body
+	}
+
+	private static Logger log = LoggerFactory.getLogger(HttpRequestDecoder.class);
+
+	/**
+	 * 头部,最多有多少字节
+	 */
+	public static final int MAX_LENGTH_OF_HEADER = 20480;
+
+	/**
+	 * 头部,每行最大的字节数
+	 */
+	public static final int MAX_LENGTH_OF_HEADER_LINE = 2048;
+
+	public static HttpRequest decode(ByteBuffer buffer, ChannelContext channelContext,boolean isBody) throws AioDecodeException {
+		int initPosition = buffer.position();
+		int readableLength = buffer.limit() - initPosition;
+		//		int count = 0;
+		Step step = Step.firstLine;
+		//		StringBuilder currLine = new StringBuilder();
+		Map<String, String> headers = new HashMap<>();
+		int contentLength = 0;
+		byte[] bodyBytes = null;
+		StringBuilder headerSb = new StringBuilder(512);
+		RequestLine firstLine = null;
+
+		while (buffer.hasRemaining()) {
+			String line;
+			try {
+				line = ByteBufferUtils.readLine(buffer, null, MAX_LENGTH_OF_HEADER_LINE);
+			} catch (LengthOverflowException e) {
+				throw new AioDecodeException(e);
+			}
+
+			int newPosition = buffer.position();
+			if (newPosition - initPosition > MAX_LENGTH_OF_HEADER) {
+				throw new AioDecodeException("max http header length " + MAX_LENGTH_OF_HEADER);
+			}
+
+			if (line == null) {
+				return null;
+			}
+
+			headerSb.append(line).append("\r\n");
+			//头部解析完成了
+			if ("".equals(line) && isBody) {
+				String contentLengthStr = headers.get(HttpConst.RequestHeaderKey.Content_Length);
+				if (StringUtils.isBlank(contentLengthStr)) {
+					contentLength = 0;
+				} else {
+					contentLength = Integer.parseInt(contentLengthStr);
+				}
+
+				int headerLength = (buffer.position() - initPosition);
+				//这个packet所需要的字节长度(含头部和体部)
+				int allNeedLength = headerLength + contentLength;
+				if (readableLength >= allNeedLength) {
+					step = Step.body;
+					break;
+				} else {
+					channelContext.setPacketNeededLength(allNeedLength);
+					return null;
+				}
+			} else {
+				if (step == Step.firstLine) {
+					firstLine = parseRequestLine(line, channelContext);
+					step = Step.header;
+				} else if (step == Step.header) {
+					//不解析包体的话,结束(换句话说就是只解析请求行与请求头)
+					if("".equals(line) && !isBody) {
+						break;
+					}
+					KeyValue keyValue = parseHeaderLine(line);
+					headers.put(keyValue.getKey(), keyValue.getValue());
+				}
+				continue;
+			}
+		}
+
+		if (step != Step.body && isBody) {
+			return null;
+		}
+
+		if (!headers.containsKey(HttpConst.RequestHeaderKey.Host)) {
+			throw new AioDecodeException("there is no host header");
+		}
+
+		HttpRequest httpRequest = new HttpRequest(channelContext.getClientNode());
+		httpRequest.setChannelContext(channelContext);
+		httpRequest.setHttpConfig((HttpConfig) channelContext.getGroupContext().getAttribute(GroupContextKey.HTTP_SERVER_CONFIG));
+		httpRequest.setHeaderString(headerSb.toString());
+		httpRequest.setRequestLine(firstLine);
+		httpRequest.setHeaders(headers);
+		httpRequest.setContentLength(contentLength);
+
+		parseQueryString(httpRequest, firstLine, channelContext);
+
+		if (contentLength == 0) {
+
+		} else {
+			bodyBytes = new byte[contentLength];
+			buffer.get(bodyBytes);
+			httpRequest.setBody(bodyBytes);
+			//解析消息体
+			parseBody(httpRequest, firstLine, bodyBytes, channelContext);
+		}
+		return httpRequest;
+
+	}
+
+	public static void decodeParams(Map<String, Object[]> params, String paramsStr, String charset, ChannelContext channelContext) {
+		if (StrUtil.isBlank(paramsStr)) {
+			return;
+		}
+		String[] keyValues = StringUtils.split(paramsStr, "&");
+		for (String keyValue : keyValues) {
+			String[] keyValueArr = StringUtils.split(keyValue, "=");
+			if (keyValueArr.length != 2) {
+				continue;
+			}
+
+			String key = keyValueArr[0];
+			String value = null;
+			try {
+				value = URLDecoder.decode(keyValueArr[1], charset);
+			} catch (UnsupportedEncodingException e) {
+				log.error(channelContext.toString(), e);
+			}
+
+			Object[] existValue = params.get(key);
+			if (existValue != null) {
+				String[] newExistValue = new String[existValue.length + 1];
+				System.arraycopy(existValue, 0, newExistValue, 0, existValue.length);
+				newExistValue[newExistValue.length - 1] = value;
+				params.put(key, newExistValue);
+			} else {
+				String[] newExistValue = new String[] { value };
+				params.put(key, newExistValue);
+			}
+		}
+		return;
+	}
+
+	/**
+	 * 解析消息体
+	 * @param httpRequest
+	 * @param firstLine
+	 * @param bodyBytes
+	 * @param channelContext
+	 * @throws AioDecodeException
+	 * @author WChao
+	 */
+	private static void parseBody(HttpRequest httpRequest, RequestLine firstLine, byte[] bodyBytes, ChannelContext channelContext) throws AioDecodeException {
+		parseBodyFormat(httpRequest, httpRequest.getHeaders());
+		RequestBodyFormat bodyFormat = httpRequest.getBodyFormat();
+
+		httpRequest.setBody(bodyBytes);
+
+		if (bodyFormat == RequestBodyFormat.MULTIPART) {
+			if (log.isInfoEnabled()) {
+				String bodyString = null;
+				if (bodyBytes != null && bodyBytes.length > 0) {
+					if (log.isDebugEnabled()) {
+						try {
+							bodyString = new String(bodyBytes, httpRequest.getCharset());
+							log.debug("{} multipart body string\r\n{}", channelContext, bodyString);
+						} catch (UnsupportedEncodingException e) {
+							log.error(channelContext.toString(), e);
+						}
+					}
+				}
+			}
+
+			//【multipart/form-data; boundary=----WebKitFormBoundaryuwYcfA2AIgxqIxA0】
+			String initBoundary = HttpParseUtils.getPerprotyEqualValue(httpRequest.getHeaders(), HttpConst.RequestHeaderKey.Content_Type, "boundary");
+			log.debug("{}, initBoundary:{}", channelContext, initBoundary);
+			HttpMultiBodyDecoder.decode(httpRequest, firstLine, bodyBytes, initBoundary, channelContext);
+		} else {
+			String bodyString = null;
+			if (bodyBytes != null && bodyBytes.length > 0) {
+				try {
+					bodyString = new String(bodyBytes, httpRequest.getCharset());
+					httpRequest.setBodyString(bodyString);
+					if (log.isInfoEnabled()) {
+						log.info("{} body string\r\n{}", channelContext, bodyString);
+					}
+				} catch (UnsupportedEncodingException e) {
+					log.error(channelContext.toString(), e);
+				}
+			}
+
+			if (bodyFormat == RequestBodyFormat.URLENCODED) {
+				parseUrlencoded(httpRequest, firstLine, bodyBytes, bodyString, channelContext);
+			}
+		}
+	}
+
+	/**
+	 * Content-Type : application/x-www-form-urlencoded; charset=UTF-8
+	 * Content-Type : application/x-www-form-urlencoded; charset=UTF-8
+	 * @param httpRequest
+	 * @param headers
+	 * @author WChao
+	 */
+	public static void parseBodyFormat(HttpRequest httpRequest, Map<String, String> headers) {
+		String Content_Type = StringUtils.lowerCase(headers.get(HttpConst.RequestHeaderKey.Content_Type));
+		RequestBodyFormat bodyFormat = null;
+		if (StringUtils.contains(Content_Type, HttpConst.RequestHeaderValue.Content_Type.application_x_www_form_urlencoded)) {
+			bodyFormat = RequestBodyFormat.URLENCODED;
+		} else if (StringUtils.contains(Content_Type, HttpConst.RequestHeaderValue.Content_Type.multipart_form_data)) {
+			bodyFormat = RequestBodyFormat.MULTIPART;
+		} else {
+			bodyFormat = RequestBodyFormat.TEXT;
+		}
+		httpRequest.setBodyFormat(bodyFormat);
+
+		if (StringUtils.isNotBlank(Content_Type)) {
+			String charset = HttpParseUtils.getPerprotyEqualValue(headers, HttpConst.RequestHeaderKey.Content_Type, "charset");
+			if (StringUtils.isNotBlank(charset)) {
+				httpRequest.setCharset(charset);
+			}
+		}
+	}
+
+	/**
+	 * 解析请求头的每一行
+	 * @param line
+	 * @return
+	 * @author WChao
+	 * 2017年2月23日 下午1:37:58
+	 */
+	public static KeyValue parseHeaderLine(String line) {
+		KeyValue keyValue = new KeyValue();
+		int p = line.indexOf(":");
+		if (p == -1) {
+			keyValue.setKey(line);
+			return keyValue;
+		}
+
+		String name = StringUtils.lowerCase(line.substring(0, p).trim());
+		String value = line.substring(p + 1).trim();
+
+		keyValue.setKey(name);
+		keyValue.setValue(value);
+
+		return keyValue;
+	}
+
+	/**
+	 * 解析第一行(请求行)
+	 * @param line
+	 * @param channelContext
+	 * @return
+	 *
+	 * @author WChao
+	 * 2017年2月23日 下午1:37:51
+	 *
+	 */
+	public static RequestLine parseRequestLine(String line, ChannelContext channelContext) throws AioDecodeException {
+		try {
+			int index1 = line.indexOf(' ');
+			String _method = StringUtils.upperCase(line.substring(0, index1));
+			Method method = Method.from(_method);
+			int index2 = line.indexOf(' ', index1 + 1);
+			// "/user/get?name=999"
+			String pathAndQueryStr = line.substring(index1 + 1, index2);
+			//"/user/get"
+			String path = null;
+			String queryStr = null;
+			int indexOfQuestionMark = pathAndQueryStr.indexOf("?");
+			if (indexOfQuestionMark != -1) {
+				queryStr = StringUtils.substring(pathAndQueryStr, indexOfQuestionMark + 1);
+				path = StringUtils.substring(pathAndQueryStr, 0, indexOfQuestionMark);
+			} else {
+				path = pathAndQueryStr;
+				queryStr = "";
+			}
+
+			String protocolVersion = line.substring(index2 + 1);
+			String[] pv = StringUtils.split(protocolVersion, "/");
+			String protocol = pv[0];
+			String version = pv[1];
+
+			RequestLine requestLine = new RequestLine();
+			requestLine.setMethod(method);
+			requestLine.setPath(path);
+			requestLine.setInitPath(path);
+			requestLine.setPathAndQuery(pathAndQueryStr);
+			requestLine.setQuery(queryStr);
+			requestLine.setVersion(version);
+			requestLine.setProtocol(protocol);
+			requestLine.setLine(line);
+
+			return requestLine;
+		} catch (Throwable e) {
+			log.error(channelContext.toString(), e);
+			throw new AioDecodeException(e);
+		}
+	}
+
+	/**
+	 * 解析URLENCODED格式的消息体
+	 * 形如: 【Content-Type : application/x-www-form-urlencoded; charset=UTF-8】
+	 * @author WChao
+	 */
+	private static void parseUrlencoded(HttpRequest httpRequest, RequestLine firstLine, byte[] bodyBytes, String bodyString, ChannelContext channelContext) {
+		if (StringUtils.isNotBlank(bodyString)) {
+			decodeParams(httpRequest.getParams(), bodyString, httpRequest.getCharset(), channelContext);
+		}
+	}
+
+	/**
+	 * 解析查询
+	 * @param httpRequest
+	 * @param firstLine
+	 * @param channelContext
+	 */
+	private static void parseQueryString(HttpRequest httpRequest, RequestLine firstLine, ChannelContext channelContext) {
+		String paramStr = firstLine.getQuery();
+		if (StringUtils.isNotBlank(paramStr)) {
+			decodeParams(httpRequest.getParams(), paramStr, httpRequest.getCharset(), channelContext);
+		}
+	}
+
+	/**
+	 *
+	 * @author WChao
+	 * 2017年2月22日 下午4:06:42
+	 *
+	 */
+	public HttpRequestDecoder() {
+
+	}
+
+}

+ 222 - 0
jim-common/src/main/java/org/jim/common/http/HttpResponse.java

@@ -0,0 +1,222 @@
+package org.jim.common.http;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.hutool.core.util.ZipUtil;
+/**
+ *
+ * @author WChao
+ *
+ */
+public class HttpResponse extends HttpPacket {
+	private static Logger log = LoggerFactory.getLogger(HttpResponse.class);
+
+	private static final long serialVersionUID = -3512681144230291786L;
+
+	/**
+	 * @param args
+	 *
+	 * @author WChao
+	 * 2017年2月22日 下午4:14:40
+	 *
+	 */
+	public static void main(String[] args) {
+	}
+
+	private HttpResponseStatus status = HttpResponseStatus.C200;
+
+	/**
+	 * 是否是静态资源
+	 * true: 静态资源
+	 */
+	private boolean isStaticRes = false;
+
+	private HttpRequest request = null;
+	private volatile List<Cookie> cookies = null;
+
+	//	private int contentLength;
+	//	private byte[] bodyBytes;
+	private String charset = HttpConst.CHARSET_NAME;
+
+	/**
+	 * 已经编码好的byte[]
+	 */
+	private byte[] encodedBytes = null;
+
+	/**
+	 *
+	 * @param request
+	 * @param httpConfig 可以为null
+	 * @author wchao
+	 */
+	public HttpResponse(HttpRequest request, HttpConfig httpConfig) {
+		this.request = request;
+
+		String Connection = StringUtils.lowerCase(request.getHeader(HttpConst.RequestHeaderKey.Connection));
+		RequestLine requestLine = request.getRequestLine();
+		String version = requestLine.getVersion();
+		if ("1.0".equals(version)) {
+			if (StringUtils.equals(Connection, HttpConst.RequestHeaderValue.Connection.keep_alive)) {
+				addHeader(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.keep_alive);
+				addHeader(HttpConst.ResponseHeaderKey.Keep_Alive, "timeout=10, max=20");
+			} else {
+				addHeader(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.close);
+			}
+		} else {
+			if (StringUtils.equals(Connection, HttpConst.RequestHeaderValue.Connection.close)) {
+				addHeader(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.close);
+			} else {
+				addHeader(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.keep_alive);
+				addHeader(HttpConst.ResponseHeaderKey.Keep_Alive, "timeout=10, max=20");
+			}
+		}
+		//暂时先设置为短连接...防止服务器一直不释放资源;
+		addHeader(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.close);
+		
+		if (httpConfig != null) {
+			addHeader(HttpConst.ResponseHeaderKey.Server, httpConfig.getServerInfo());
+		}
+	}
+
+	public boolean addCookie(Cookie cookie) {
+		if (cookies == null) {
+			synchronized (this) {
+				if (cookies == null) {
+					cookies = new ArrayList<>();
+				}
+			}
+		}
+		return cookies.add(cookie);
+	}
+
+	/**
+	 * @return the charset
+	 */
+	public String getCharset() {
+		return charset;
+	}
+
+	/**
+	 * @return the cookies
+	 */
+	public List<Cookie> getCookies() {
+		return cookies;
+	}
+
+	/**
+	 * @return the encodedBytes
+	 */
+	public byte[] getEncodedBytes() {
+		return encodedBytes;
+	}
+
+	/**
+	 * @return the request
+	 */
+	public HttpRequest getHttpRequestPacket() {
+		return request;
+	}
+
+	/**
+	 * @return the status
+	 */
+	@Override
+	public HttpResponseStatus getStatus() {
+		return status;
+	}
+
+	private void gzip(HttpRequest request) {
+		if (request.getIsSupportGzip()) {
+			byte[] bs = this.getBody();
+			if (bs.length >= 600) {
+				byte[] bs2 = ZipUtil.gzip(bs);
+				if (bs2.length < bs.length) {
+					this.body = bs2;
+					this.addHeader(HttpConst.ResponseHeaderKey.Content_Encoding, "gzip");
+				}
+			}
+		} else {
+			log.info("{} 竟然不支持gzip, {}", request.getChannelContext(), request.getHeader(HttpConst.RequestHeaderKey.User_Agent));
+		}
+	}
+
+	/**
+	 * @return the isStaticRes
+	 */
+	public boolean isStaticRes() {
+		return isStaticRes;
+	}
+
+	@Override
+	public String logstr() {
+		String str = null;
+		if (request != null) {
+			str = "\r\n响应: 请求ID_" + request.getId() + "  " + request.getRequestLine().getPathAndQuery();
+			str += "\r\n" + this.getHeaderString();
+		} else {
+			str = "\r\n响应\r\n" + status.getHeaderText();
+		}
+		return str;
+	}
+
+	public void setBody(byte[] body, HttpRequest request) {
+		this.body = body;
+	}
+
+	/**
+	 * @param body the body to set
+	 */
+	public void setBodyAndGzip(byte[] body, HttpRequest request) {
+		this.body = body;
+		if (body != null) {
+			gzip(request);
+		}
+	}
+
+	/**
+	 * @param charset the charset to set
+	 */
+	public void setCharset(String charset) {
+		this.charset = charset;
+	}
+
+	/**
+	 * @param cookies the cookies to set
+	 */
+	public void setCookies(List<Cookie> cookies) {
+		this.cookies = cookies;
+	}
+
+	/**
+	 * @param encodedBytes the encodedBytes to set
+	 */
+	public void setEncodedBytes(byte[] encodedBytes) {
+		this.encodedBytes = encodedBytes;
+	}
+
+	/**
+	 * @param request the request to set
+	 */
+	public void setHttpRequestPacket(HttpRequest request) {
+		this.request = request;
+	}
+
+	/**
+	 * @param isStaticRes the isStaticRes to set
+	 */
+	public void setStaticRes(boolean isStaticRes) {
+		this.isStaticRes = isStaticRes;
+	}
+
+	/**
+	 * @param status the status to set
+	 */
+	public void setStatus(HttpResponseStatus status) {
+		this.status = status;
+	}
+}

+ 147 - 0
jim-common/src/main/java/org/jim/common/http/HttpResponseEncoder.java

@@ -0,0 +1,147 @@
+package org.jim.common.http;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.core.ChannelContext;
+import org.tio.core.GroupContext;
+
+/**
+ *
+ * @author wchao
+ * 2017年8月4日 上午9:41:12
+ */
+public class HttpResponseEncoder {
+	public static enum Step {
+		firstLine, header, body
+	}
+
+	private static Logger log = LoggerFactory.getLogger(HttpResponseEncoder.class);
+
+	public static final int MAX_HEADER_LENGTH = 20480;
+
+	/**
+	 *
+	 * @param httpResponse
+	 * @param groupContext
+	 * @param channelContext
+	 * @param skipCookie true: 忽略掉cookie部分的编码
+	 * @return
+	 * @author wchao
+	 */
+	public static ByteBuffer encode(HttpResponse httpResponse, GroupContext groupContext, ChannelContext channelContext, boolean skipCookie) {
+		byte[] encodedBytes = httpResponse.getEncodedBytes();
+		if (encodedBytes != null) {
+			ByteBuffer ret = ByteBuffer.wrap(encodedBytes);
+			ret.position(ret.limit());
+			return ret;
+		}
+
+		int bodyLength = 0;
+		byte[] body = httpResponse.getBody();
+		if (body != null) {
+			bodyLength = body.length;
+		}
+
+		StringBuilder sb = new StringBuilder(256);
+
+		HttpResponseStatus httpResponseStatus = httpResponse.getStatus();
+		//		httpResponseStatus.get
+		sb.append("HTTP/1.1 ").append(httpResponseStatus.getStatus()).append(" ").append(httpResponseStatus.getDescription()).append("\r\n");
+
+		Map<String, String> headers = httpResponse.getHeaders();
+		if (headers != null && headers.size() > 0) {
+			headers.put(HttpConst.ResponseHeaderKey.Content_Length, bodyLength + "");
+			Set<Entry<String, String>> set = headers.entrySet();
+			for (Entry<String, String> entry : set) {
+				sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
+			}
+		}
+
+		if (!skipCookie) {
+			//处理cookie
+			List<Cookie> cookies = httpResponse.getCookies();
+			if (cookies != null) {
+				for (Cookie cookie : cookies) {
+					sb.append(HttpConst.ResponseHeaderKey.Set_Cookie).append(": ");
+					sb.append(cookie.toString());
+					sb.append("\r\n");
+					if (log.isInfoEnabled()) {
+						log.info("{}, set-cookie:{}", channelContext, cookie.toString());
+					}
+				}
+			}
+		}
+
+		sb.append("\r\n");
+
+		byte[] headerBytes = null;
+		try {
+			String headerString = sb.toString();
+			httpResponse.setHeaderString(headerString);
+			headerBytes = headerString.getBytes(httpResponse.getCharset());
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+
+		ByteBuffer buffer = ByteBuffer.allocate(headerBytes.length + bodyLength);
+		buffer.put(headerBytes);
+
+		if (bodyLength > 0) {
+			buffer.put(body);
+		}
+		return buffer;
+	}
+
+	/**
+	 * @param args
+	 *
+	 * @author wchao
+	 * 2017年2月22日 下午4:06:42
+	 *
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	/**
+	 * 解析请求头的每一行
+	 * @param line
+	 * @return
+	 *
+	 * @author wchao
+	 * 2017年2月23日 下午1:37:58
+	 *
+	 */
+	public static KeyValue parseHeaderLine(String line) {
+		KeyValue keyValue = new KeyValue();
+		int p = line.indexOf(":");
+		if (p == -1) {
+			keyValue.setKey(line);
+			return keyValue;
+		}
+
+		String name = line.substring(0, p).trim();
+		String value = line.substring(p + 1).trim();
+
+		keyValue.setKey(name);
+		keyValue.setValue(value);
+
+		return keyValue;
+	}
+
+	/**
+	 *
+	 *
+	 * @author wchao
+	 */
+	public HttpResponseEncoder() {
+
+	}
+
+}

File diff suppressed because it is too large
+ 216 - 0
jim-common/src/main/java/org/jim/common/http/HttpResponseStatus.java


+ 37 - 0
jim-common/src/main/java/org/jim/common/http/HttpUuid.java

@@ -0,0 +1,37 @@
+package org.jim.common.http;
+
+import org.tio.core.intf.TioUuid;
+
+/**
+ * @author wchao
+ * 2017年6月5日 上午10:44:26
+ */
+public class HttpUuid implements TioUuid {
+	//	private static Logger log = LoggerFactory.getLogger(HttpUuid.class);
+
+	//	private static java.util.concurrent.atomic.AtomicLong seq = new AtomicLong();
+
+	/**
+	 * @param args
+	 * @author wchao
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	/**
+	 *
+	 * @author wchao
+	 */
+	public HttpUuid() {
+	}
+
+	/**
+	 * @return
+	 * @author wchao
+	 */
+	@Override
+	public String uuid() {
+		return null;
+	}
+}

+ 39 - 0
jim-common/src/main/java/org/jim/common/http/KeyValue.java

@@ -0,0 +1,39 @@
+package org.jim.common.http;
+
+/**
+ *
+ * @author wchao
+ *
+ */
+public class KeyValue {
+	private String key;
+	private String value;
+
+	/**
+	 * @return the key
+	 */
+	public String getKey() {
+		return key;
+	}
+
+	/**
+	 * @return the value
+	 */
+	public String getValue() {
+		return value;
+	}
+
+	/**
+	 * @param key the key to set
+	 */
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	/**
+	 * @param value the value to set
+	 */
+	public void setValue(String value) {
+		this.value = value;
+	}
+}

+ 26 - 0
jim-common/src/main/java/org/jim/common/http/Method.java

@@ -0,0 +1,26 @@
+package org.jim.common.http;
+
+import java.util.Objects;
+
+/**
+ * @author wchao
+ * 2017年6月28日 下午2:23:16
+ */
+public enum Method {
+	GET("GET"), POST("POST"), HEAD("HEAD"), PUT("PUT"), TRACE("TRACE"), OPTIONS("OPTIONS"), PATCH("PATCH");
+	public static Method from(String method) {
+		Method[] values = Method.values();
+		for (Method v : values) {
+			if (Objects.equals(v.value, method)) {
+				return v;
+			}
+		}
+		return GET;
+	}
+
+	String value;
+
+	private Method(String value) {
+		this.value = value;
+	}
+}

File diff suppressed because it is too large
+ 1310 - 0
jim-common/src/main/java/org/jim/common/http/MimeType.java


+ 117 - 0
jim-common/src/main/java/org/jim/common/http/RequestLine.java

@@ -0,0 +1,117 @@
+package org.jim.common.http;
+
+/**
+ * @author wchao
+ * 2017年6月28日 下午2:20:32
+ */
+public class RequestLine {
+	private Method method;
+	private String path; //譬如http://www.163.com/user/get?name=tan&id=789,那些此值就是/user/get
+	private String initPath; //同path,只是path可能会被业务端修改,而这个是记录访问者访问的最原始path的
+	private String query; //譬如http://www.163.com/user/get?name=tan&id=789,那些此值就是name=tan&id=789
+	private String pathAndQuery;
+	private String protocol;
+	private String version;
+	private String line;
+
+	/**
+	 * @return the line
+	 */
+	public String getLine() {
+		return line;
+	}
+
+	/**
+	 * @return the method
+	 */
+	public Method getMethod() {
+		return method;
+	}
+
+	/**
+	 * @return the path
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	public String getPathAndQuery() {
+		return pathAndQuery;
+	}
+
+	/**
+	 * @return the query
+	 */
+	public String getQuery() {
+		return query;
+	}
+
+	/**
+	 * @return the version
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * @param line the line to set
+	 */
+	public void setLine(String line) {
+		this.line = line;
+	}
+
+	/**
+	 * @param method the method to set
+	 */
+	public void setMethod(Method method) {
+		this.method = method;
+	}
+
+	/**
+	 * @param path the path to set
+	 */
+	public void setPath(String path) {
+		this.path = path;
+	}
+
+	public void setPathAndQuery(String pathAndQuery) {
+		this.pathAndQuery = pathAndQuery;
+	}
+
+	/**
+	 * @param query the query to set
+	 */
+	public void setQuery(String query) {
+		this.query = query;
+	}
+
+	/**
+	 * @param version the version to set
+	 */
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	/**
+	 * @return the protocol
+	 */
+	public String getProtocol() {
+		return protocol;
+	}
+
+	/**
+	 * @param protocol the protocol to set
+	 */
+	public void setProtocol(String protocol) {
+		this.protocol = protocol;
+	}
+
+	public String getInitPath() {
+		return initPath;
+	}
+
+	public void setInitPath(String initPath) {
+		this.initPath = initPath;
+	}
+	
+}

+ 62 - 0
jim-common/src/main/java/org/jim/common/http/UploadFile.java

@@ -0,0 +1,62 @@
+package org.jim.common.http;
+
+/**
+ *
+ * @author wchao
+ * 2017年7月26日 下午3:12:56
+ */
+public class UploadFile {
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+
+	}
+
+	private String name = null;
+	private int size = -1;
+
+	private byte[] data = null;
+	//    private File file = null;
+
+	/**
+	 *
+	 */
+	public UploadFile() {
+
+	}
+
+	public byte[] getData() {
+		return data;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setData(byte[] data) {
+		this.data = data;
+	}
+
+	//    public File getFile()
+	//    {
+	//        return file;
+	//    }
+	//
+	//    public void setFile(File file)
+	//    {
+	//        this.file = file;
+	//    }
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+}

+ 49 - 0
jim-common/src/main/java/org/jim/common/http/handler/IHttpRequestHandler.java

@@ -0,0 +1,49 @@
+package org.jim.common.http.handler;
+
+import org.jim.common.http.HttpRequest;
+import org.jim.common.http.HttpResponse;
+import org.jim.common.http.RequestLine;
+
+/**
+ *
+ * @author wchao
+ *
+ */
+public interface IHttpRequestHandler {
+	/**
+	 *
+	 * @param packet
+	 * @param requestLine
+	 * @return
+	 * @throws Exception
+	 * @author wchao
+	 */
+	public HttpResponse handler(HttpRequest packet, RequestLine requestLine) throws Exception;
+
+	/**
+	 *
+	 * @param request
+	 * @param requestLine
+	 * @param channelContext
+	 * @return
+	 * @author wchao
+	 */
+	public HttpResponse resp404(HttpRequest request, RequestLine requestLine);
+
+	/**
+	 *
+	 * @param request
+	 * @param requestLine
+	 * @param throwable
+	 * @return
+	 * @author wchao
+	 */
+	public HttpResponse resp500(HttpRequest request, RequestLine requestLine, java.lang.Throwable throwable);
+	
+	/**
+	 * 清空静态资源缓存,如果没有缓存,可以不处理
+	 * @param request
+	 * @author: wchao
+	 */
+	public void clearStaticResCache(HttpRequest request);
+}

+ 37 - 0
jim-common/src/main/java/org/jim/common/http/listener/IHttpServerListener.java

@@ -0,0 +1,37 @@
+package org.jim.common.http.listener;
+
+import org.jim.common.http.HttpRequest;
+import org.jim.common.http.HttpResponse;
+import org.jim.common.http.RequestLine;
+/**
+ * @author wchao
+ * 2017年7月25日 下午2:16:06
+ */
+public interface IHttpServerListener {
+
+	/**
+	 * 在执行org.tio.http.server.handler.IHttpRequestHandler.handler(HttpRequestPacket, RequestLine, ChannelContext<HttpSessionContext, HttpPacket, Object>)后会调用此方法,业务层可以统一在这里给HttpResponsePacket作一些修饰
+	 * @param packet
+	 * @param requestLine
+	 * @param channelContext
+	 * @param httpResponse
+	 * @return
+	 * @throws Exception
+	 * @author wchao
+	 */
+	public void doAfterHandler(HttpRequest packet, RequestLine requestLine, HttpResponse httpResponse) throws Exception;
+
+	/**
+	 * 在执行org.tio.http.server.handler.IHttpRequestHandler.handler(HttpRequestPacket, RequestLine, ChannelContext<HttpSessionContext, HttpPacket, Object>)前会先调用这个方法<br>
+	 * 如果返回了HttpResponsePacket对象,则后续都不再执行,表示调用栈就此结束<br>
+	 * @param packet
+	 * @param requestLine
+	 * @param channelContext
+	 * @param httpResponseFromCache 从缓存中获取到的HttpResponse对象
+	 * @return
+	 * @throws Exception
+	 * @author wchao
+	 */
+	public HttpResponse doBeforeHandler(HttpRequest packet, RequestLine requestLine, HttpResponse httpResponseFromCache) throws Exception;
+
+}

+ 83 - 0
jim-common/src/main/java/org/jim/common/http/session/HttpSession.java

@@ -0,0 +1,83 @@
+package org.jim.common.http.session;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jim.common.ImSessionContext;
+import org.jim.common.http.HttpConfig;
+
+/**
+ *
+ * @author wchao
+ * 2017年8月5日 上午10:16:26
+ */
+public class HttpSession extends ImSessionContext implements java.io.Serializable {
+
+	private static final long serialVersionUID = 6077020620501316538L;
+
+	private Map<String, Serializable> data = new ConcurrentHashMap<>();
+
+	private String id = null;
+
+	public HttpSession() {
+	}
+
+	/**
+	 * @author wchao
+	 */
+	public HttpSession(String id) {
+		this.id = id;
+	}
+
+	/**
+	 * 清空所有属性
+	 * @param httpConfig
+	 * @author wchao
+	 */
+	public void clear(HttpConfig httpConfig) {
+		data.clear();
+		httpConfig.getSessionStore().put(id, this);
+	}
+
+	/**
+	 * 获取会话属性
+	 * @param key
+	 * @return
+	 * @author wchao
+	 */
+	public Object getAttribute(String key) {
+		return data.get(key);
+	}
+
+	public Map<String, Serializable> getData() {
+		return data;
+	}
+
+	/**
+	 *
+	 * @param key
+	 * @param httpConfig
+	 * @author wchao
+	 */
+	public void removeAttribute(String key, HttpConfig httpConfig) {
+		data.remove(key);
+		httpConfig.getSessionStore().put(id, this);
+	}
+
+	/**
+	 * 设置会话属性
+	 * @param key
+	 * @param value
+	 * @param httpConfig
+	 * @author wchao
+	 */
+	public void setAttribute(String key, Serializable value, HttpConfig httpConfig) {
+		data.put(key, value);
+		httpConfig.getSessionStore().put(id, this);
+	}
+
+	public void setData(Map<String, Serializable> data) {
+		this.data = data;
+	}
+}

+ 45 - 0
jim-common/src/main/java/org/jim/common/jim.properties

@@ -0,0 +1,45 @@
+##J-IM基础配置
+#ip
+jim.bind.ip = 127.0.0.1
+#端口
+jim.port = 8888
+#心跳超时时长
+jim.heartbeat.timeout = 0
+#是否开启消息持久化(on:开启,off:不开启)
+jim.store = off
+#是否开启集群(on:开启,off:不开启)
+jim.cluster = off
+#是否开启SSL(on:开启,off:不开启)
+jim.ssl = off
+#JKS证书地址
+jim.key.store.path = classpath:ssl/keystore.jks
+#JKS证书密码
+jim.key.store.pwd = 214323428310224
+
+##http协议 配置
+#html/css/js等的根目录,支持classpath:也支持绝对路径
+jim.http.page = classpath:page
+#http mvc扫描包路径
+jim.http.scan.packages =
+#http资源缓存时长
+jim.http.max.live.time = 0
+
+##ws协议配置
+
+
+##Redis相关配置
+#连接池连接不够用时,重试获取连接次数
+jim.redis.retrynum = 100
+#可用连接实例的最大数目,默认值为8;
+jim.redis.maxactive = -1
+#控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
+jim.redis.maxidle = 20
+#等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。
+jim.redis.maxwait = 5000
+jim.redis.timeout = 2000
+#redis所在机器ip
+jim.redis.host = 127.0.0.1
+#redis端口号
+jim.redis.port = 6379
+#redis密码
+jim.redis.auth = 

+ 23 - 0
jim-common/src/main/java/org/jim/common/listener/AbstractImBindListener.java

@@ -0,0 +1,23 @@
+/**
+ * 
+ */
+package org.jim.common.listener;
+
+import org.jim.common.ImConst;
+import org.jim.common.ImConfig;
+/**
+ * @author WChao
+ * 2018/08/26
+ */
+public abstract class AbstractImBindListener implements ImBindListener,ImConst {
+	
+	protected ImConfig imConfig;
+
+	public ImConfig getImConfig() {
+		return imConfig;
+	}
+
+	public void setImConfig(ImConfig imConfig) {
+		this.imConfig = imConfig;
+	}
+}

+ 48 - 0
jim-common/src/main/java/org/jim/common/listener/ImBindListener.java

@@ -0,0 +1,48 @@
+package org.jim.common.listener;
+
+import org.tio.core.ChannelContext;
+
+/**
+ * IM绑定用户及群组监听器;
+ * @author WChao
+ * @date 2018年4月8日 下午4:09:14
+ */
+public interface ImBindListener {
+	/**
+	 * 绑定群组后回调该方法
+	 * @param channelContext
+	 * @param group
+	 * @throws Exception
+	 */
+	void onAfterGroupBind(ChannelContext channelContext, String group) throws Exception;
+
+	/**
+	 * 解绑群组后回调该方法
+	 * @param channelContext
+	 * @param group
+	 * @throws Exception
+	 */
+	void onAfterGroupUnbind(ChannelContext channelContext, String group) throws Exception;
+	/**
+	 * 绑定用户后回调该方法
+	 * @param channelContext
+	 * @param userId
+	 * @throws Exception
+	 */
+	void onAfterUserBind(ChannelContext channelContext, String userId) throws Exception;
+
+	/**
+	 * 解绑用户后回调该方法
+	 * @param channelContext
+	 * @param userId
+	 * @throws Exception
+	 */
+	void onAfterUserUnbind(ChannelContext channelContext, String userId) throws Exception;
+	/**
+	 * 更新用户终端协议类型及在线状态;
+	 * @param channelContext
+	 * @param terminal(ws、tcp、http、android、ios等)
+	 * @param status(online、offline)
+	 */
+    void initUserTerminal(ChannelContext channelContext , String terminal , String status);
+}

+ 23 - 0
jim-common/src/main/java/org/jim/common/message/AbstractMessageHelper.java

@@ -0,0 +1,23 @@
+/**
+ * 
+ */
+package org.jim.common.message;
+
+import org.jim.common.ImConst;
+import org.jim.common.ImConfig;
+
+/**
+ * @author HP
+ *
+ */
+public abstract class AbstractMessageHelper implements MessageHelper,ImConst {
+	protected ImConfig imConfig;
+
+	public ImConfig getImConfig() {
+		return imConfig;
+	}
+
+	public void setImConfig(ImConfig imConfig) {
+		this.imConfig = imConfig;
+	}
+}

+ 136 - 0
jim-common/src/main/java/org/jim/common/message/MessageHelper.java

@@ -0,0 +1,136 @@
+package org.jim.common.message;
+
+import org.jim.common.listener.ImBindListener;
+import org.jim.common.packets.ChatBody;
+import org.jim.common.packets.Group;
+import org.jim.common.packets.User;
+import org.jim.common.packets.UserMessageData;
+
+import java.util.List;
+/**
+ * @author WChao
+ * @date 2018年4月9日 下午4:31:51
+ */
+public interface MessageHelper {
+	/**
+	 * 获取im群组、人员绑定监听器;
+	 * @return
+	 */
+	public ImBindListener getBindListener();
+	/**
+	 * 判断用户是否在线
+	 * @param userId
+	 * @return
+	 */
+	public boolean isOnline(String userId);
+	/**
+	 * 获取群组所有成员信息(根据type区分在线还是离线)
+	 * @param group_id
+	 * @param type(0:所有在线用户,1:所有离线用户,2:所有用户[在线+离线])
+	 * @return
+	 */
+	public Group getGroupUsers(String group_id,Integer type);
+	/**
+	 * 获取群组所有成员信息(在线+离线)
+	 * @param user_id
+	 * @param type(0:所有在线用户,1:所有离线用户,2:所有用户[在线+离线])
+	 * @return
+	 */
+	public List<Group> getAllGroupUsers(String user_id,Integer type);
+	/**
+	 * 获取好友分组所有成员信息
+	 * @param user_id
+	 * @param friend_group_id
+	 * @param type(0:所有在线用户,1:所有离线用户,2:所有用户[在线+离线])
+	 * @return
+	 */
+	public Group getFriendUsers(String user_id , String friend_group_id,Integer type);
+	/**
+	 * 获取好友分组所有成员信息
+	 * @param user_id
+	 * @param type(0:所有在线用户,1:所有离线用户,2:所有用户[在线+离线])
+	 * @return
+	 */
+	public List<Group> getAllFriendUsers(String user_id,Integer type);
+	/**
+	 * 根据在线类型获取用户信息;
+	 * @param userid
+	 * @param type(0:所有在线用户,1:所有离线用户,2:所有用户[在线+离线])
+	 * @return
+	 */
+	public User getUserByType(String userid,Integer type);
+	/**
+	 * 添加群组成员
+	 * @param userid
+	 * @param group_id
+	 */
+	public void addGroupUser(String userid,String group_id);
+	/**
+	 * 获取群组所有成员;
+	 * @param group_id
+	 * @return
+	 */
+	public List<String> getGroupUsers(String group_id);
+	/**
+	 * 获取用户拥有的群组;
+	 * @param user_id
+	 * @return
+	 */
+	public List<String> getGroups(String user_id);
+	/**
+	 * 消息持久化写入
+	 * @param timelineTable
+	 * @param timelineId
+	 * @param chatBody
+	 */
+	public void writeMessage(String timelineTable , String timelineId , ChatBody chatBody);
+	/**
+	 * 移除群组用户
+	 * @param userId
+	 * @param group_id
+	 */
+	public void removeGroupUser(String userId,String group_id);
+	/**
+	 * 获取与指定用户离线消息;
+	 * @param userId
+	 * @param fromUserId
+	 * @return
+	 */
+	public UserMessageData getFriendsOfflineMessage(String userId,String fromUserId);
+	/**
+	 * 获取与所有用户离线消息;
+	 * @param userId
+	 * @return
+	 */
+	public UserMessageData getFriendsOfflineMessage(String userId);
+	/**
+	 * 获取用户指定群组离线消息;
+	 * @param userId
+	 * @param groupId
+	 * @return
+	 */
+	public UserMessageData getGroupOfflineMessage(String userId,String groupId);
+	/**
+	 * 获取与指定用户历史消息;
+	 * @param userId
+	 * @param fromUserId
+	 * @param beginTime 消息区间开始时间
+	 * @param endTime 消息区间结束时间
+	 * @param offset 分页偏移量
+	 * @param count 数量
+	 * @return
+	 */
+	public UserMessageData getFriendHistoryMessage(String userId, String fromUserId,Double beginTime,Double endTime,Integer offset,Integer count);
+	
+	/**
+	 * 获取与指定群组历史消息;
+	 * @param userId
+	 * @param groupid
+	 * @param beginTime 消息区间开始时间
+	 * @param endTime 消息区间结束时间
+	 * @param offset 分页偏移量
+	 * @param count 数量
+	 * @return
+	 */
+	public UserMessageData getGroupHistoryMessage(String userId, String groupid,Double beginTime,Double endTime,Integer offset,Integer count);
+}

+ 24 - 0
jim-common/src/main/java/org/jim/common/packets/AuthReqBody.java

@@ -0,0 +1,24 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年9月12日 下午2:49:49
+ */
+public class AuthReqBody extends Message {
+	
+	private static final long serialVersionUID = -5687459633884615894L;
+	private String token;//token验证;
+
+	public String getToken() {
+		return token;
+	}
+
+	public void setToken(String token) {
+		this.token = token;
+	}
+	
+}

+ 24 - 0
jim-common/src/main/java/org/jim/common/packets/AuthRespBody.java

@@ -0,0 +1,24 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年9月12日 下午2:50:23
+ */
+public class AuthRespBody extends Message {
+    
+	private static final long serialVersionUID = -2985356076555121875L;
+	private String token;
+
+	public String getToken() {
+		return token;
+	}
+
+	public void setToken(String token) {
+		this.token = token;
+	}
+	
+}

+ 165 - 0
jim-common/src/main/java/org/jim/common/packets/ChatBody.java

@@ -0,0 +1,165 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年7月26日 上午11:34:44
+ */
+public class ChatBody extends Message {
+	
+	private static final long serialVersionUID = 5731474214655476286L;
+	/**
+	 * 发送用户id;
+	 */
+	private String from;
+	/**
+	 * 目标用户id;
+	 */
+	private String to;
+	/**
+	 * 消息类型;(如:0:text、1:image、2:voice、3:vedio、4:music、5:news)
+	 */
+	private Integer msgType;
+	/**
+	 * 聊天类型;(如公聊、私聊)
+	 */
+	private Integer chatType;
+	/**
+	 * 消息内容;
+	 */
+	private String content;
+	/**
+	 * 消息发到哪个群组;
+	 */
+	private String group_id;
+	
+	private ChatBody(){}
+	
+	private ChatBody(String id , String from , String to , Integer msgType , Integer chatType , String content , String group_id , Integer cmd , Long createTime , JSONObject extras){
+		this.id = id;
+		this.from = from ;
+		this.to = to;
+		this.msgType = msgType;
+		this.chatType = chatType;
+		this.content = content;
+		this.group_id = group_id;
+		this.cmd = cmd;
+		this.createTime = createTime;
+		this.extras = extras;
+	}
+	
+	public static ChatBody.Builder newBuilder(){
+		return new ChatBody.Builder();
+	}
+	public String getFrom() {
+		return from;
+	}
+	public ChatBody setFrom(String from) {
+		this.from = from;
+		return this;
+	}
+	public String getTo() {
+		return to;
+	}
+	public ChatBody setTo(String to) {
+		this.to = to;
+		return this;
+	}
+	
+	public Integer getMsgType() {
+		return msgType;
+	}
+	public ChatBody setMsgType(Integer msgType) {
+		this.msgType = msgType;
+		return this;
+	}
+	public String getContent() {
+		return content;
+	}
+	public ChatBody setContent(String content) {
+		this.content = content;
+		return this;
+	}
+	
+	public String getGroup_id() {
+		return group_id;
+	}
+	public ChatBody setGroup_id(String group_id) {
+		this.group_id = group_id;
+		return this;
+	}
+	public Integer getChatType() {
+		return chatType;
+	}
+	public ChatBody setChatType(Integer chatType) {
+		this.chatType = chatType;
+		return this;
+	}
+	
+	public static class Builder extends Message.Builder<ChatBody,ChatBody.Builder>{
+		/**
+		 * 来自user_id;
+		 */
+		private String from;
+		/**
+		 * 目标user_id;
+		 */
+		private String to;
+		/**
+		 * 消息类型;(如:0:text、1:image、2:voice、3:vedio、4:music、5:news)
+		 */
+		private Integer msgType;
+		/**
+		 * 聊天类型;(如公聊、私聊)
+		 */
+		private Integer chatType;
+		/**
+		 * 消息内容;
+		 */
+		private String content;
+		/**
+		 * 消息发到哪个群组;
+		 */
+		private String group_id;
+		
+		public Builder(){};
+		
+		public Builder setFrom(String from) {
+			this.from = from;
+			return this;
+		}
+		public Builder setTo(String to) {
+			this.to = to;
+			return this;
+		}
+		public Builder setMsgType(Integer msgType) {
+			this.msgType = msgType;
+			return this;
+		}
+		public Builder setChatType(Integer chatType) {
+			this.chatType = chatType;
+			return this;
+		}
+		public Builder setContent(String content) {
+			this.content = content;
+			return this;
+		}
+		public Builder setGroup_id(String group_id) {
+			this.group_id = group_id;
+			return this;
+		}
+		@Override
+		protected Builder getThis() {
+			return this;
+		}
+		@Override
+		public ChatBody build(){
+			return new ChatBody(this.id , this.from , this.to , this.msgType , this.chatType , this.content , this.group_id ,this.cmd , this.createTime , this.extras);
+		}
+	}
+}

+ 59 - 0
jim-common/src/main/java/org/jim/common/packets/ChatType.java

@@ -0,0 +1,59 @@
+package org.jim.common.packets;
+
+/**
+ * <pre>
+ **
+ * 聊天类型
+ * </pre>
+ */
+public enum ChatType{
+  /**
+   * <pre>
+   *未知
+   * </pre>
+   *
+   * <code>CHAT_TYPE_UNKNOW = 0;</code>
+   */
+  CHAT_TYPE_UNKNOW(0),
+  /**
+   * <pre>
+   *公聊
+   * </pre>
+   *
+   * <code>CHAT_TYPE_PUBLIC = 1;</code>
+   */
+  CHAT_TYPE_PUBLIC(1),
+  /**
+   * <pre>
+   *私聊
+   * </pre>
+   *
+   * <code>CHAT_TYPE_PRIVATE = 2;</code>
+   */
+  CHAT_TYPE_PRIVATE(2),
+  ;
+
+  public final int getNumber() {
+    return value;
+  }
+
+  public static ChatType valueOf(int value) {
+    return forNumber(value);
+  }
+
+  public static ChatType forNumber(int value) {
+    switch (value) {
+      case 0: return CHAT_TYPE_UNKNOW;
+      case 1: return CHAT_TYPE_PUBLIC;
+      case 2: return CHAT_TYPE_PRIVATE;
+      default: return null;
+    }
+  }
+
+  private final int value;
+
+  private ChatType(int value) {
+    this.value = value;
+  }
+}
+

+ 49 - 0
jim-common/src/main/java/org/jim/common/packets/Client.java

@@ -0,0 +1,49 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年7月26日 下午3:11:55
+ */
+public class Client extends Message{
+	
+	private static final long serialVersionUID = 6196600593975727155L;
+	private String ip; //客户端ip
+	private int port; //客户端port
+	private User user; //如果没登录过,则为null
+	private String region;  //地区
+	private String useragent;  //浏览器信息
+	public String getIp() {
+		return ip;
+	}
+	public void setIp(String ip) {
+		this.ip = ip;
+	}
+	public int getPort() {
+		return port;
+	}
+	public void setPort(int port) {
+		this.port = port;
+	}
+	public User getUser() {
+		return user;
+	}
+	public void setUser(User user) {
+		this.user = user;
+	}
+	public String getRegion() {
+		return region;
+	}
+	public void setRegion(String region) {
+		this.region = region;
+	}
+	public String getUseragent() {
+		return useragent;
+	}
+	public void setUseragent(String useragent) {
+		this.useragent = useragent;
+	}
+}

+ 21 - 0
jim-common/src/main/java/org/jim/common/packets/CloseReqBody.java

@@ -0,0 +1,21 @@
+package org.jim.common.packets;
+
+/**
+ * @author WChao
+ * @date 2018年4月13日 下午4:20:40
+ */
+public class CloseReqBody extends Message {
+
+	private static final long serialVersionUID = 771895783302296339L;
+	public CloseReqBody(){};
+	public CloseReqBody(String userid){
+		this.userid = userid;
+	}
+	private String userid;//用户id;
+	public String getUserid() {
+		return userid;
+	}
+	public void setUserid(String userid) {
+		this.userid = userid;
+	}
+}

+ 199 - 0
jim-common/src/main/java/org/jim/common/packets/Command.java

@@ -0,0 +1,199 @@
+package org.jim.common.packets;
+
+import org.jim.common.utils.DynamicEnumUtil;
+
+public enum Command{
+  /**
+   * <code>COMMAND_UNKNOW = 0;</code>
+   */
+  COMMAND_UNKNOW(0),
+  /**
+   * <pre>
+   *握手请求,含http的websocket握手请求
+   * </pre>
+   *
+   * <code>COMMAND_HANDSHAKE_REQ = 1;</code>
+   */
+  COMMAND_HANDSHAKE_REQ(1),
+  /**
+   * <pre>
+   *握手响应,含http的websocket握手响应
+   * </pre>
+   *
+   * <code>COMMAND_HANDSHAKE_RESP = 2;</code>
+   */
+  COMMAND_HANDSHAKE_RESP(2),
+  /**
+   * <pre>
+   *鉴权请求
+   * </pre>
+   *
+   * <code>COMMAND_AUTH_REQ = 3;</code>
+   */
+  COMMAND_AUTH_REQ(3),
+  /**
+   * <pre>
+   * 鉴权响应
+   * </pre>
+   *
+   * <code>COMMAND_AUTH_RESP = 4;</code>
+   */
+  COMMAND_AUTH_RESP(4),
+  /**
+   * <pre>
+   *登录请求
+   * </pre>
+   *
+   * <code>COMMAND_LOGIN_REQ = 5;</code>
+   */
+  COMMAND_LOGIN_REQ(5),
+  /**
+   * <pre>
+   *登录响应
+   * </pre>
+   *
+   * <code>COMMAND_LOGIN_RESP = 6;</code>
+   */
+  COMMAND_LOGIN_RESP(6),
+  /**
+   * <pre>
+   *申请进入群组
+   * </pre>
+   *
+   * <code>COMMAND_JOIN_GROUP_REQ = 7;</code>
+   */
+  COMMAND_JOIN_GROUP_REQ(7),
+  /**
+   * <pre>
+   *申请进入群组响应
+   * </pre>
+   *
+   * <code>COMMAND_JOIN_GROUP_RESP = 8;</code>
+   */
+  COMMAND_JOIN_GROUP_RESP(8),
+  /**
+   * <pre>
+   *进入群组通知
+   * </pre>
+   *
+   * <code>COMMAND_JOIN_GROUP_NOTIFY_RESP = 9;</code>
+   */
+  COMMAND_JOIN_GROUP_NOTIFY_RESP(9),
+  /**
+   * <pre>
+   *退出群组通知
+   * </pre>
+   *
+   * <code>COMMAND_EXIT_GROUP_NOTIFY_RESP = 10;</code>
+   */
+  COMMAND_EXIT_GROUP_NOTIFY_RESP(10),
+  /**
+   * <pre>
+   *聊天请求
+   * </pre>
+   *
+   * <code>COMMAND_CHAT_REQ = 11;</code>
+   */
+  COMMAND_CHAT_REQ(11),
+  /**
+   * <pre>
+   *聊天响应
+   * </pre>
+   *
+   * <code>COMMAND_CHAT_RESP = 12;</code>
+   */
+  COMMAND_CHAT_RESP(12),
+  /**
+   * <pre>
+   *心跳请求
+   * </pre>
+   *
+   * <code>COMMAND_HEARTBEAT_REQ = 13;</code>
+   */
+  COMMAND_HEARTBEAT_REQ(13),
+  /**
+   * <pre>
+   *关闭请求
+   * </pre>
+   *
+   * <code>COMMAND_CLOSE_REQ = 14;</code>
+   */
+  COMMAND_CLOSE_REQ(14),
+  /**
+   * <pre>
+   *发出撤消消息指令(管理员可以撤消所有人的消息,自己可以撤消自己的消息)
+   * </pre>
+   *
+   * <code>COMMAND_CANCEL_MSG_REQ = 15;</code>
+   */
+  COMMAND_CANCEL_MSG_REQ(15),
+  /**
+   * <pre>
+   *收到撤消消息指令
+   * </pre>
+   *
+   * <code>COMMAND_CANCEL_MSG_RESP = 16;</code>
+   */
+  COMMAND_CANCEL_MSG_RESP(16),
+  /**
+   * <pre>
+   *获取用户信息;
+   * </pre>
+   *
+   * <code>COMMAND_GET_USER_REQ = 17;</code>
+   */
+  COMMAND_GET_USER_REQ(17),
+  /**
+   * <pre>
+   *获取用户信息响应;
+   * </pre>
+   *
+   * <code>COMMAND_GET_USER_RESP = 18;</code>
+   */
+  COMMAND_GET_USER_RESP(18),
+  /**
+   * <pre>
+   * 获取聊天消息;
+   * </pre>
+   *
+   * <code>COMMAND_GET_MESSAGE_REQ = 19;</code>
+   */
+  COMMAND_GET_MESSAGE_REQ(19),
+  /**
+   * <pre>
+   * 获取聊天消息响应;
+   * </pre>
+   *
+   * <code>COMMAND_GET_MESSAGE_RESP = 20;</code>
+   */
+  COMMAND_GET_MESSAGE_RESP(20),
+  ;
+
+  public final int getNumber() {
+    return value;
+  }
+
+  public static Command valueOf(int value) {
+    return forNumber(value);
+  }
+
+  public static Command forNumber(int value) {
+	  for(Command command : Command.values()){
+	   	   if(command.getNumber() == value){
+	   		   return command;
+	   	   }
+      }
+	  return null;
+  }
+
+  public static Command addAndGet(String name , int value){
+	  return DynamicEnumUtil.addEnum(Command.class, name,new Class[]{int.class}, new Object[]{value});
+  }
+  
+  private final int value;
+
+  private Command(int value) {
+    this.value = value;
+  }
+}
+

+ 64 - 0
jim-common/src/main/java/org/jim/common/packets/DeviceType.java

@@ -0,0 +1,64 @@
+package org.jim.common.packets;
+
+/**
+ * <pre>
+ *设备类型
+ * </pre>
+ * enum {@code DeviceType}
+ */
+public enum DeviceType{
+  /**
+   * <code>DEVICE_TYPE_UNKNOW = 0;</code>
+   */
+  DEVICE_TYPE_UNKNOW(0),
+  /**
+   * <pre>
+   *PC
+   * </pre>
+   *
+   * <code>DEVICE_TYPE_PC = 1;</code>
+   */
+  DEVICE_TYPE_PC(1),
+  /**
+   * <pre>
+   *安卓
+   * </pre>
+   *
+   * <code>DEVICE_TYPE_ANDROID = 2;</code>
+   */
+  DEVICE_TYPE_ANDROID(2),
+  /**
+   * <pre>
+   *IOS
+   * </pre>
+   *
+   * <code>DEVICE_TYPE_IOS = 3;</code>
+   */
+  DEVICE_TYPE_IOS(3),
+  ;
+
+  public final int getNumber() {
+    return value;
+  }
+
+  public static DeviceType valueOf(int value) {
+    return forNumber(value);
+  }
+
+  public static DeviceType forNumber(int value) {
+    switch (value) {
+      case 0: return DEVICE_TYPE_UNKNOW;
+      case 1: return DEVICE_TYPE_PC;
+      case 2: return DEVICE_TYPE_ANDROID;
+      case 3: return DEVICE_TYPE_IOS;
+      default: return null;
+    }
+  }
+
+  private final int value;
+
+  private DeviceType(int value) {
+    this.value = value;
+  }
+}
+

+ 31 - 0
jim-common/src/main/java/org/jim/common/packets/ExitGroupNotifyRespBody.java

@@ -0,0 +1,31 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 退出群组通知消息体
+ * 作者: WChao 创建时间: 2017年7月26日 下午5:15:18
+ */
+public class ExitGroupNotifyRespBody extends Message{
+	
+	private static final long serialVersionUID = 3680734574052114902L;
+	private User user;
+	private String group;
+	
+	public User getUser() {
+		return user;
+	}
+	public ExitGroupNotifyRespBody setUser(User user) {
+		this.user = user;
+		return this;
+	}
+	public String getGroup() {
+		return group;
+	}
+	public ExitGroupNotifyRespBody setGroup(String group) {
+		this.group = group;
+		return this;
+	}
+}

+ 144 - 0
jim-common/src/main/java/org/jim/common/packets/Group.java

@@ -0,0 +1,144 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+import java.util.List;
+
+/**
+ * 版本: [1.0]
+ * 功能说明: 
+ * 作者: WChao 创建时间: 2017年9月21日 下午1:54:04
+ */
+public class Group extends Message{
+	
+	private static final long serialVersionUID = -3817755433171220952L;
+
+	/**
+	 * 群租id
+	 */
+	private String group_id;
+
+	/**
+	 * 群组名称;
+	 */
+	private String name;
+
+	/**
+	 * 群组头像;
+	 */
+	private String avatar;
+
+	/**
+	 * 在线人数;
+	 */
+	private Integer online;
+
+	/**
+	 * 组用户;
+	 */
+	private List<User> users;
+
+	/**
+	 * sw_service_account_role_department_middle 表主键id
+	 */
+	private String serviceAccountRoleDepartmentId;
+
+	/**
+	 * 游客(visitorDepartmentId) 或者 客户(customerDepartmentId)
+	 */
+	private String consumerId;
+
+	/**
+	 * 群组类型 0:游客-客服 类型 1:客户-客服 类型
+	 */
+	private Integer groupType;
+
+	/**
+	 * 平台id
+	 */
+	private String companyId;
+
+	/**
+	 * 部门id
+	 */
+	private String departmentId;
+
+	public Group(){}
+	public Group(String group_id , String name){
+		this.group_id = group_id;
+		this.name = name;
+	}
+
+	public String getCompanyId() {
+		return companyId;
+	}
+
+	public void setCompanyId(String companyId) {
+		this.companyId = companyId;
+	}
+
+	public String getDepartmentId() {
+		return departmentId;
+	}
+
+	public void setDepartmentId(String departmentId) {
+		this.departmentId = departmentId;
+	}
+
+	public String getServiceAccountRoleDepartmentId() {
+		return serviceAccountRoleDepartmentId;
+	}
+
+	public void setServiceAccountRoleDepartmentId(String serviceAccountRoleDepartmentId) {
+		this.serviceAccountRoleDepartmentId = serviceAccountRoleDepartmentId;
+	}
+
+	public String getConsumerId() {
+		return consumerId;
+	}
+
+	public void setConsumerId(String consumerId) {
+		this.consumerId = consumerId;
+	}
+
+	public Integer getGroupType() {
+		return groupType;
+	}
+
+	public void setGroupType(Integer groupType) {
+		this.groupType = groupType;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+	public String getAvatar() {
+		return avatar;
+	}
+	public void setAvatar(String avatar) {
+		this.avatar = avatar;
+	}
+	public Integer getOnline() {
+		return online;
+	}
+	public void setOnline(Integer online) {
+		this.online = online;
+	}
+	public List<User> getUsers() {
+		return users;
+	}
+	public void setUsers(List<User> users) {
+		this.users = users;
+	}
+	public String getGroup_id() {
+		return group_id;
+	}
+	public void setGroup_id(String group_id) {
+		this.group_id = group_id;
+	}
+}

+ 28 - 0
jim-common/src/main/java/org/jim/common/packets/HandshakeBody.java

@@ -0,0 +1,28 @@
+/**
+ * 
+ */
+package org.jim.common.packets;
+
+/**
+ * @author WChao
+ *
+ */
+public class HandshakeBody extends Message{
+
+	private static final long serialVersionUID = 4493254915372077140L;
+	private byte hbyte;
+	
+	public HandshakeBody(){}
+	public HandshakeBody(byte hbyte){
+		this.hbyte = hbyte;
+	}
+	public byte getHbyte() {
+		return hbyte;
+	}
+
+	public HandshakeBody setHbyte(byte hbyte) {
+		this.hbyte = hbyte;
+		return this;
+	}
+	
+}

+ 0 - 0
jim-common/src/main/java/org/jim/common/packets/HeartbeatBody.java


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