├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── cn │ │ └── flaty │ │ ├── NettyPush │ │ ├── base │ │ │ ├── PageBean.java │ │ │ └── QueryPageObject.java │ │ ├── entity │ │ │ ├── packet │ │ │ │ ├── ClientPacket.java │ │ │ │ └── GenericPacket.java │ │ │ └── persitence │ │ │ │ └── Client.java │ │ ├── nosql │ │ │ ├── JedisTemplate.java │ │ │ ├── JedisUtils.java │ │ │ └── RedisClientRepository.java │ │ ├── repository │ │ │ ├── ClientRepository.java │ │ │ ├── H2ClientRepository.java │ │ │ ├── JdbcTemplateWrapper.java │ │ │ └── MysqlClientRepository.java │ │ ├── server │ │ │ ├── DefaultListener.java │ │ │ ├── Listener.java │ │ │ ├── channelInitializer │ │ │ │ ├── AbstractChannelInitializer.java │ │ │ │ └── PushChannelInitializer.java │ │ │ ├── codec │ │ │ │ └── push │ │ │ │ │ ├── DeserializeHandler.java │ │ │ │ │ ├── MergeFrameEncoder.java │ │ │ │ │ ├── PushFrameDecoder.java │ │ │ │ │ ├── PushFrameEncoder.java │ │ │ │ │ └── SplitFrameDecoder.java │ │ │ ├── conn │ │ │ │ ├── GuavaConnPool.java │ │ │ │ ├── NettyConnection.java │ │ │ │ └── NettyConnectionPool.java │ │ │ └── frame │ │ │ │ ├── FrameHead.java │ │ │ │ ├── SimplePushHead.java │ │ │ │ ├── SimplePushInFrame.java │ │ │ │ └── SimplePushOutFrame.java │ │ ├── services │ │ │ ├── ClientDispacherService.java │ │ │ ├── ConnPoolService.java │ │ │ └── PushService.java │ │ └── utils │ │ │ ├── AssertUtils.java │ │ │ ├── ByteUtil.java │ │ │ ├── CharsetUtil.java │ │ │ ├── FastJsonUtils.java │ │ │ ├── InetSocketUtils.java │ │ │ ├── MyCharsetUtil.java │ │ │ ├── RuntimeUtils.java │ │ │ └── beanFactoryUtils.java │ │ └── pushAdmin │ │ ├── entity │ │ ├── Message.java │ │ ├── PushMessagePacket.java │ │ └── SendedMessage.java │ │ ├── repository │ │ ├── ClientMessageRepository.java │ │ └── H2ClientMessageRepository.java │ │ ├── services │ │ ├── DefaultListenerProxy.java │ │ └── PushServiceProxy.java │ │ └── views │ │ ├── BaseDataWrapper.java │ │ ├── BaseInterceptor.java │ │ ├── WebConstants.java │ │ └── push │ │ ├── PushMessageController.java │ │ └── PushMessageForm.java ├── resources │ ├── applicationContext.xml │ ├── config │ │ ├── jdbc.properties │ │ └── server.properties │ ├── logback.xml │ ├── nosql │ │ └── applicationRedis.xml │ ├── spring │ │ ├── applicationDataSource.xml │ │ ├── applicationRescourse.xml │ │ └── applicationServer.xml │ ├── springMvc │ │ └── applicationMvc.xml │ ├── sql │ │ └── schema.sql │ └── task │ │ └── applicationTask.xml └── webapp │ ├── WEB-INF │ ├── decorators.xml │ ├── jsp │ │ ├── common │ │ │ ├── base.jsp │ │ │ └── menus.jsp │ │ └── pushMessage │ │ │ ├── new.jsp │ │ │ └── query.jsp │ └── web.xml │ ├── index.jsp │ └── res │ ├── amazonUi │ ├── css │ │ ├── admin.css │ │ ├── amazeui.css │ │ ├── amazeui.flat.css │ │ ├── amazeui.flat.min.css │ │ ├── amazeui.min.css │ │ └── app.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ └── js │ │ ├── amazeui.js │ │ ├── amazeui.legacy.js │ │ ├── amazeui.legacy.min.js │ │ ├── amazeui.min.js │ │ ├── amazeui.widgets.helper.js │ │ ├── amazeui.widgets.helper.min.js │ │ ├── app.js │ │ ├── handlebars.min.js │ │ ├── jquery.min.js │ │ └── polyfill │ │ ├── rem.min.js │ │ └── respond.min.js │ └── module │ └── pushMessage │ └── new.js └── test ├── java └── cn │ └── flaty │ ├── Profiles.java │ ├── StartServer.java │ ├── nosql │ └── JedisTemplateTest.java │ └── spring │ ├── SpringContextTestCase.java │ └── SpringTransactionalTestCase.java └── resources ├── log4jdbc.log4j2.properties └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings/* 3 | logs/* 4 | .classpath 5 | .project 6 | *.db 7 | *.class 8 | *.bak 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ###基础通信协议 4 | 简单的基础的自定义通信协议 5 | +-------+----+-----+----------------+ 6 | bytes | 4 | 1 | 3 | ... | 7 | +------+-----+-----+----------------+ 8 | packet | 长度 | 编码 | 保留 | 包体 | 9 | +------+-----+-----+----------------+ 10 | 11 | 12 | ###web分支 13 | 14 | 为区分报文类型在包体加上**4字节**,**因业务不同**,故放包体中 15 | 16 | 包体 17 | +--------+----------------+ 18 | bytes | 4 | ... | 19 | +--------+----------------+ 20 | packet | 报文类型| 报文内容 | 21 | +--------+----------------+ 22 | 23 | * 实现简单的推送(android) 24 | * 先用H2内存数据库做为开发环境 25 | * 心跳处理 26 | 27 | 28 | > android客户端详见 [android客户端](https://github.com/flatychen/nettyPusherAndroid) 29 | 30 | 31 | ###简单说明 32 | 33 | - 开发环境使用嵌入式手动启动jetty,位于src/test/ startServer.java中 34 | - cn.flaty包下的PushAdmin包与NettyPush包分别对应着连接管理与消息管理后台,以为后面分离做好准备; 35 | - 由于管理与连接暂时没分离,故连接的启动使用spring监听器启动 36 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | cn.flaty 5 | nettyPusher 6 | 0.0.1-SNAPSHOT 7 | war 8 | 9 | 10 | UTF-8 11 | 1.7 12 | 1.7 13 | 1.7 14 | 1.7 15 | 16 | 17 | 18 | 19 | 2.4 20 | 3.3.2 21 | 2.4 22 | 1.2.2 23 | 1.8 24 | 1.4 25 | 2.2 26 | 1.9.0 27 | 17.0 28 | 29 | 4.0.2 30 | 31 | 1.7.12 32 | 1.1.2 33 | 1.16 34 | 35 | 1.7.2 36 | 3.2.8.RELEASE 37 | 2.2.2 38 | 39 | 2.4.2 40 | 41 | 1.1.40 42 | 1.9.8 43 | 44 | 1.0.0.GA 45 | 4.3.1.Final 46 | 47 | 48 | 4.0.28.Final 49 | 50 | 2.7.2 51 | 52 | 53 | 54 | 55 | 4.8.2 56 | 1.9.5 57 | 58 | 59 | 8.1.16.v20140903 60 | 61 | 62 | 63 | 65 | com.h2database 66 | h2 67 | 1.4.184 68 | 69 | 70 | 71 | 72 | 73 | org.slf4j 74 | slf4j-api 75 | ${org.slf4j-version} 76 | 77 | 78 | org.slf4j 79 | log4j-over-slf4j 80 | ${org.slf4j-version} 81 | 82 | 83 | org.slf4j 84 | jcl-over-slf4j 85 | ${org.slf4j-version} 86 | 87 | 88 | org.slf4j 89 | jul-to-slf4j 90 | ${org.slf4j-version} 91 | 92 | 93 | ch.qos.logback 94 | logback-classic 95 | ${logback.version} 96 | 97 | 98 | org.bgee.log4jdbc-log4j2 99 | log4jdbc-log4j2-jdbc4 100 | ${log4jdbc-log4j2-jdbc4-version} 101 | 102 | 103 | 104 | 105 | commons-beanutils 106 | commons-beanutils 107 | ${commons-beanutils-version} 108 | 109 | 110 | commons-lang 111 | commons-lang 112 | ${commons-lang-version} 113 | 114 | 115 | org.apache.commons 116 | commons-lang3 117 | ${commons-lang3-version} 118 | 119 | 120 | 121 | commons-fileupload 122 | commons-fileupload 123 | ${commons-fileupload-version} 124 | 125 | 126 | commons-io 127 | commons-io 128 | ${commons-io-version} 129 | 130 | 131 | commons-codec 132 | commons-codec 133 | ${commons-codec-version} 134 | 135 | 136 | commons-dbcp 137 | commons-dbcp 138 | ${commons-dbcp-version} 139 | 140 | 141 | org.apache.commons 142 | commons-pool2 143 | ${commons-pool-version} 144 | 145 | 146 | 147 | 148 | org.springframework 149 | spring-context 150 | ${org.springframework-version} 151 | 152 | 153 | 154 | commons-logging 155 | commons-logging 156 | 157 | 158 | 159 | 160 | org.springframework 161 | spring-context-support 162 | ${org.springframework-version} 163 | 164 | 165 | 166 | org.springframework 167 | spring-jdbc 168 | ${org.springframework-version} 169 | 170 | 171 | 172 | org.springframework 173 | spring-webmvc 174 | ${org.springframework-version} 175 | 176 | 177 | 178 | 179 | org.aspectj 180 | aspectjweaver 181 | ${org.aspectj-version} 182 | 183 | 184 | 185 | 186 | 187 | 188 | cglib 189 | cglib-nodep 190 | ${cglib-version} 191 | 192 | 193 | 194 | org.aspectj 195 | aspectjweaver 196 | ${org.aspectj-version} 197 | 198 | 199 | 200 | com.google.guava 201 | guava 202 | ${guava.version} 203 | 204 | 205 | 206 | 207 | ${jdbc.driver.groupId} 208 | ${jdbc.driver.artifactId} 209 | ${jdbc.driver.version} 210 | 211 | 212 | 213 | 214 | 215 | org.codehaus.jackson 216 | jackson-mapper-asl 217 | ${org.codehaus.jackson-version} 218 | 219 | 220 | 221 | com.alibaba 222 | fastjson 223 | ${com.alibaba.fastjson} 224 | 225 | 226 | 227 | 228 | redis.clients 229 | jedis 230 | ${jedis-version} 231 | 232 | 233 | 234 | io.netty 235 | netty-all 236 | ${netty-version} 237 | 238 | 239 | 240 | 241 | opensymphony 242 | sitemesh 243 | ${sitemesh-version} 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | javax.validation 252 | validation-api 253 | ${javax.validation-version} 254 | 255 | 256 | org.hibernate 257 | hibernate-validator 258 | ${org.hibernate-version} 259 | 260 | 261 | 262 | 263 | ${jdbc.driver.groupId} 264 | ${jdbc.driver.artifactId} 265 | ${jdbc.driver.version} 266 | 267 | 268 | 269 | 270 | 271 | org.springframework 272 | spring-test 273 | ${org.springframework-version} 274 | test 275 | 276 | 277 | junit 278 | junit 279 | ${junit-version} 280 | test 281 | 282 | 283 | org.mockito 284 | mockito-core 285 | ${org.mockito.mockito-core} 286 | test 287 | 288 | 289 | 290 | 291 | org.eclipse.jetty 292 | jetty-webapp 293 | ${jetty.version} 294 | provided 295 | 296 | 297 | org.eclipse.jetty 298 | jetty-jsp 299 | ${jetty.version} 300 | provided 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | maven-resources-plugin 313 | 2.5 314 | 315 | 316 | 317 | maven-clean-plugin 318 | 2.4.1 319 | 320 | 321 | maven-compiler-plugin 322 | 2.4 323 | 324 | ${jdk} 325 | 326 | 327 | 328 | org.eclipse.jetty 329 | jetty-maven-plugin 330 | 9.2.6.v20141205 331 | 332 | manual 333 | 334 | /${project.artifactId} 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/base/PageBean.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.base; 2 | 3 | import java.util.List; 4 | 5 | public class PageBean { 6 | 7 | public final int DEFAULT_PAGE_SIZE = 15; 8 | 9 | private int pageSize = DEFAULT_PAGE_SIZE ; 10 | 11 | private int toPage; 12 | 13 | private int totalPage; 14 | 15 | private int totalRows; 16 | 17 | private List objList; 18 | 19 | 20 | 21 | public PageBean(int toPage, int pageSize, int totalRows, List objList) { 22 | super(); 23 | this.objList = objList; 24 | initPageParams(toPage,pageSize,totalRows); 25 | } 26 | 27 | private void initPageParams(int toPage, int pageSize, int totalRows) { 28 | //页大小 29 | if(pageSize > 0){ 30 | this.pageSize = pageSize; 31 | } 32 | 33 | //分页数 34 | if(totalRows > 0){ 35 | this.totalRows = totalRows; 36 | int total = totalRows / pageSize; 37 | if (totalRows % pageSize != 0) { 38 | total++; 39 | } 40 | totalPage= total; 41 | }else{ 42 | totalPage = 1; 43 | } 44 | 45 | //请求页合法化 46 | if (toPage <= 0 ) { 47 | this.toPage = 1; 48 | } else if(toPage >= totalPage){ 49 | toPage= totalPage; 50 | } else{ 51 | this.toPage = toPage; 52 | } 53 | 54 | 55 | } 56 | 57 | public void setToPage(int toPage) { 58 | this.toPage = toPage; 59 | } 60 | 61 | public void setTotalPage(int totalPage) { 62 | this.totalPage = totalPage; 63 | } 64 | 65 | public void setTotalRows(int totalRows) { 66 | this.totalRows = totalRows; 67 | } 68 | 69 | public void setObjList(List objList) { 70 | this.objList = objList; 71 | } 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/base/QueryPageObject.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.base; 2 | 3 | /** 4 | * 5 | * @author flatychen 6 | * @date 2014-5-8 7 | */ 8 | public class QueryPageObject { 9 | 10 | public static final int DEFAULT_PAGE_SIZE = 15; 11 | 12 | public static final int DEFAULT_PAGE_NAV_SIZE = 5; 13 | 14 | public static final int DEFAULT_PAGE_NO = 1; 15 | 16 | public static final int ALL_DATA = 0; 17 | 18 | /** 19 | * 页面大小 20 | */ 21 | private Integer pageSize = DEFAULT_PAGE_SIZE; 22 | 23 | /** 24 | * 请求页 25 | */ 26 | private Integer pageNo = DEFAULT_PAGE_NO; 27 | 28 | /** 29 | * 分页数字导航显示 30 | */ 31 | private Integer pageNavSize = DEFAULT_PAGE_NAV_SIZE; 32 | 33 | public QueryPageObject(Integer pageNo,Integer pageSize, Integer pageNavSize) { 34 | super(); 35 | this.setPageNavSize(pageNavSize); 36 | this.setPageNo(pageNo); 37 | this.setPageSize(pageSize); 38 | } 39 | 40 | public QueryPageObject(Integer pageNo, Integer pageSize) { 41 | this(pageNo,pageSize,DEFAULT_PAGE_NAV_SIZE); 42 | } 43 | 44 | public QueryPageObject() { 45 | this(DEFAULT_PAGE_NO , DEFAULT_PAGE_SIZE); 46 | } 47 | 48 | public Integer getPageSize() { 49 | return pageSize; 50 | } 51 | 52 | /** 53 | * 54 | * @author flatychen 55 | * @date 2014-11-17 56 | * @param pageSize 当为0时,查找所有 57 | * @return 58 | * @version 59 | */ 60 | public QueryPageObject setPageSize(Integer pageSize) { 61 | this.pageSize = initPageNumberValid(pageSize); 62 | return this; 63 | } 64 | 65 | public Integer getPageNo() { 66 | return pageNo; 67 | } 68 | 69 | public QueryPageObject setPageNo(Integer pageNo) { 70 | this.pageNo = initPageNumberValid(pageNo); 71 | return this; 72 | } 73 | 74 | public int getPageNavSize() { 75 | return pageNavSize; 76 | } 77 | 78 | public QueryPageObject setPageNavSize(Integer pageNavSize) { 79 | this.pageNavSize = initPageNumberValid(pageNavSize); 80 | return this; 81 | } 82 | 83 | private int initPageNumberValid(Integer number) { 84 | return (number == null || number < 0) ? 1 : number; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/entity/packet/ClientPacket.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.entity.packet; 2 | 3 | import org.apache.commons.codec.digest.DigestUtils; 4 | 5 | /** 6 | * 客户端信息 7 | * 8 | * @author flatychen 9 | * 10 | */ 11 | public class ClientPacket { 12 | 13 | @Override 14 | public String toString() { 15 | return "ClientPacket [appKey=" + appKey + ", did=" + did + ", appVer=" 16 | + appVer + ", os=" + os + "]"; 17 | } 18 | 19 | private String appKey; 20 | 21 | private String did; 22 | 23 | private int appVer; 24 | 25 | private String os; 26 | 27 | public String getDid() { 28 | return did; 29 | } 30 | 31 | public void setDid(String did) { 32 | this.did = did; 33 | } 34 | 35 | public String getAppKey() { 36 | return appKey; 37 | } 38 | 39 | public void setAppKey(String appKey) { 40 | this.appKey = appKey; 41 | } 42 | 43 | public int getAppVer() { 44 | return appVer; 45 | } 46 | 47 | public void setAppVer(int appVer) { 48 | this.appVer = appVer; 49 | } 50 | 51 | public String getOs() { 52 | return os; 53 | } 54 | 55 | public void setOs(String os) { 56 | this.os = os; 57 | } 58 | 59 | /** 60 | * 得到APP连接唯一码 61 | * 62 | * @return 63 | * @author flatychen 64 | */ 65 | public String getAppClientUID() { 66 | return DigestUtils.md5(this.appKey + ":" + this.did).toString(); 67 | } 68 | 69 | /** 70 | * 组装成redis hash field 71 | * 72 | * @return 73 | * @author flatychen 74 | */ 75 | public String getRedisField() { 76 | return new StringBuilder().append(this.did).append(":").append(this.os) 77 | .append(":").append(this.appVer).toString(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/entity/packet/GenericPacket.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.entity.packet; 2 | 3 | /** 4 | * 5 | * 消息报文基础bean 6 | * 7 | * @author flatychen 8 | * 9 | */ 10 | public class GenericPacket { 11 | 12 | public static int server_push_text = 1024; 13 | 14 | public static int client_heart = 4096; 15 | 16 | public static int client_connected = 4097; 17 | 18 | private int commond; 19 | 20 | private String message; 21 | 22 | public int getCommond() { 23 | return commond; 24 | } 25 | 26 | public GenericPacket(String message) { 27 | super(); 28 | this.commond = Integer.parseInt(new String(message.substring(0, 4))); 29 | this.message = message.substring(4, message.length()); 30 | } 31 | 32 | public void setCommond(int commond) { 33 | this.commond = commond; 34 | } 35 | 36 | public String getMessage() { 37 | return message; 38 | } 39 | 40 | public void setMessage(String message) { 41 | this.message = message; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/entity/persitence/Client.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.entity.persitence; 2 | 3 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 4 | 5 | public class Client extends ClientPacket { 6 | 7 | private long expireTime; 8 | 9 | public long getExpireTime() { 10 | return expireTime; 11 | } 12 | 13 | public void setExpireTime(long expireTime) { 14 | this.expireTime = expireTime; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/nosql/JedisTemplate.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.nosql; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import redis.clients.jedis.Jedis; 11 | import redis.clients.jedis.JedisPool; 12 | import redis.clients.jedis.Pipeline; 13 | import redis.clients.jedis.Tuple; 14 | import redis.clients.jedis.exceptions.JedisException; 15 | 16 | /** 17 | * 18 | * 对springside JedisTemplate 进一步完善 19 | * 20 | * @author flatychen 21 | * 22 | */ 23 | public class JedisTemplate { 24 | 25 | private static Logger logger = LoggerFactory.getLogger(JedisTemplate.class); 26 | 27 | private JedisPool jedisPool; 28 | 29 | public JedisTemplate(JedisPool jedisPool) { 30 | this.jedisPool = jedisPool; 31 | } 32 | 33 | /** 34 | * Callback interface for template. 35 | */ 36 | public interface JedisAction { 37 | T action(Jedis jedis); 38 | } 39 | 40 | /** 41 | * Callback interface for template without result. 42 | */ 43 | public interface JedisActionNoResult { 44 | void action(Jedis jedis); 45 | } 46 | 47 | /** 48 | * Callback interface for template. 49 | */ 50 | public interface PipelineAction { 51 | List action(Pipeline Pipeline); 52 | } 53 | 54 | /** 55 | * Callback interface for template without result. 56 | */ 57 | public interface PipelineActionNoResult { 58 | void action(Pipeline Pipeline); 59 | } 60 | 61 | /** 62 | * Execute with a call back action with result. 63 | */ 64 | public T execute(JedisAction jedisAction) throws JedisException { 65 | Jedis jedis = null; 66 | try { 67 | jedis = jedisPool.getResource(); 68 | return jedisAction.action(jedis); 69 | } catch (JedisException e) { 70 | handleJedisException(e); 71 | throw e; 72 | } finally { 73 | jedis.close(); 74 | } 75 | } 76 | 77 | /** 78 | * Execute with a call back action without result. 79 | */ 80 | public void execute(JedisActionNoResult jedisAction) throws JedisException { 81 | Jedis jedis = null; 82 | try { 83 | jedis = jedisPool.getResource(); 84 | jedisAction.action(jedis); 85 | } catch (JedisException e) { 86 | handleJedisException(e); 87 | throw e; 88 | } finally { 89 | jedis.close(); 90 | } 91 | } 92 | 93 | /** 94 | * Execute with a call back action with result in pipeline. 95 | */ 96 | public List execute(PipelineAction pipelineAction) 97 | throws JedisException { 98 | Jedis jedis = null; 99 | try { 100 | jedis = jedisPool.getResource(); 101 | Pipeline pipeline = jedis.pipelined(); 102 | pipelineAction.action(pipeline); 103 | return pipeline.syncAndReturnAll(); 104 | } catch (JedisException e) { 105 | handleJedisException(e); 106 | throw e; 107 | } finally { 108 | jedis.close(); 109 | } 110 | } 111 | 112 | /** 113 | * Execute with a call back action without result in pipeline. 114 | */ 115 | public void execute(PipelineActionNoResult pipelineAction) 116 | throws JedisException { 117 | Jedis jedis = null; 118 | try { 119 | jedis = jedisPool.getResource(); 120 | Pipeline pipeline = jedis.pipelined(); 121 | pipelineAction.action(pipeline); 122 | pipeline.sync(); 123 | } catch (JedisException e) { 124 | handleJedisException(e); 125 | throw e; 126 | } finally { 127 | jedis.close(); 128 | } 129 | } 130 | 131 | /** 132 | * Return the internal JedisPool. 133 | */ 134 | public JedisPool getJedisPool() { 135 | return jedisPool; 136 | } 137 | 138 | /** 139 | * Handle jedisException, write log and return whether the connection is 140 | * broken. 141 | */ 142 | protected void handleJedisException(JedisException jedisException) { 143 | jedisException.printStackTrace(); 144 | // if (jedisException instanceof JedisConnectionException) { 145 | // logger.error("Redis connection lost."); 146 | // } else if (jedisException instanceof JedisDataException) { 147 | // if ((jedisException.getMessage() != null) 148 | // && (jedisException.getMessage().indexOf("READONLY") != -1)) { 149 | // logger.error("Redis connection are read-only slave."); 150 | // } 151 | // } else { 152 | // logger.error("Jedis exception happen.", jedisException); 153 | // } 154 | } 155 | 156 | // / Common Actions /// 157 | 158 | /** 159 | * Remove the specified keys. If a given key does not exist no operation is 160 | * performed for this key. 161 | * 162 | * return false if one of the key is not exist. 163 | */ 164 | public Boolean del(final String... keys) { 165 | return execute(new JedisAction() { 166 | 167 | @Override 168 | public Boolean action(Jedis jedis) { 169 | return jedis.del(keys) == keys.length ? true : false; 170 | } 171 | }); 172 | } 173 | 174 | public void flushDB() { 175 | execute(new JedisActionNoResult() { 176 | 177 | @Override 178 | public void action(Jedis jedis) { 179 | jedis.flushDB(); 180 | } 181 | }); 182 | } 183 | 184 | // / String Actions /// 185 | 186 | /** 187 | * Get the value of the specified key. If the key does not exist null is 188 | * returned. If the value stored at key is not a string an error is returned 189 | * because GET can only handle string values. 190 | */ 191 | public String get(final String key) { 192 | return execute(new JedisAction() { 193 | 194 | @Override 195 | public String action(Jedis jedis) { 196 | return jedis.get(key); 197 | } 198 | }); 199 | } 200 | 201 | /** 202 | * Get the value of the specified key as Long.If the key does not exist null 203 | * is returned. 204 | */ 205 | public Long getAsLong(final String key) { 206 | String result = get(key); 207 | return result != null ? Long.valueOf(result) : null; 208 | } 209 | 210 | /** 211 | * Get the value of the specified key as Integer.If the key does not exist 212 | * null is returned. 213 | */ 214 | public Integer getAsInt(final String key) { 215 | String result = get(key); 216 | return result != null ? Integer.valueOf(result) : null; 217 | } 218 | 219 | /** 220 | * Get the values of all the specified keys. If one or more keys dont exist 221 | * or is not of type String, a 'nil' value is returned instead of the value 222 | * of the specified key, but the operation never fails. 223 | */ 224 | public List mget(final String... keys) { 225 | return execute(new JedisAction>() { 226 | 227 | @Override 228 | public List action(Jedis jedis) { 229 | return jedis.mget(keys); 230 | } 231 | }); 232 | } 233 | 234 | /** 235 | * Set the string value as value of the key. The string can't be longer than 236 | * 1073741824 bytes (1 GB). 237 | */ 238 | public void set(final String key, final String value) { 239 | execute(new JedisActionNoResult() { 240 | 241 | @Override 242 | public void action(Jedis jedis) { 243 | jedis.set(key, value); 244 | } 245 | }); 246 | } 247 | 248 | /** 249 | * The command is exactly equivalent to the following group of commands: 250 | * {@link #set(String, String) SET} + {@link #expire(String, int) EXPIRE}. 251 | * The operation is atomic. 252 | */ 253 | public void setex(final String key, final String value, final int seconds) { 254 | execute(new JedisActionNoResult() { 255 | 256 | @Override 257 | public void action(Jedis jedis) { 258 | jedis.setex(key, seconds, value); 259 | } 260 | }); 261 | } 262 | 263 | /** 264 | * SETNX works exactly like {@link #setNX(String, String) SET} with the only 265 | * difference that if the key already exists no operation is performed. 266 | * SETNX actually means "SET if Not eXists". 267 | * 268 | * return true if the key was set. 269 | */ 270 | public Boolean setnx(final String key, final String value) { 271 | return execute(new JedisAction() { 272 | 273 | @Override 274 | public Boolean action(Jedis jedis) { 275 | return jedis.setnx(key, value) == 1 ? true : false; 276 | } 277 | }); 278 | } 279 | 280 | /** 281 | * The command is exactly equivalent to the following group of commands: 282 | * {@link #setex(String, String, int) SETEX} + 283 | * {@link #sexnx(String, String) SETNX}. The operation is atomic. 284 | */ 285 | public Boolean setnxex(final String key, final String value, 286 | final int seconds) { 287 | return execute(new JedisAction() { 288 | 289 | @Override 290 | public Boolean action(Jedis jedis) { 291 | String result = jedis.set(key, value, "NX", "EX", seconds); 292 | return JedisUtils.isStatusOk(result); 293 | } 294 | }); 295 | } 296 | 297 | /** 298 | * GETSET is an atomic set this value and return the old value command. Set 299 | * key to the string value and return the old value stored at key. The 300 | * string can't be longer than 1073741824 bytes (1 GB). 301 | */ 302 | public String getSet(final String key, final String value) { 303 | return execute(new JedisAction() { 304 | 305 | @Override 306 | public String action(Jedis jedis) { 307 | return jedis.getSet(key, value); 308 | } 309 | }); 310 | } 311 | 312 | /** 313 | * Increment the number stored at key by one. If the key does not exist or 314 | * contains a value of a wrong type, set the key to the value of "0" before 315 | * to perform the increment operation. 316 | *

317 | * INCR commands are limited to 64 bit signed integers. 318 | *

319 | * Note: this is actually a string operation, that is, in Redis there are 320 | * not "integer" types. Simply the string stored at the key is parsed as a 321 | * base 10 64 bit signed integer, incremented, and then converted back as a 322 | * string. 323 | * 324 | * @return Integer reply, this commands will reply with the new value of key 325 | * after the increment. 326 | */ 327 | public Long incr(final String key) { 328 | return execute(new JedisAction() { 329 | @Override 330 | public Long action(Jedis jedis) { 331 | return jedis.incr(key); 332 | } 333 | }); 334 | } 335 | 336 | public Long incrBy(final String key, final long increment) { 337 | return execute(new JedisAction() { 338 | @Override 339 | public Long action(Jedis jedis) { 340 | return jedis.incrBy(key, increment); 341 | } 342 | }); 343 | } 344 | 345 | public Double incrByFloat(final String key, final double increment) { 346 | return execute(new JedisAction() { 347 | @Override 348 | public Double action(Jedis jedis) { 349 | return jedis.incrByFloat(key, increment); 350 | } 351 | }); 352 | } 353 | 354 | /** 355 | * Decrement the number stored at key by one. If the key does not exist or 356 | * contains a value of a wrong type, set the key to the value of "0" before 357 | * to perform the decrement operation. 358 | */ 359 | public Long decr(final String key) { 360 | return execute(new JedisAction() { 361 | @Override 362 | public Long action(Jedis jedis) { 363 | return jedis.decr(key); 364 | } 365 | }); 366 | } 367 | 368 | public Long decrBy(final String key, final long decrement) { 369 | return execute(new JedisAction() { 370 | @Override 371 | public Long action(Jedis jedis) { 372 | return jedis.decrBy(key, decrement); 373 | } 374 | }); 375 | } 376 | 377 | // / Hash Actions /// 378 | /** 379 | * If key holds a hash, retrieve the value associated to the specified 380 | * field. 381 | *

382 | * If the field is not found or the key does not exist, a special 'nil' 383 | * value is returned. 384 | */ 385 | public String hget(final String key, final String fieldName) { 386 | return execute(new JedisAction() { 387 | @Override 388 | public String action(Jedis jedis) { 389 | return jedis.hget(key, fieldName); 390 | } 391 | }); 392 | } 393 | 394 | public List hmget(final String key, final String... fieldsNames) { 395 | return execute(new JedisAction>() { 396 | @Override 397 | public List action(Jedis jedis) { 398 | return jedis.hmget(key, fieldsNames); 399 | } 400 | }); 401 | } 402 | 403 | public Map hgetAll(final String key) { 404 | return execute(new JedisAction>() { 405 | @Override 406 | public Map action(Jedis jedis) { 407 | return jedis.hgetAll(key); 408 | } 409 | }); 410 | } 411 | 412 | public void hset(final String key, final String fieldName, 413 | final String value) { 414 | execute(new JedisActionNoResult() { 415 | 416 | @Override 417 | public void action(Jedis jedis) { 418 | jedis.hset(key, fieldName, value); 419 | } 420 | }); 421 | } 422 | 423 | public void hmset(final String key, final Map map) { 424 | execute(new JedisActionNoResult() { 425 | 426 | @Override 427 | public void action(Jedis jedis) { 428 | jedis.hmset(key, map); 429 | } 430 | }); 431 | 432 | } 433 | 434 | public Boolean hsetnx(final String key, final String fieldName, 435 | final String value) { 436 | return execute(new JedisAction() { 437 | 438 | @Override 439 | public Boolean action(Jedis jedis) { 440 | return jedis.hsetnx(key, fieldName, value) == 1 ? true : false; 441 | } 442 | }); 443 | } 444 | 445 | public Long hincrBy(final String key, final String fieldName, 446 | final long increment) { 447 | return execute(new JedisAction() { 448 | @Override 449 | public Long action(Jedis jedis) { 450 | return jedis.hincrBy(key, fieldName, increment); 451 | } 452 | }); 453 | } 454 | 455 | public Double hincrByFloat(final String key, final String fieldName, 456 | final double increment) { 457 | return execute(new JedisAction() { 458 | @Override 459 | public Double action(Jedis jedis) { 460 | return jedis.hincrByFloat(key, fieldName, increment); 461 | } 462 | }); 463 | } 464 | 465 | public Long hdel(final String key, final String... fieldsNames) { 466 | return execute(new JedisAction() { 467 | @Override 468 | public Long action(Jedis jedis) { 469 | return jedis.hdel(key, fieldsNames); 470 | } 471 | }); 472 | } 473 | 474 | public Boolean hexists(final String key, final String fieldName) { 475 | return execute(new JedisAction() { 476 | @Override 477 | public Boolean action(Jedis jedis) { 478 | return jedis.hexists(key, fieldName); 479 | } 480 | }); 481 | } 482 | 483 | public Set hkeys(final String key) { 484 | return execute(new JedisAction>() { 485 | @Override 486 | public Set action(Jedis jedis) { 487 | return jedis.hkeys(key); 488 | } 489 | }); 490 | } 491 | 492 | public Long hlen(final String key) { 493 | return execute(new JedisAction() { 494 | @Override 495 | public Long action(Jedis jedis) { 496 | return jedis.hlen(key); 497 | } 498 | }); 499 | } 500 | 501 | // / List Actions /// 502 | 503 | public Long lpush(final String key, final String... values) { 504 | return execute(new JedisAction() { 505 | @Override 506 | public Long action(Jedis jedis) { 507 | return jedis.lpush(key, values); 508 | } 509 | }); 510 | } 511 | 512 | public String rpop(final String key) { 513 | return execute(new JedisAction() { 514 | 515 | @Override 516 | public String action(Jedis jedis) { 517 | return jedis.rpop(key); 518 | } 519 | }); 520 | } 521 | 522 | public String brpop(final String key) { 523 | return execute(new JedisAction() { 524 | 525 | @Override 526 | public String action(Jedis jedis) { 527 | List nameValuePair = jedis.brpop(key); 528 | if (nameValuePair != null) { 529 | return nameValuePair.get(1); 530 | } else { 531 | return null; 532 | } 533 | } 534 | }); 535 | } 536 | 537 | public String brpop(final int timeout, final String key) { 538 | return execute(new JedisAction() { 539 | 540 | @Override 541 | public String action(Jedis jedis) { 542 | List nameValuePair = jedis.brpop(timeout, key); 543 | if (nameValuePair != null) { 544 | return nameValuePair.get(1); 545 | } else { 546 | return null; 547 | } 548 | } 549 | }); 550 | } 551 | 552 | /** 553 | * Not support for sharding. 554 | */ 555 | public String rpoplpush(final String sourceKey, final String destinationKey) { 556 | return execute(new JedisAction() { 557 | 558 | @Override 559 | public String action(Jedis jedis) { 560 | return jedis.rpoplpush(sourceKey, destinationKey); 561 | } 562 | }); 563 | } 564 | 565 | /** 566 | * Not support for sharding. 567 | */ 568 | public String brpoplpush(final String source, final String destination, 569 | final int timeout) { 570 | return execute(new JedisAction() { 571 | 572 | @Override 573 | public String action(Jedis jedis) { 574 | return jedis.brpoplpush(source, destination, timeout); 575 | } 576 | }); 577 | } 578 | 579 | public Long llen(final String key) { 580 | return execute(new JedisAction() { 581 | 582 | @Override 583 | public Long action(Jedis jedis) { 584 | return jedis.llen(key); 585 | } 586 | }); 587 | } 588 | 589 | public String lindex(final String key, final long index) { 590 | return execute(new JedisAction() { 591 | 592 | @Override 593 | public String action(Jedis jedis) { 594 | return jedis.lindex(key, index); 595 | } 596 | }); 597 | } 598 | 599 | public List lrange(final String key, final int start, final int end) { 600 | return execute(new JedisAction>() { 601 | 602 | @Override 603 | public List action(Jedis jedis) { 604 | return jedis.lrange(key, start, end); 605 | } 606 | }); 607 | } 608 | 609 | public void ltrim(final String key, final int start, final int end) { 610 | execute(new JedisActionNoResult() { 611 | @Override 612 | public void action(Jedis jedis) { 613 | jedis.ltrim(key, start, end); 614 | } 615 | }); 616 | } 617 | 618 | public void ltrimFromLeft(final String key, final int size) { 619 | execute(new JedisActionNoResult() { 620 | @Override 621 | public void action(Jedis jedis) { 622 | jedis.ltrim(key, 0, size - 1); 623 | } 624 | }); 625 | } 626 | 627 | public Boolean lremFirst(final String key, final String value) { 628 | return execute(new JedisAction() { 629 | @Override 630 | public Boolean action(Jedis jedis) { 631 | Long count = jedis.lrem(key, 1, value); 632 | return (count == 1); 633 | } 634 | }); 635 | } 636 | 637 | public Boolean lremAll(final String key, final String value) { 638 | return execute(new JedisAction() { 639 | @Override 640 | public Boolean action(Jedis jedis) { 641 | Long count = jedis.lrem(key, 0, value); 642 | return (count > 0); 643 | } 644 | }); 645 | } 646 | 647 | // / Set Actions /// 648 | public Boolean sadd(final String key, final String member) { 649 | return execute(new JedisAction() { 650 | 651 | @Override 652 | public Boolean action(Jedis jedis) { 653 | return jedis.sadd(key, member) == 1 ? true : false; 654 | } 655 | }); 656 | } 657 | 658 | public Set smembers(final String key) { 659 | return execute(new JedisAction>() { 660 | 661 | @Override 662 | public Set action(Jedis jedis) { 663 | return jedis.smembers(key); 664 | } 665 | }); 666 | } 667 | 668 | // / Ordered Set Actions /// 669 | /** 670 | * return true for add new element, false for only update the score. 671 | */ 672 | public Boolean zadd(final String key, final double score, 673 | final String member) { 674 | return execute(new JedisAction() { 675 | 676 | @Override 677 | public Boolean action(Jedis jedis) { 678 | return jedis.zadd(key, score, member) == 1 ? true : false; 679 | } 680 | }); 681 | } 682 | 683 | public Double zscore(final String key, final String member) { 684 | return execute(new JedisAction() { 685 | 686 | @Override 687 | public Double action(Jedis jedis) { 688 | return jedis.zscore(key, member); 689 | } 690 | }); 691 | } 692 | 693 | public Long zrank(final String key, final String member) { 694 | return execute(new JedisAction() { 695 | 696 | @Override 697 | public Long action(Jedis jedis) { 698 | return jedis.zrank(key, member); 699 | } 700 | }); 701 | } 702 | 703 | public Long zrevrank(final String key, final String member) { 704 | return execute(new JedisAction() { 705 | 706 | @Override 707 | public Long action(Jedis jedis) { 708 | return jedis.zrevrank(key, member); 709 | } 710 | }); 711 | } 712 | 713 | public Long zcount(final String key, final double min, final double max) { 714 | return execute(new JedisAction() { 715 | 716 | @Override 717 | public Long action(Jedis jedis) { 718 | return jedis.zcount(key, min, max); 719 | } 720 | }); 721 | } 722 | 723 | public Set zrange(final String key, final int start, final int end) { 724 | return execute(new JedisAction>() { 725 | 726 | @Override 727 | public Set action(Jedis jedis) { 728 | return jedis.zrange(key, start, end); 729 | } 730 | }); 731 | } 732 | 733 | public Set zrangeWithScores(final String key, final int start, 734 | final int end) { 735 | return execute(new JedisAction>() { 736 | 737 | @Override 738 | public Set action(Jedis jedis) { 739 | return jedis.zrangeWithScores(key, start, end); 740 | } 741 | }); 742 | } 743 | 744 | public Set zrevrange(final String key, final int start, 745 | final int end) { 746 | return execute(new JedisAction>() { 747 | 748 | @Override 749 | public Set action(Jedis jedis) { 750 | return jedis.zrevrange(key, start, end); 751 | } 752 | }); 753 | } 754 | 755 | public Set zrevrangeWithScores(final String key, final int start, 756 | final int end) { 757 | return execute(new JedisAction>() { 758 | 759 | @Override 760 | public Set action(Jedis jedis) { 761 | return jedis.zrevrangeWithScores(key, start, end); 762 | } 763 | }); 764 | } 765 | 766 | public Set zrangeByScore(final String key, final double min, 767 | final double max) { 768 | return execute(new JedisAction>() { 769 | 770 | @Override 771 | public Set action(Jedis jedis) { 772 | return jedis.zrangeByScore(key, min, max); 773 | } 774 | }); 775 | } 776 | 777 | public Set zrangeByScoreWithScores(final String key, 778 | final double min, final double max) { 779 | return execute(new JedisAction>() { 780 | 781 | @Override 782 | public Set action(Jedis jedis) { 783 | return jedis.zrangeByScoreWithScores(key, min, max); 784 | } 785 | }); 786 | } 787 | 788 | public Set zrevrangeByScore(final String key, final double max, 789 | final double min) { 790 | return execute(new JedisAction>() { 791 | 792 | @Override 793 | public Set action(Jedis jedis) { 794 | return jedis.zrevrangeByScore(key, max, min); 795 | } 796 | }); 797 | } 798 | 799 | public Set zrevrangeByScoreWithScores(final String key, 800 | final double max, final double min) { 801 | return execute(new JedisAction>() { 802 | 803 | @Override 804 | public Set action(Jedis jedis) { 805 | return jedis.zrevrangeByScoreWithScores(key, max, min); 806 | } 807 | }); 808 | } 809 | 810 | public Boolean zrem(final String key, final String member) { 811 | return execute(new JedisAction() { 812 | 813 | @Override 814 | public Boolean action(Jedis jedis) { 815 | return jedis.zrem(key, member) == 1 ? true : false; 816 | } 817 | }); 818 | } 819 | 820 | public Long zremByScore(final String key, final double start, 821 | final double end) { 822 | return execute(new JedisAction() { 823 | 824 | @Override 825 | public Long action(Jedis jedis) { 826 | return jedis.zremrangeByScore(key, start, end); 827 | } 828 | }); 829 | } 830 | 831 | public Long zremByRank(final String key, final long start, final long end) { 832 | return execute(new JedisAction() { 833 | 834 | @Override 835 | public Long action(Jedis jedis) { 836 | return jedis.zremrangeByRank(key, start, end); 837 | } 838 | }); 839 | } 840 | 841 | public Long zcard(final String key) { 842 | return execute(new JedisAction() { 843 | 844 | @Override 845 | public Long action(Jedis jedis) { 846 | return jedis.zcard(key); 847 | } 848 | }); 849 | } 850 | } 851 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/nosql/JedisUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.nosql; 2 | 3 | import redis.clients.jedis.Jedis; 4 | import redis.clients.jedis.JedisPool; 5 | import redis.clients.jedis.exceptions.JedisException; 6 | import cn.flaty.NettyPush.nosql.JedisTemplate.JedisAction; 7 | 8 | public class JedisUtils { 9 | 10 | private static final String OK_CODE = "OK"; 11 | private static final String OK_MULTI_CODE = "+OK"; 12 | 13 | /** 14 | * 判断 返回值是否ok. 15 | */ 16 | public static boolean isStatusOk(String status) { 17 | return (status != null) 18 | && (OK_CODE.equals(status) || OK_MULTI_CODE.equals(status)); 19 | } 20 | 21 | /** 22 | * Ping the jedis instance, return true is the result is PONG. 23 | */ 24 | public static boolean ping(JedisPool pool) { 25 | JedisTemplate template = new JedisTemplate(pool); 26 | try { 27 | String result = template.execute(new JedisAction() { 28 | @Override 29 | public String action(Jedis jedis) { 30 | return jedis.ping(); 31 | } 32 | }); 33 | return (result != null) && result.equals("PONG"); 34 | } catch (JedisException e) { 35 | return false; 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/nosql/RedisClientRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.nosql; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 9 | 10 | /** 11 | * 12 | *

13 |  * did -> 设备ID
14 |  * appkey -> app唯一标识码
15 |  * appClientUId = md5(appKey+did)    
16 |  * 
17 |  * client:[id] -> [appClientUId]  **[String]**  **expire[x second]** 
18 |  * [appkey] -> id  **[set]**
19 |  * [appkey]:[os] -> id         **[set]** 
20 |  * [appkey]:[appVer] -> id     **[set]**
21 |  * [appkey]:[label] -> id         **[set]**
22 |  * 
23 | * 24 | * 25 | * @author flatychen 26 | * 27 | */ 28 | @Repository 29 | public class RedisClientRepository { 30 | 31 | private static int clientExpireTime = 30; 32 | private static String clientsKeyPrefix = "clients:"; 33 | private static String clientKeyPrefix = "client:"; 34 | 35 | // @Autowired 36 | private JedisTemplate jedisTemplate; 37 | 38 | private static Logger logger = LoggerFactory 39 | .getLogger(RedisClientRepository.class); 40 | 41 | /** 42 | * 43 | * 44 | * @param client 45 | * @return 46 | * @author flatychen 47 | */ 48 | public void saveClient(ClientPacket client) { 49 | jedisTemplate.hset(clientsKeyPrefix + client.getAppKey(), 50 | client.getRedisField(), client.getAppClientUID()); 51 | jedisTemplate.setex(clientKeyPrefix + client.getAppClientUID(), "1", 52 | clientExpireTime); 53 | } 54 | 55 | /** 56 | * 57 | * 58 | * @param client 59 | * @return 60 | * @author flatychen 61 | */ 62 | public void touchClient(ClientPacket client) { 63 | jedisTemplate.setex(clientKeyPrefix + client.getAppClientUID(), "1", 64 | clientExpireTime); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/repository/ClientRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.lang.time.DateUtils; 6 | 7 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 8 | import cn.flaty.NettyPush.entity.persitence.Client; 9 | 10 | public interface ClientRepository { 11 | 12 | @SuppressWarnings("deprecation") 13 | public static long client_db_live_time = DateUtils.MILLIS_IN_SECOND * 40; 14 | 15 | public boolean insertClient(ClientPacket c); 16 | 17 | public Client getClient(String did); 18 | 19 | public List queryClients(String appKey); 20 | 21 | public boolean touchClient(ClientPacket c); 22 | 23 | public boolean delExpireClient(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/repository/H2ClientRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.repository; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 10 | import cn.flaty.NettyPush.entity.persitence.Client; 11 | 12 | @Repository 13 | public class H2ClientRepository implements ClientRepository { 14 | 15 | @Autowired 16 | private JdbcTemplateWrapper jdbc; 17 | 18 | @Override 19 | public boolean insertClient(ClientPacket c) { 20 | // 设置过期时间 21 | long expireTime = new Date(new Date().getTime() 22 | + ClientRepository.client_db_live_time).getTime(); 23 | String sql = " insert into tb_online_client(appkey,did,expireTime,appVer,os) values(?,?,?,?,?)"; 24 | return jdbc.saveORUpdate(sql, new Object[] { c.getAppKey(), c.getDid(), 25 | expireTime, c.getAppVer(), c.getOs() }) == 1; 26 | } 27 | 28 | @Override 29 | public List queryClients(String appKey) { 30 | String sql = " select appkey,did,expireTime from tb_online_client where appkey = ? and expireTime > ? "; 31 | return jdbc 32 | .queryForBeanList(sql, Client.class, new Object[] { appKey ,new Date().getTime()}); 33 | } 34 | 35 | @Override 36 | public boolean delExpireClient() { 37 | String sql = " delete from tb_online_client where expireTime < ? "; 38 | return jdbc.saveORUpdate(sql, new Object[] { new Date().getTime() }) >= 1; 39 | } 40 | 41 | @Override 42 | public boolean touchClient(ClientPacket c) { 43 | String sql = " update tb_online_client set expireTime = ? where did = ?"; 44 | return jdbc.saveORUpdate(sql, 45 | new Object[] { new Date().getTime() + ClientRepository.client_db_live_time , c.getDid() }) == 1; 46 | } 47 | 48 | @Override 49 | public Client getClient(String did) { 50 | String sql = " select appkey,did,expireTime from tb_online_client where did = ?"; 51 | return jdbc.queryForBean(sql, Client.class, new Object[] { did }); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/repository/JdbcTemplateWrapper.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.repository; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.dao.DataAccessException; 10 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 11 | import org.springframework.jdbc.core.JdbcTemplate; 12 | import org.springframework.stereotype.Component; 13 | 14 | /** 15 | * Spring jdbcTemplate 包装器类,提供基本SQL操作 16 | * 17 | * @author flatychen 18 | * 19 | */ 20 | 21 | @Component 22 | public class JdbcTemplateWrapper { 23 | 24 | private static Logger log = LoggerFactory 25 | .getLogger(JdbcTemplateWrapper.class); 26 | 27 | @Autowired 28 | private JdbcTemplate jdbcTemplate; 29 | 30 | public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 31 | this.jdbcTemplate = jdbcTemplate; 32 | } 33 | 34 | private final List query(String sql, Class clazz, Object args[]) { 35 | List l = null; 36 | try { 37 | log.info("====>>query{ sql:{}, args:{} }", sql, 38 | Arrays.toString(args)); 39 | l = this.jdbcTemplate.query(sql, args, 40 | new BeanPropertyRowMapper(clazz)); 41 | } catch (DataAccessException e) { 42 | e.printStackTrace(); 43 | } 44 | return l; 45 | 46 | } 47 | 48 | /** 49 | * class得到对象 50 | * 51 | * @param sql 52 | * @param clazz 53 | * @param args 54 | * @return 55 | * @author flatychen 56 | * @date 2014-1-10 57 | */ 58 | public final T queryForBean(String sql, Class clazz, Object args[]) { 59 | List l = this.query(sql, clazz, args); 60 | if ((l != null) && (l.size() == 1)) { 61 | return l.get(0); 62 | } else { 63 | 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * class得到对象list 70 | * 71 | * @param sql 72 | * @param clazz 73 | * @param args 74 | * @return 75 | * @author flatychen 76 | * @date 2014-1-10 77 | */ 78 | public final List queryForBeanList(String sql, Class clazz, 79 | Object args[]) { 80 | return this.query(sql, clazz, args); 81 | 82 | } 83 | 84 | /** 85 | * 返回 long --> count(1) 86 | * 87 | * @param sql 88 | * @param args 89 | * @return 90 | * @author flatychen 91 | * @date 2014-1-10 92 | */ 93 | public final long queryForLong(String sql, Object args[]) { 94 | log.info("====>>queryForLong[ sql:[ {} ] , args:[ {} ] ]", sql, 95 | Arrays.toString(args)); 96 | return this.jdbcTemplate.queryForLong(sql, args); 97 | 98 | } 99 | 100 | /** 101 | * 返回 int -->count(1) 102 | * 103 | * @param sql 104 | * @param args 105 | * @return 106 | * @author flatychen 107 | * @date 2014-1-10 108 | */ 109 | public final int queryForInt(String sql, Object args[]) { 110 | log.info("====>>queryForInt[ sql:[ {} ] , args:[ {} ] ]", sql, 111 | Arrays.toString(args)); 112 | return this.jdbcTemplate.queryForInt(sql, args); 113 | 114 | } 115 | 116 | /** 117 | * 返回 object eg: Integer,Object 118 | * 119 | * @param sql 120 | * @param args 121 | * @return 122 | * @author flatychen 123 | * @param 124 | * @date 2014-1-10 125 | */ 126 | public final T queryForObject(String sql, Class clazz, Object args[]) { 127 | log.info("====>>queryForObject[ sql:[{}] , args:[{}] ]", sql, 128 | Arrays.toString(args)); 129 | return this.jdbcTemplate.queryForObject(sql, clazz, args); 130 | 131 | } 132 | 133 | /** 134 | * 135 | * @author flatychen 136 | * @param sql 137 | * @param args 138 | * @return 139 | * @date 2014-1-12 140 | */ 141 | public int saveORUpdate(String sql, Object args[]) { 142 | log.info("====>>saveORUpdate[ sql:[ {} ] , args:[ {} ] ]", sql, 143 | Arrays.toString(args)); 144 | int num = 0; 145 | try { 146 | num = jdbcTemplate.update(sql, args); 147 | } catch (DataAccessException e) { 148 | e.printStackTrace(); 149 | throw e; 150 | } 151 | return num; 152 | } 153 | 154 | /** 155 | * 批量更新 156 | * 157 | * @author flatychen 158 | * @param sql 159 | * @param listArgs 160 | * @return 161 | */ 162 | public int batchUpdate(String sql, List listArgs) { 163 | log.info("====>>batchInsert[ sql:[ {} ] , args:[ {} ] ]", sql, 164 | Arrays.toString(listArgs.toArray())); 165 | int num[] = null; 166 | try { 167 | num = jdbcTemplate.batchUpdate(sql, listArgs); 168 | } catch (DataAccessException e) { 169 | e.printStackTrace(); 170 | throw e; 171 | } 172 | return num.length; 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/repository/MysqlClientRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 8 | import cn.flaty.NettyPush.entity.persitence.Client; 9 | 10 | //@Repository 11 | public class MysqlClientRepository implements ClientRepository { 12 | 13 | @Autowired 14 | private JdbcTemplateWrapper jdbc; 15 | 16 | @Override 17 | public boolean insertClient(ClientPacket c) { 18 | // TODO Auto-generated method stub 19 | return false; 20 | } 21 | 22 | @Override 23 | public Client getClient(String did) { 24 | // TODO Auto-generated method stub 25 | return null; 26 | } 27 | 28 | @Override 29 | public List queryClients(String appKey) { 30 | // TODO Auto-generated method stub 31 | return null; 32 | } 33 | 34 | 35 | @Override 36 | public boolean delExpireClient() { 37 | // TODO Auto-generated method stub 38 | return false; 39 | } 40 | 41 | @Override 42 | public boolean touchClient(ClientPacket c) { 43 | // TODO Auto-generated method stub 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/DefaultListener.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.PooledByteBufAllocator; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.MessageSizeEstimator; 12 | import io.netty.channel.MessageSizeEstimator.Handle; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.nio.NioServerSocketChannel; 15 | import io.netty.handler.logging.LogLevel; 16 | import io.netty.handler.logging.LoggingHandler; 17 | 18 | import java.text.MessageFormat; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.springframework.beans.factory.annotation.Required; 23 | 24 | import cn.flaty.NettyPush.utils.RuntimeUtils; 25 | 26 | /** 27 | * 28 | * 默认监听器 29 | * @author flaty 30 | * 31 | */ 32 | 33 | public class DefaultListener implements Listener{ 34 | 35 | private Logger log = LoggerFactory.getLogger(DefaultListener.class); 36 | 37 | private ChannelInitializer channelInitializer; 38 | 39 | private int port; 40 | 41 | private String host; 42 | 43 | 44 | public void start() { 45 | // acceptor 线程,用于监听 相当于 bio accept,并分发至worker线程 46 | EventLoopGroup acceptor = new NioEventLoopGroup(1); 47 | // work线程,用于处理,取当前服务器 cpu 数量 * 2 48 | EventLoopGroup worker = new NioEventLoopGroup(RuntimeUtils.getProcessors() * 2); 49 | try { 50 | // 启动器 51 | ServerBootstrap sbs = new ServerBootstrap(); 52 | sbs.group(acceptor, worker).channel(NioServerSocketChannel.class) 53 | // .childOption(ChannelOption.SO_KEEPALIVE, true) 54 | .childOption(ChannelOption.TCP_NODELAY, true) // 禁止 Nagle算法 55 | // .childOption(ChannelOption.SO_REUSEADDR, true) 56 | .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // 使用内存池 回收堆外内存 57 | // .childOption(ChannelOption.MESSAGE_SIZE_ESTIMATOR, new MessageSizeEstimator() { 58 | // @Override 59 | // public Handle newHandle() { 60 | // return new Handle() { 61 | // @Override 62 | // public int size(Object msg) { 63 | // return 256; 64 | // } 65 | // }; 66 | // } 67 | // }) // 使用内存池 回收堆外内存 68 | 69 | .localAddress(host, port) 70 | .handler(new LoggingHandler(LogLevel.INFO)) 71 | .childHandler(channelInitializer); 72 | log.info(MessageFormat.format("---> server start and listen on port:{0}",this.port+"")); 73 | ChannelFuture f = sbs.bind().sync(); 74 | f.channel().closeFuture().sync(); 75 | } catch (InterruptedException e) { 76 | log.error(e.getMessage()); 77 | e.printStackTrace(); 78 | } finally{ 79 | acceptor.shutdownGracefully(); 80 | worker.shutdownGracefully(); 81 | } 82 | } 83 | 84 | @Required 85 | public void setChannelInitializer(ChannelInitializer channelInitializer) { 86 | this.channelInitializer = channelInitializer; 87 | } 88 | 89 | @Required 90 | public void setPort(int port) { 91 | this.port = port; 92 | } 93 | 94 | @Required 95 | public void setHost(String host) { 96 | this.host = host; 97 | } 98 | 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/Listener.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server; 2 | 3 | 4 | public interface Listener { 5 | 6 | 7 | /** 8 | * 启动服务器监听 9 | */ 10 | public void start(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/channelInitializer/AbstractChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.channelInitializer; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | 8 | /** 9 | * 10 | * 抽像ChannelInitializer
11 | * 12 | * 用于组织不同的channel 13 | * 14 | * @author flatychen 15 | * 16 | */ 17 | public abstract class AbstractChannelInitializer extends ChannelInitializer { 18 | 19 | private ChannelHandler[] inHandlers; 20 | 21 | private ChannelHandler[] outHandlers; 22 | 23 | public void setInHandlers(ChannelHandler[] inHandlers) { 24 | this.inHandlers = inHandlers; 25 | } 26 | 27 | public void setOutHandlers(ChannelHandler[] outHandlers) { 28 | this.outHandlers = outHandlers; 29 | } 30 | 31 | @Override 32 | protected void initChannel(Channel ch) throws Exception { 33 | ChannelPipeline pipeline = ch.pipeline(); 34 | if (inHandlers != null) { 35 | pipeline.addFirst(inHandlers); 36 | } 37 | this.initPipeline(pipeline); 38 | if (outHandlers != null) { 39 | // pipeline.addl(outHandlers); 40 | } 41 | 42 | } 43 | 44 | protected abstract void initPipeline(ChannelPipeline pipeline); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/channelInitializer/PushChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.channelInitializer; 2 | 3 | import io.netty.channel.ChannelPipeline; 4 | 5 | import org.springframework.beans.factory.annotation.Required; 6 | 7 | import cn.flaty.NettyPush.server.codec.push.DeserializeHandler; 8 | import cn.flaty.NettyPush.server.codec.push.MergeFrameEncoder; 9 | import cn.flaty.NettyPush.server.codec.push.PushFrameDecoder; 10 | import cn.flaty.NettyPush.server.codec.push.PushFrameEncoder; 11 | import cn.flaty.NettyPush.server.codec.push.SplitFrameDecoder; 12 | import cn.flaty.NettyPush.server.frame.FrameHead; 13 | 14 | /** 15 | * 16 | * 推送channel pipeline 17 | * 18 | * @author flatychen 19 | * 20 | */ 21 | public class PushChannelInitializer extends AbstractChannelInitializer { 22 | 23 | private FrameHead frameHeader; 24 | 25 | @Override 26 | protected void initPipeline(ChannelPipeline pipeline) { 27 | 28 | // 切包,粘包 29 | pipeline.addLast(new SplitFrameDecoder(frameHeader)); 30 | // 解码 31 | pipeline.addLast(new PushFrameDecoder(frameHeader)); 32 | 33 | // 编码器 34 | // 粘包 35 | pipeline.addLast(new MergeFrameEncoder(frameHeader)); 36 | // 编码 37 | pipeline.addLast(new PushFrameEncoder(frameHeader)); 38 | 39 | // 业务处理 40 | pipeline.addLast(new DeserializeHandler()); 41 | 42 | } 43 | 44 | @Required 45 | public void setFrameHeader(FrameHead frameHeader) { 46 | this.frameHeader = frameHeader; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/codec/push/DeserializeHandler.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.codec.push; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import cn.flaty.NettyPush.server.conn.NettyConnection; 10 | import cn.flaty.NettyPush.services.ClientDispacherService; 11 | import cn.flaty.NettyPush.utils.beanFactoryUtils; 12 | 13 | /** 14 | * 15 | * 业务处理. 16 | * 17 | * @author flatychen 18 | * 19 | */ 20 | public class DeserializeHandler extends SimpleChannelInboundHandler { 21 | 22 | private ClientDispacherService deserialize; 23 | 24 | private Logger log = LoggerFactory.getLogger(DeserializeHandler.class); 25 | 26 | public DeserializeHandler() { 27 | super(); 28 | deserialize = beanFactoryUtils.getClientDispacherService(); 29 | } 30 | 31 | @Override 32 | protected void channelRead0(ChannelHandlerContext ctx, String msg) 33 | throws Exception { 34 | log.info("receive msg:{}", msg); 35 | // 分发 36 | NettyConnection conn = new NettyConnection(ctx); 37 | deserialize.dispacher(conn, msg); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/codec/push/MergeFrameEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.codec.push; 2 | 3 | import io.netty.handler.codec.LengthFieldPrepender; 4 | import cn.flaty.NettyPush.server.frame.FrameHead; 5 | 6 | 7 | /** 8 | * @author flaty 9 | * 10 | */ 11 | public class MergeFrameEncoder extends LengthFieldPrepender { 12 | 13 | public MergeFrameEncoder(FrameHead frameHead){ 14 | super(frameHead.byteLength(),- frameHead.headLength(),false); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/codec/push/PushFrameDecoder.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.codec.push; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.util.List; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import cn.flaty.NettyPush.server.frame.FrameHead; 14 | import cn.flaty.NettyPush.server.frame.SimplePushInFrame; 15 | import cn.flaty.NettyPush.utils.AssertUtils; 16 | 17 | /** 18 | * 19 | * 根据包头解码 20 | * 21 | * @author flatychen 22 | * 23 | */ 24 | public class PushFrameDecoder extends ByteToMessageDecoder { 25 | 26 | private Logger log = LoggerFactory.getLogger(PushFrameDecoder.class); 27 | 28 | private FrameHead frameHead; 29 | 30 | public PushFrameDecoder(FrameHead frameHead) { 31 | super(); 32 | AssertUtils.notNull(frameHead, " frameHead 不能为空 "); 33 | this.frameHead = frameHead; 34 | } 35 | 36 | @Override 37 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, 38 | List out) throws Exception { 39 | 40 | int bytesHaveRead = in.readableBytes(); 41 | 42 | // 读到0字节时且有读事件发生时为TCP rst重置 43 | if (bytesHaveRead == 0) { 44 | InetSocketAddress isa = (InetSocketAddress) ctx.channel() 45 | .remoteAddress(); 46 | log.warn("----> {} 关闭 ", isa.toString()); 47 | in.release(); 48 | return; 49 | } 50 | 51 | byte[] bytes = new byte[bytesHaveRead]; 52 | in.readBytes(bytes); 53 | // 包解码 54 | SimplePushInFrame frame = new SimplePushInFrame(frameHead, bytes); 55 | String _s = frame.getBody(); 56 | out.add(_s); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/codec/push/PushFrameEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.codec.push; 2 | 3 | import java.util.Arrays; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | import cn.flaty.NettyPush.server.frame.FrameHead; 9 | import cn.flaty.NettyPush.server.frame.SimplePushOutFrame; 10 | 11 | public class PushFrameEncoder extends MessageToByteEncoder { 12 | 13 | private FrameHead frameHead; 14 | 15 | public PushFrameEncoder(FrameHead frameHead) { 16 | super(); 17 | this.frameHead = frameHead; 18 | } 19 | 20 | @Override 21 | protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) 22 | throws Exception { 23 | SimplePushOutFrame frame = new SimplePushOutFrame(frameHead, msg); 24 | out.writeBytes(frame.getHead()); 25 | out.writeBytes(frame.getBody()); 26 | } 27 | 28 | 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/codec/push/SplitFrameDecoder.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.codec.push; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 6 | import cn.flaty.NettyPush.server.frame.FrameHead; 7 | 8 | /** 9 | * 10 | * 使用netty 自带进行 切包,粘包 11 | * 12 | * @author flatychen 13 | * 14 | */ 15 | public class SplitFrameDecoder extends LengthFieldBasedFrameDecoder { 16 | 17 | public SplitFrameDecoder(FrameHead frameHead) { 18 | super(frameHead.maxLength(), 0, frameHead.byteLength(), frameHead 19 | .headLength(), frameHead.byteLength()); 20 | } 21 | 22 | /* 23 | * 直接切除包头长度字节.防止内存拷贝 24 | */ 25 | @Override 26 | protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, 27 | int index, int length) { 28 | // 增加一次引用计数 29 | buffer.retain(); 30 | // 切聊包头长度字节 31 | return buffer.slice(index, length); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/conn/GuavaConnPool.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.conn; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import cn.flaty.NettyPush.repository.ClientRepository; 11 | 12 | import com.google.common.cache.CacheBuilder; 13 | import com.google.common.cache.CacheLoader; 14 | import com.google.common.cache.LoadingCache; 15 | import com.google.common.cache.RemovalListener; 16 | import com.google.common.cache.RemovalNotification; 17 | 18 | /** 19 | * 20 | * 使用guava 存储tcp本地连接 21 | * 22 | * @author flatychen 23 | * 24 | */ 25 | public class GuavaConnPool implements 26 | NettyConnectionPool { 27 | 28 | private Logger log = LoggerFactory.getLogger(GuavaConnPool.class); 29 | 30 | private LoadingCache cache = CacheBuilder 31 | .newBuilder() 32 | .expireAfterAccess(ClientRepository.client_db_live_time, 33 | TimeUnit.MILLISECONDS) 34 | .removalListener(new RemovalListener() { 35 | @Override 36 | public void onRemoval( 37 | RemovalNotification notification) { 38 | log.debug(notification.getKey() + " is removed "); 39 | } 40 | }).build(new CacheLoader() { 41 | @Override 42 | public NettyConnection load(String key) throws Exception { 43 | NettyConnection conn = cache.get(key); 44 | if (conn != null) { 45 | log.info("---> refresh key {} ", key); 46 | return conn; 47 | } else { 48 | log.info("---> loading a not exist key {} ", key); 49 | return null; 50 | } 51 | } 52 | 53 | }); 54 | 55 | @Override 56 | public boolean set(String key, NettyConnection t) { 57 | cache.put(key, t); 58 | return false; 59 | } 60 | 61 | @SuppressWarnings("finally") 62 | @Override 63 | public NettyConnection get(String key) { 64 | NettyConnection conn = null; 65 | try { 66 | conn = cache.get(key); 67 | } catch (ExecutionException e) { 68 | e.printStackTrace(); 69 | } finally { 70 | return conn; 71 | } 72 | } 73 | 74 | @Override 75 | public boolean touch(String key) { 76 | cache.refresh(key); 77 | return true; 78 | } 79 | 80 | @Override 81 | public Map asMap() { 82 | return cache.asMap(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/conn/NettyConnection.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.conn; 2 | 3 | 4 | import org.apache.log4j.spi.LoggerFactory; 5 | import org.slf4j.Logger; 6 | 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import cn.flaty.NettyPush.utils.AssertUtils; 10 | 11 | /** 12 | * 13 | * TCP 连接 14 | * 15 | * @author flatychen 16 | * 17 | */ 18 | public class NettyConnection { 19 | 20 | 21 | private Logger log = org.slf4j.LoggerFactory.getLogger(NettyConnection.class); 22 | 23 | private ChannelHandlerContext context; 24 | 25 | public NettyConnection(ChannelHandlerContext context) { 26 | super(); 27 | AssertUtils.notNull(context, "----> context 不能为空"); 28 | this.context = context; 29 | } 30 | 31 | public ChannelFuture writeAndFlush(String s) { 32 | log.info("send:{}",s); 33 | return context.writeAndFlush(s); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/conn/NettyConnectionPool.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.conn; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 7 | * 本地TCP连接池 8 | * 9 | * @author flatychen 10 | * 11 | * @param 12 | * @param 13 | */ 14 | public interface NettyConnectionPool { 15 | 16 | /** 17 | * 放连接 18 | * 19 | * @param key 20 | * @param t 21 | * @return 22 | * @author flatychen 23 | */ 24 | public boolean set(K key, V t); 25 | 26 | /** 27 | * 刷新连接 28 | * 29 | * @param key 30 | * @return 31 | * @author flatychen 32 | */ 33 | public boolean touch(K key); 34 | 35 | /** 36 | * 37 | * 根据key取连接 38 | * 39 | * @param key 40 | * @return 41 | * @author flatychen 42 | */ 43 | public V get(K key); 44 | 45 | /** 46 | * 消耗性能,仅用于测试! 47 | * 48 | * @return 49 | */ 50 | @Deprecated 51 | public Map asMap(); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/frame/FrameHead.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.frame; 2 | 3 | /** 4 | * 5 | * 包头长度定义 6 | * 7 | * @author flatychen 8 | * 9 | */ 10 | public interface FrameHead { 11 | 12 | public static enum charset { 13 | 14 | NULL, US_ASCII, ISO_8859_1, UTF_8, GBK, GB2312 15 | 16 | } 17 | 18 | public static enum encype { 19 | NULL 20 | } 21 | 22 | /** 23 | * 用于切包的,包长度所占字节数 24 | * 25 | * @return 26 | */ 27 | int byteLength(); 28 | 29 | /** 30 | * 包最大支持的报文长度 31 | * 32 | * @return 33 | */ 34 | int maxLength(); 35 | 36 | /** 37 | * 包头部所占字节 38 | * 39 | * @return 40 | */ 41 | int headLength(); 42 | 43 | /** 44 | * 包长度转换 45 | * 46 | * @param b 47 | * @return 48 | */ 49 | int bytesToInt(byte[] b); 50 | 51 | /** 52 | * 包长度转换 53 | * 54 | * @param length 55 | * @return 56 | */ 57 | byte[] intToBytes(int length); 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/frame/SimplePushHead.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.frame; 2 | 3 | import cn.flaty.NettyPush.utils.ByteUtil; 4 | 5 | /** 6 | * 7 | * 简单推送包头定义
8 | * 4字节长度, 4字节包头 9 | * 10 | * @author flatychen 11 | * 12 | */ 13 | public class SimplePushHead implements FrameHead { 14 | 15 | public final int FRAME_LENGTH_BYTES = 4; 16 | 17 | public final int MAX_LENGTH = Integer.MAX_VALUE; 18 | 19 | public final int HEAD_LENGTH_BYTES = 4; 20 | 21 | @Override 22 | public int byteLength() { 23 | return FRAME_LENGTH_BYTES; 24 | } 25 | 26 | @Override 27 | public int maxLength() { 28 | return MAX_LENGTH; 29 | } 30 | 31 | @Override 32 | public int headLength() { 33 | return HEAD_LENGTH_BYTES; 34 | } 35 | 36 | @Override 37 | public int bytesToInt(byte[] b) { 38 | if (b.length != this.FRAME_LENGTH_BYTES) { 39 | throw new IllegalArgumentException("----> 包长度数组非法"); 40 | } 41 | return ByteUtil.byteArrayToInt(b); 42 | } 43 | 44 | @Override 45 | public byte[] intToBytes(int length) { 46 | return ByteUtil.intToByteArray(length); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/frame/SimplePushInFrame.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.frame; 2 | 3 | import cn.flaty.NettyPush.utils.AssertUtils; 4 | import cn.flaty.NettyPush.utils.CharsetUtil; 5 | 6 | /** 7 | * 8 | * 解码包 9 | * @author flatychen 10 | * 11 | */ 12 | public class SimplePushInFrame { 13 | private FrameHead frameHead; 14 | 15 | private byte[] head; 16 | 17 | private String body; 18 | 19 | 20 | public SimplePushInFrame(FrameHead frameHead, byte[] frame) { 21 | super(); 22 | this.frameHead = frameHead; 23 | this.init(frame); 24 | this.frameHead = frameHead; 25 | } 26 | 27 | 28 | private void init(byte[] frame) { 29 | if( frame.length <= frameHead.byteLength()){ 30 | throw new IllegalArgumentException(" frame 内容不能为空 "); 31 | } 32 | 33 | head = new byte[frameHead.headLength()]; 34 | 35 | System.arraycopy(frame, 0, head, 0, frameHead.headLength()); 36 | 37 | this.setBody(frame,frame.length - frameHead.headLength()); 38 | } 39 | 40 | 41 | 42 | private void setBody(byte[] body,int bodyLength) { 43 | 44 | byte _body [] = body; 45 | 46 | this.setCharset(_body,bodyLength); 47 | 48 | } 49 | 50 | 51 | private void setCharset(byte[] _body,int bodyLength) { 52 | int charsetType = head[0]; 53 | String s = null; 54 | FrameHead.charset c = FrameHead.charset.values()[charsetType]; 55 | switch (c) { 56 | case NULL: 57 | s = new String(_body); 58 | break; 59 | case UTF_8: 60 | s = new String(_body, frameHead.headLength(), bodyLength, CharsetUtil.UTF_8); 61 | break; 62 | case US_ASCII: 63 | s = new String(_body, frameHead.headLength(), bodyLength, CharsetUtil.US_ASCII); 64 | break; 65 | case ISO_8859_1: 66 | s = new String(_body, frameHead.headLength(), bodyLength, CharsetUtil.ISO_8859_1); 67 | break; 68 | case GBK: 69 | s = new String(_body, frameHead.headLength(), bodyLength, CharsetUtil.GBK); 70 | break; 71 | case GB2312: 72 | s = new String(_body, frameHead.headLength(), bodyLength,CharsetUtil.GB2312); 73 | break; 74 | } 75 | this.body = s; 76 | 77 | } 78 | 79 | 80 | public byte getEncypeType(){ 81 | AssertUtils.notNull(head, " 包头不能为空"); 82 | return head[0]; 83 | } 84 | 85 | public byte getCharsetType(){ 86 | AssertUtils.notNull(head, " 包头不能为空"); 87 | return head[1]; 88 | } 89 | 90 | 91 | public String getBody() { 92 | return body; 93 | } 94 | 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/server/frame/SimplePushOutFrame.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.server.frame; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | import cn.flaty.NettyPush.server.frame.FrameHead.charset; 6 | import cn.flaty.NettyPush.utils.CharsetUtil; 7 | 8 | /** 9 | * 组装包 10 | * 11 | * @author flatychen 12 | * 13 | */ 14 | public class SimplePushOutFrame { 15 | 16 | private FrameHead frameHead; 17 | 18 | private charset charset; 19 | 20 | private byte[] length; 21 | 22 | private byte[] head; 23 | 24 | private byte[] body; 25 | 26 | public SimplePushOutFrame(FrameHead frameHead, String body) { 27 | super(); 28 | if (body == null || body.length() <= 0) { 29 | throw new IllegalArgumentException("body不可为空或长度非法"); 30 | } 31 | this.frameHead = frameHead; 32 | this.init(body); 33 | } 34 | 35 | private void init(String body) { 36 | length = new byte[frameHead.headLength()]; 37 | head = new byte[frameHead.headLength()]; 38 | this.body = body.getBytes(); 39 | 40 | 41 | this.charset = this.setCharset(body); 42 | this.setHead(); 43 | this.setLength(this.body.length); 44 | 45 | } 46 | 47 | 48 | private void setHead() { 49 | head[0] = (byte) charset.ordinal(); 50 | head[1] = (byte) FrameHead.encype.NULL.ordinal(); 51 | 52 | } 53 | 54 | private charset setCharset(String body) { 55 | String charsetName = Charset.defaultCharset().name(); 56 | charset charsetType = null; 57 | if (CharsetUtil.UTF_8.name().equalsIgnoreCase(charsetName)) { 58 | charsetType = FrameHead.charset.UTF_8; 59 | } else if (CharsetUtil.GBK.name().equalsIgnoreCase(charsetName)) { 60 | charsetType = FrameHead.charset.GBK; 61 | } else if (CharsetUtil.GB2312.displayName().equalsIgnoreCase( 62 | charsetName)) { 63 | charsetType = FrameHead.charset.GB2312; 64 | } else if (CharsetUtil.US_ASCII.displayName().equalsIgnoreCase( 65 | charsetName)) { 66 | charsetType = FrameHead.charset.US_ASCII; 67 | } else if (CharsetUtil.ISO_8859_1.displayName().equalsIgnoreCase( 68 | charsetName)) { 69 | charsetType = FrameHead.charset.ISO_8859_1; 70 | } 71 | return charsetType; 72 | } 73 | 74 | private void setLength(int l) { 75 | this.length = this.frameHead.intToBytes(l); 76 | } 77 | 78 | public byte[] getBody() { 79 | return body; 80 | } 81 | 82 | public byte[] getHead() { 83 | return head; 84 | } 85 | 86 | public byte[] getLength() { 87 | return length; 88 | } 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/services/ClientDispacherService.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.services; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 9 | import cn.flaty.NettyPush.entity.packet.GenericPacket; 10 | import cn.flaty.NettyPush.server.conn.NettyConnection; 11 | import cn.flaty.NettyPush.utils.FastJsonUtils; 12 | 13 | /** 14 | * 服务端报文分发 15 | * 16 | * @author flatychen 17 | * 18 | */ 19 | @Service 20 | public class ClientDispacherService extends ConnPoolService { 21 | 22 | @Autowired 23 | private PushService pushService; 24 | 25 | private static Logger log = LoggerFactory 26 | .getLogger(ClientDispacherService.class); 27 | 28 | /** 29 | * 客户端报文分发 30 | * 31 | * @param conn 32 | * @param msg 33 | */ 34 | public void dispacher(NettyConnection conn, String data) { 35 | GenericPacket m = new GenericPacket(data); 36 | // 得到包命令 37 | int commond = m.getCommond(); 38 | // 新连接 39 | if (commond == GenericPacket.client_connected) { 40 | this.validateAndSave(conn, m.getMessage()); 41 | // 心跳,刷新客户端 42 | } else if (commond == GenericPacket.client_heart) { 43 | this.keepAliveOfClient(m.getMessage()); 44 | } else { 45 | log.error(" invalid client packet type."); 46 | } 47 | 48 | } 49 | 50 | /** 51 | * 检测新连接合法性,并保存 52 | * 53 | * @param conn 54 | * @param message 55 | */ 56 | private void validateAndSave(NettyConnection conn, String message) { 57 | log.info("new client:{}", message); 58 | ClientPacket client = null; 59 | 60 | try { 61 | client = FastJsonUtils.praseToObject(message, ClientPacket.class); 62 | } catch (Exception e) { 63 | log.error("连接发送包不合法: {}", e.getMessage()); 64 | return; 65 | } 66 | 67 | // 检查是否有消息需要发送 68 | pushService.sendForNewClient(client, conn); 69 | 70 | // 保存连接信息于本地连接池中和DB中 71 | super.saveClientInfo(client); 72 | pool.set(client.getDid(), conn); 73 | 74 | // 自动刷新DB中客户端数据 75 | this.startRefleshClientOfDb(); 76 | 77 | } 78 | 79 | private void startRefleshClientOfDb() { 80 | if (!super.isRefleshClient()) { 81 | super.delExpireClientsOfDb(); 82 | } 83 | } 84 | 85 | /** 86 | * 87 | * 刷新客户端 88 | * 89 | * @param conn 90 | * @param message 91 | */ 92 | private void keepAliveOfClient(String message) { 93 | 94 | log.info("heartBeat:{}", message); 95 | 96 | ClientPacket client = FastJsonUtils.praseToObject(message, 97 | ClientPacket.class); 98 | 99 | pool.touch(client.getDid()); 100 | resetClientExpire(client); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/services/ConnPoolService.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.services; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.Timer; 6 | import java.util.TimerTask; 7 | 8 | import org.apache.commons.lang.time.DateUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 13 | import cn.flaty.NettyPush.entity.persitence.Client; 14 | import cn.flaty.NettyPush.repository.ClientRepository; 15 | import cn.flaty.NettyPush.server.conn.NettyConnection; 16 | import cn.flaty.NettyPush.server.conn.NettyConnectionPool; 17 | import cn.flaty.NettyPush.utils.AssertUtils; 18 | 19 | @Service 20 | public abstract class ConnPoolService { 21 | private volatile boolean isRefleshing = false; 22 | 23 | @Autowired 24 | private ClientRepository clientInfoRepo; 25 | 26 | @Autowired 27 | protected NettyConnectionPool pool; 28 | 29 | protected List queryClients(ClientPacket client) { 30 | return clientInfoRepo.queryClients(client.getAppKey()); 31 | } 32 | 33 | protected void saveClientInfo(ClientPacket clientPacket) { 34 | AssertUtils.notNull(clientPacket); 35 | AssertUtils.notNull(clientPacket.getDid()); 36 | Client client = this.getClient(clientPacket.getDid()); 37 | if (client == null) { 38 | clientInfoRepo.insertClient(clientPacket); 39 | } else { 40 | clientInfoRepo.touchClient(client); 41 | } 42 | 43 | } 44 | 45 | protected void resetClientExpire(ClientPacket client) { 46 | AssertUtils.notNull(client); 47 | AssertUtils.notNull(client.getDid()); 48 | clientInfoRepo.touchClient(client); 49 | } 50 | 51 | protected void delExpireClientsOfDb() { 52 | this.isRefleshing = true; 53 | Timer refleshClientInfo = new Timer("refleshClientInfo"); 54 | refleshClientInfo.schedule(new TimerTask() { 55 | @Override 56 | public void run() { 57 | clientInfoRepo.delExpireClient(); 58 | } 59 | }, 1024, 1 * DateUtils.MILLIS_IN_HOUR); 60 | } 61 | 62 | protected Client getClient(String did) { 63 | return clientInfoRepo.getClient(did); 64 | } 65 | 66 | protected boolean isRefleshClient() { 67 | return isRefleshing; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/services/PushService.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.services; 2 | 3 | import java.util.Collections; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import org.apache.commons.beanutils.PropertyUtils; 8 | import org.apache.commons.lang.time.DateUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.core.task.AsyncTaskExecutor; 13 | import org.springframework.stereotype.Service; 14 | 15 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 16 | import cn.flaty.NettyPush.entity.packet.GenericPacket; 17 | import cn.flaty.NettyPush.entity.persitence.Client; 18 | import cn.flaty.NettyPush.server.conn.NettyConnection; 19 | import cn.flaty.NettyPush.utils.FastJsonUtils; 20 | import cn.flaty.pushAdmin.entity.Message; 21 | import cn.flaty.pushAdmin.entity.PushMessagePacket; 22 | import cn.flaty.pushAdmin.entity.SendedMessage; 23 | import cn.flaty.pushAdmin.repository.ClientMessageRepository; 24 | 25 | @Service 26 | public class PushService extends ConnPoolService { 27 | 28 | @Autowired 29 | private AsyncTaskExecutor task; 30 | 31 | @Autowired 32 | private ClientMessageRepository clientMessageRepository; 33 | 34 | private Logger log = LoggerFactory.getLogger(PushService.class); 35 | 36 | /** 37 | * 推送
38 | * FIXME 大用户?注意优化!! juc 包 39 | * @param client 40 | * 41 | * @param msg 42 | */ 43 | public void sendOnLineClient(Client client, PushMessagePacket pm) { 44 | 45 | Long msgId = this.saveMessage(client,pm); 46 | 47 | 48 | 49 | List clients = Collections.unmodifiableList(super 50 | .queryClients(client)); 51 | NettyConnection conn = null; 52 | 53 | int size = clients.size(); 54 | if (size > 0) { 55 | log.info("sending {} client message:{}", clients.size(),pm.toString()); 56 | for (Client clientInfo : clients) { 57 | try { 58 | conn = pool.get(clientInfo.getDid()); 59 | if (conn != null) { 60 | this.send(msgId, clientInfo, pm, conn); 61 | } 62 | } catch (Exception e) { 63 | log.error(e.getMessage()); 64 | continue; 65 | } 66 | 67 | } 68 | } else { 69 | log.info("no client to send"); 70 | } 71 | 72 | } 73 | 74 | private void send(long msgId, ClientPacket client, PushMessagePacket pm, 75 | NettyConnection conn) throws Exception { 76 | 77 | // 发送推送消息 78 | String _msg = (GenericPacket.server_push_text) 79 | + FastJsonUtils.toJsonString(pm); 80 | conn.writeAndFlush(_msg); 81 | 82 | // 标记消息已发送 83 | SendedMessage cm = new SendedMessage(); 84 | cm.setDid(client.getDid()); 85 | cm.setAppKey(client.getAppKey()); 86 | cm.setMsgId(msgId); 87 | clientMessageRepository.insertSendedMessage(cm); 88 | 89 | } 90 | 91 | /** 92 | * 93 | * 保存消息于db中 94 | * 95 | * @param pushMessage 96 | * @return 97 | * @author flatychen 98 | * @param client 99 | */ 100 | private long saveMessage(Client client, PushMessagePacket pushMessage) { 101 | Message message = new Message(); 102 | try { 103 | PropertyUtils.copyProperties(message, pushMessage); 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } 107 | 108 | // 构造推送消息 109 | Date now = new Date(); 110 | // 一小时过期 111 | message.setExpireTime(DateUtils.addHours(now, 1).getTime()); 112 | message.setMsgId(now.getTime()); 113 | message.setAppKey(client.getAppKey()); 114 | clientMessageRepository.insertMessage(message); 115 | return now.getTime(); 116 | } 117 | 118 | /** 119 | * 120 | * 为新连接的客户端推送 121 | * 122 | * @param client 123 | * @param conn 124 | * @author flatychen 125 | */ 126 | public void sendForNewClient(final ClientPacket client, 127 | final NettyConnection conn) { 128 | 129 | final List messages = Collections 130 | .unmodifiableList(clientMessageRepository 131 | .queryClientMessage(client)); 132 | 133 | if (messages.size() > 0) { 134 | task.submit(new Runnable() { 135 | @Override 136 | public void run() { 137 | for (Message message : messages) { 138 | PushMessagePacket pm = new PushMessagePacket(); 139 | try { 140 | PropertyUtils.copyProperties(pm, message); 141 | send(message.getMsgId(), client, pm, conn); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | continue; 145 | } 146 | 147 | } 148 | 149 | } 150 | }); 151 | } 152 | 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/AssertUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | 4 | public abstract class AssertUtils { 5 | 6 | 7 | /** 8 | * AssertUtils that an object is {@code null} . 9 | *
AssertUtils.isNull(value, "The value must be null");
10 | * @param object the object to check 11 | * @param message the exception message to use if the assertion fails 12 | * @throws IllegalArgumentException if the object is not {@code null} 13 | */ 14 | public static void isNull(Object object, String message) { 15 | if (object != null) { 16 | throw new IllegalArgumentException(message); 17 | } 18 | } 19 | 20 | /** 21 | * AssertUtils that an object is {@code null} . 22 | *
AssertUtils.isNull(value);
23 | * @param object the object to check 24 | * @throws IllegalArgumentException if the object is not {@code null} 25 | */ 26 | public static void isNull(Object object) { 27 | isNull(object, "[Assertion failed] - the object argument must be null"); 28 | } 29 | 30 | /** 31 | * AssertUtils that an object is not {@code null} . 32 | *
AssertUtils.notNull(clazz, "The class must not be null");
33 | * @param object the object to check 34 | * @param message the exception message to use if the assertion fails 35 | * @throws IllegalArgumentException if the object is {@code null} 36 | */ 37 | public static void notNull(Object object, String message) { 38 | if (object == null) { 39 | throw new IllegalArgumentException(message); 40 | } 41 | } 42 | 43 | /** 44 | * AssertUtils that an object is not {@code null} . 45 | *
AssertUtils.notNull(clazz);
46 | * @param object the object to check 47 | * @throws IllegalArgumentException if the object is {@code null} 48 | */ 49 | public static void notNull(Object object) { 50 | notNull(object, "[Assertion failed] - this argument is required; it must not be null"); 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | public class ByteUtil { 4 | 5 | public static byte[] intToByteArray(int i) { 6 | byte[] result = new byte[4]; 7 | // 由高位到低位 8 | result[0] = (byte) ((i >> 24) & 0xFF); 9 | result[1] = (byte) ((i >> 16) & 0xFF); 10 | result[2] = (byte) ((i >> 8) & 0xFF); 11 | result[3] = (byte) (i & 0xFF); 12 | return result; 13 | } 14 | 15 | public static int byteArrayToInt(byte[] bytes) { 16 | int value = 0; 17 | // 由高位到低位 18 | for (int i = 0; i < 4; i++) { 19 | int shift = (4 - 1 - i) * 8; 20 | value += (bytes[i] & 0x000000FF) << shift;// 往高位游 21 | } 22 | return value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/CharsetUtil.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | 4 | import java.nio.charset.Charset; 5 | 6 | public final class CharsetUtil { 7 | 8 | public static final Charset UTF_8 = Charset.forName("UTF-8"); 9 | 10 | public static final Charset GBK = Charset.forName("GBK"); 11 | 12 | public static final Charset ISO_8859_1 = Charset.forName("ISO_8859_1"); 13 | 14 | public static final Charset GB2312 = Charset.forName("GB2312"); 15 | 16 | public static final Charset US_ASCII = Charset.forName("US-ASCII"); 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/FastJsonUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.alibaba.fastjson.JSON; 8 | import com.alibaba.fastjson.TypeReference; 9 | import com.alibaba.fastjson.serializer.SerializerFeature; 10 | 11 | /** 12 | * json 工具类,依赖fastjson,反序列化对象时支持嵌套引用; 13 | * 14 | * @author flatychen 15 | * @date 2014-5-21 16 | */ 17 | public class FastJsonUtils { 18 | 19 | /** 20 | * 默认时间格式 21 | */ 22 | private static String dateFormat = "yyyy-MM-dd HH:mm:ss"; 23 | 24 | /** 25 | * fastjson 默认转换,时间为long类型,支持所有对象 26 | * 27 | * @author flatychen 28 | * @date 2014-5-21 29 | * @param o 30 | * 31 | * @return 32 | */ 33 | public static String toJsonString(Object o) { 34 | return JSON.toJSONString(o); 35 | } 36 | 37 | /** 38 | * 使用时间{yyyy-MM-dd HH:mm:ss}的转换; 39 | * 40 | * @author flatychen 41 | * @date 2014-5-21 42 | * @param o 43 | * @return 44 | */ 45 | public static String toJsonStringWithDefaultDateFormat(Object o) { 46 | return toJsonStringWithDateFormat(o, dateFormat); 47 | } 48 | 49 | /** 50 | * 带时间格时的转换 51 | * 52 | * @author flatychen 53 | * @date 2014-5-21 54 | * @param o 55 | * @param dateFormat 56 | * 时间格式 57 | * @return 58 | */ 59 | public static String toJsonStringWithDateFormat(Object o, String dateFormat) { 60 | return JSON.toJSONStringWithDateFormat(o, dateFormat, new SerializerFeature[0]); 61 | } 62 | 63 | /** 64 | * 反列化对象,支持嵌套引用 65 | * @author flatychen 66 | * @date 2014-5-21 67 | * @param json 68 | * @param clazz class类型 69 | * @return 70 | */ 71 | public static T praseToObject(String json, Class clazz) { 72 | return JSON.parseObject(json, clazz); 73 | } 74 | 75 | /** 76 | * 反列化数组类,JSON格式必须为[],支持嵌套引用 77 | * @author flatychen 78 | * @date 2014-5-21 79 | * @param json 80 | * @param clazz 81 | * @return 82 | */ 83 | public static List praseToList(String json, Class clazz) { 84 | return JSON.parseArray(json, clazz); 85 | } 86 | 87 | 88 | /** 89 | * 解析成Map,用于从JSON从筛选数据再解析; 90 | * @author flatychen 91 | * @date 2014-5-22 92 | * @param json 93 | * @return 94 | */ 95 | public static Map praseToMap(String json) { 96 | return JSON.parseObject(json, new TypeReference>(){}); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/InetSocketUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | public class InetSocketUtils { 6 | 7 | public static String getHostName(InetSocketAddress socket) { 8 | return socket.getHostName(); 9 | } 10 | 11 | public static String getPort(InetSocketAddress socket) { 12 | return socket.getPort()+""; 13 | } 14 | 15 | public static String getSocketAddress(InetSocketAddress socket) { 16 | return getHostName(socket) + ":" + getPort(socket); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/MyCharsetUtil.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | 4 | import java.nio.charset.Charset; 5 | 6 | public final class MyCharsetUtil { 7 | 8 | public static final Charset UTF_8 = Charset.forName("UTF-8"); 9 | 10 | public static final Charset GBK = Charset.forName("gbk"); 11 | 12 | public static final Charset GB2312 = Charset.forName("gb2312 "); 13 | 14 | public static final Charset US_ASCII = Charset.forName("US-ASCII"); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/RuntimeUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | public class RuntimeUtils { 4 | 5 | private static Runtime runtime = Runtime.getRuntime(); 6 | 7 | public static int getProcessors(){ 8 | return runtime.availableProcessors() ; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/NettyPush/utils/beanFactoryUtils.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.NettyPush.utils; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | import cn.flaty.NettyPush.services.ClientDispacherService; 7 | import cn.flaty.NettyPush.services.PushService; 8 | 9 | 10 | public class beanFactoryUtils { 11 | 12 | 13 | private final static String DESERIALIZESERVICE_NAME = "clientDispacherService"; 14 | private final static String PUSHSERVICE_NAME = "pushService"; 15 | 16 | private static ApplicationContext context; 17 | 18 | public static void setApplicationContext(ApplicationContext applicationContext) 19 | throws BeansException { 20 | beanFactoryUtils.context = applicationContext; 21 | } 22 | 23 | public static T getBean(String name,Class clazz){ 24 | return context.getBean(name, clazz); 25 | } 26 | 27 | 28 | public static ClientDispacherService getClientDispacherService(){ 29 | return getBean(DESERIALIZESERVICE_NAME, ClientDispacherService.class); 30 | } 31 | 32 | 33 | public static PushService getPushService(){ 34 | return getBean(PUSHSERVICE_NAME, PushService.class); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/entity/Message.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.entity; 2 | 3 | /** 4 | * 5 | * 幸存消息,也即未过期消息 6 | * 7 | * @author flatychen 8 | * 9 | * 消息日志表 10 | create table tb_msg_log( 11 | appkey VARCHAR(100), 12 | msgId BIGINT, 13 | title VARCHAR(60), 14 | content VARCHAR(900), 15 | flag int, 16 | pushActionMixin VARCHAR(200), 17 | createTime BIGINT, 18 | expireTime BIGINT 19 | ); 20 | 21 | * 22 | */ 23 | public class Message extends PushMessagePacket { 24 | 25 | private String appKey; 26 | 27 | private long msgId; 28 | 29 | private long createTime; 30 | 31 | private long expireTime; 32 | 33 | public long getMsgId() { 34 | return msgId; 35 | } 36 | 37 | public void setMsgId(long msgId) { 38 | this.msgId = msgId; 39 | } 40 | 41 | 42 | public String getAppKey() { 43 | return appKey; 44 | } 45 | 46 | public void setAppKey(String appKey) { 47 | this.appKey = appKey; 48 | } 49 | 50 | public long getCreateTime() { 51 | return createTime; 52 | } 53 | 54 | public void setCreateTime(long createTime) { 55 | this.createTime = createTime; 56 | } 57 | 58 | public long getExpireTime() { 59 | return expireTime; 60 | } 61 | 62 | public void setExpireTime(long expireTime) { 63 | this.expireTime = expireTime; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/entity/PushMessagePacket.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.entity; 2 | 3 | import cn.flaty.NettyPush.entity.persitence.Client; 4 | 5 | /** 6 | * 推送文本消息报文 7 | * 8 | * @author flatychen 9 | * 10 | */ 11 | public class PushMessagePacket { 12 | 13 | 14 | @Override 15 | public String toString() { 16 | return "PushMessagePacket [title=" + title + ", content=" + content 17 | + ", flag=" + flag + ", pushActionMixin=" + pushActionMixin 18 | + "]"; 19 | } 20 | 21 | private String title; 22 | 23 | private String content; 24 | 25 | private int flag; 26 | 27 | private String pushActionMixin; 28 | 29 | public String getPushActionMixin() { 30 | return pushActionMixin; 31 | } 32 | 33 | public void setPushActionMixin(String pushActionMixin) { 34 | this.pushActionMixin = pushActionMixin; 35 | } 36 | 37 | public String getTitle() { 38 | return title; 39 | } 40 | 41 | public void setTitle(String title) { 42 | this.title = title; 43 | } 44 | 45 | public String getContent() { 46 | return content; 47 | } 48 | 49 | public void setContent(String content) { 50 | this.content = content; 51 | } 52 | 53 | public int getFlag() { 54 | return flag; 55 | } 56 | 57 | public void setFlag(int flag) { 58 | this.flag = flag; 59 | } 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/entity/SendedMessage.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.entity; 2 | 3 | /** 4 | * 5 | * 幸存消息,也即未过期消息 6 | * 7 | * @author flatychen 8 | * 9 | */ 10 | public class SendedMessage { 11 | 12 | private String appKey; 13 | 14 | private String did; 15 | 16 | private long msgId; 17 | 18 | public String getDid() { 19 | return did; 20 | } 21 | 22 | public void setDid(String did) { 23 | this.did = did; 24 | } 25 | 26 | public String getAppKey() { 27 | return appKey; 28 | } 29 | 30 | public void setAppKey(String appKey) { 31 | this.appKey = appKey; 32 | } 33 | 34 | public long getMsgId() { 35 | return msgId; 36 | } 37 | 38 | public void setMsgId(long msgId) { 39 | this.msgId = msgId; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/repository/ClientMessageRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.repository; 2 | 3 | import java.util.List; 4 | 5 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 6 | import cn.flaty.pushAdmin.entity.Message; 7 | import cn.flaty.pushAdmin.entity.SendedMessage; 8 | 9 | 10 | public interface ClientMessageRepository { 11 | 12 | public boolean insertMessage(Message msg); 13 | 14 | public boolean insertSendedMessage(SendedMessage sm); 15 | 16 | public List queryClientMessage(ClientPacket clientInfo); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/repository/H2ClientMessageRepository.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.repository; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import cn.flaty.NettyPush.entity.packet.ClientPacket; 10 | import cn.flaty.NettyPush.repository.JdbcTemplateWrapper; 11 | import cn.flaty.pushAdmin.entity.Message; 12 | import cn.flaty.pushAdmin.entity.SendedMessage; 13 | 14 | @Repository 15 | public class H2ClientMessageRepository implements ClientMessageRepository { 16 | 17 | @Autowired 18 | private JdbcTemplateWrapper jdbc; 19 | 20 | @Override 21 | public boolean insertMessage(Message m) { 22 | String sql = " insert into tb_message( appkey,msgId, title ,content, flag , pushActionMixin ,createTime,expireTime) values(?,?,?,?,?,?,?,?)"; 23 | return jdbc 24 | .saveORUpdate( 25 | sql, 26 | new Object[] { m.getAppKey(), new Date().getTime(), 27 | m.getTitle(), m.getContent(), m.getFlag(), 28 | m.getPushActionMixin(), new Date().getTime(), 29 | m.getExpireTime() }) == 1; 30 | } 31 | 32 | @Override 33 | public List queryClientMessage(ClientPacket clientInfo) { 34 | String sql = " select appkey,msgId, title ,content, flag , pushActionMixin ,createTime,expireTime from tb_message a where a.appkey = ? and a.expireTime >= ? and not exists ( select * from tb_sended_msg b where a.msgid = b.msgid and a.appkey=b.appkey)"; 35 | return jdbc.queryForBeanList(sql, Message.class, new Object[] { 36 | clientInfo.getAppKey(), new Date().getTime() }); 37 | } 38 | 39 | @Override 40 | public boolean insertSendedMessage(SendedMessage sm) { 41 | String sql = " insert into tb_sended_msg(appkey,did,msgid) values(?,?,?)"; 42 | return jdbc.saveORUpdate(sql, 43 | new Object[] { sm.getAppKey(), sm.getDid(), sm.getMsgId() }) == 1; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/services/DefaultListenerProxy.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.services; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationListener; 6 | import org.springframework.context.event.ContextRefreshedEvent; 7 | import org.springframework.core.task.TaskExecutor; 8 | import org.springframework.stereotype.Component; 9 | 10 | import cn.flaty.NettyPush.server.Listener; 11 | import cn.flaty.NettyPush.utils.beanFactoryUtils; 12 | 13 | @Component 14 | public class DefaultListenerProxy implements ApplicationListener{ 15 | 16 | @Autowired 17 | private TaskExecutor exe; 18 | 19 | @Autowired 20 | private Listener listener; 21 | 22 | @Override 23 | public void onApplicationEvent(ContextRefreshedEvent event) { 24 | ApplicationContext context= event.getApplicationContext(); 25 | // 父容器启动 26 | if(context.getParent() == null){ 27 | beanFactoryUtils.setApplicationContext(context); 28 | exe.execute(new Runnable() { 29 | @Override 30 | public void run() { 31 | listener.start(); 32 | } 33 | });; 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/services/PushServiceProxy.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.services; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import cn.flaty.NettyPush.services.PushService; 9 | import cn.flaty.pushAdmin.entity.PushMessagePacket; 10 | import cn.flaty.pushAdmin.views.BaseDataWrapper; 11 | import cn.flaty.pushAdmin.views.push.PushMessageForm; 12 | 13 | @Service 14 | public class PushServiceProxy { 15 | 16 | @Autowired 17 | private PushService pushService; 18 | 19 | private Logger log = LoggerFactory.getLogger(PushServiceProxy.class); 20 | 21 | /** 22 | * 消息推送 23 | * 24 | * @param pushMessageBean 25 | * @return 26 | */ 27 | public BaseDataWrapper push(PushMessageForm pushMessageBean) { 28 | log.info("pushMessage: {}",pushMessageBean.toString()); 29 | BaseDataWrapper json = new BaseDataWrapper(); 30 | PushMessagePacket pushMessage = null; 31 | 32 | // 消息序列化 33 | try { 34 | pushMessage = pushMessageBean.parsePushMessage(); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | json.setSuccess(false); 38 | return json; 39 | 40 | } 41 | 42 | // 推送消息 43 | try { 44 | pushService.sendOnLineClient(pushMessageBean.getClient(),pushMessage); 45 | } catch (Exception e) { 46 | json.setSuccess(false); 47 | return json; 48 | } 49 | json.setSuccess(true); 50 | return json; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/views/BaseDataWrapper.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.views; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | 9 | /** 10 | * 基础数据包装器 11 | * 12 | * @author flatychen 13 | * 14 | */ 15 | public class BaseDataWrapper { 16 | 17 | /** 18 | * 调用是否成功 19 | */ 20 | private boolean success; 21 | 22 | /** 23 | * 调用返回消息,如为成功可为空 24 | */ 25 | private String message; 26 | 27 | 28 | /** 29 | * 调用返回数据 30 | */ 31 | private Map data; 32 | 33 | public Map getData() { 34 | return data; 35 | } 36 | 37 | 38 | public void setData(Map data) { 39 | this.data = data; 40 | } 41 | 42 | /** 43 | * 初始化数据包装器大小 44 | * @author flatychen 45 | * @date 2014-7-11 46 | * @param mapSize 47 | * @version 48 | */ 49 | public BaseDataWrapper buildWrapper(int mapSize){ 50 | if(this.data != null){ 51 | throw new IllegalArgumentException("----> map 重复初始化! "); 52 | } 53 | this.data = new HashMap(mapSize); 54 | return this; 55 | } 56 | 57 | 58 | /** 59 | * 初始化数据包装器 60 | * @author flatychen 61 | * @date 2014-7-11 62 | * @param mapSize 63 | * @version 64 | */ 65 | public BaseDataWrapper buildWrapper(){ 66 | return this.buildWrapper(5); 67 | } 68 | 69 | public boolean isSuccess() { 70 | return success; 71 | } 72 | 73 | 74 | public void setSuccess(boolean success) { 75 | this.success = success; 76 | } 77 | 78 | public String getMessage() { 79 | // 默认添加message 80 | if( StringUtils.isBlank(message)){ 81 | if(this.success){ 82 | this.message = "成功!"; 83 | }else{ 84 | this.message = "失败!"; 85 | } 86 | } 87 | return message; 88 | } 89 | 90 | 91 | public void setMessage(String message) { 92 | this.message = message; 93 | } 94 | 95 | 96 | 97 | 98 | } -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/views/BaseInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.views; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 8 | 9 | /** 10 | * 11 | * 12 | * 基础拦截器 13 | * @author flaty 14 | * 15 | */ 16 | @Controller 17 | public class BaseInterceptor extends HandlerInterceptorAdapter { 18 | 19 | @Override 20 | public boolean preHandle(HttpServletRequest request, 21 | HttpServletResponse response, Object handler) throws Exception { 22 | this.addBaseContext(request); // 添加web上下文路径 23 | return true; 24 | } 25 | 26 | private void addBaseContext(HttpServletRequest request) { 27 | String base = request.getContextPath(); 28 | String servletPath = request.getServletPath(); 29 | request.setAttribute(WebConstants.BASE, request.getScheme() + "://" 30 | + request.getServerName() + ":" + request.getServerPort() 31 | + base); 32 | request.setAttribute(WebConstants.WEBAPP_PATH, 33 | request.getScheme() + "://" + request.getServerName() + ":" 34 | + request.getServerPort()); 35 | request.setAttribute(WebConstants.CURRENT_PATH, 36 | request.getAttribute(WebConstants.BASE) + servletPath); 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/views/WebConstants.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.views; 2 | 3 | /** 4 | * 5 | * WEB常用的常量 6 | * 7 | * @author flatychen 8 | * 9 | */ 10 | public final class WebConstants { 11 | 12 | /** 13 | * 验证码常量 14 | */ 15 | public static final String SECURIYT_CODE = "securityCode"; 16 | 17 | /** 18 | * 分页名称 19 | */ 20 | public static final String PAGEBEANNAME = "pageBean"; 21 | 22 | /** 23 | * token 24 | */ 25 | public static final String TOKENNAME = "token"; 26 | 27 | 28 | /** 29 | * web 上下文环境 30 | */ 31 | public static final String BASE = "base"; 32 | 33 | 34 | /** 35 | * 当前访问URL 36 | */ 37 | public static final String WEBAPP_PATH = "webapp_path"; 38 | 39 | /** 40 | * 当前访问URL 41 | */ 42 | public static final String CURRENT_PATH = "url"; 43 | 44 | 45 | /** 46 | * 企业用户session常量 47 | */ 48 | public static final String USER_SESSION = "user_session"; 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/views/push/PushMessageController.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.views.push; 2 | 3 | import javax.validation.Valid; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | import cn.flaty.NettyPush.entity.persitence.Client; 13 | import cn.flaty.pushAdmin.services.PushServiceProxy; 14 | import cn.flaty.pushAdmin.views.BaseDataWrapper; 15 | 16 | @Controller 17 | @RequestMapping("/pushMessage") 18 | public class PushMessageController { 19 | 20 | private static Logger log = LoggerFactory 21 | .getLogger(PushMessageController.class); 22 | 23 | @Autowired 24 | private PushServiceProxy pushServiceProxy; 25 | 26 | @RequestMapping("/new") 27 | public String news() { 28 | return "pushMessage/new"; 29 | } 30 | 31 | @RequestMapping("/create") 32 | @ResponseBody 33 | public BaseDataWrapper create(@Valid PushMessageForm msg) { 34 | 35 | // 手动设定过滤信息 36 | Client clientsParams = new Client(); 37 | clientsParams.setAppKey("apptest"); 38 | 39 | msg.setClient(clientsParams); 40 | return pushServiceProxy.push(msg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/flaty/pushAdmin/views/push/PushMessageForm.java: -------------------------------------------------------------------------------- 1 | package cn.flaty.pushAdmin.views.push; 2 | 3 | import java.util.Arrays; 4 | import java.util.Date; 5 | 6 | import org.apache.commons.beanutils.PropertyUtils; 7 | import org.hibernate.validator.constraints.NotEmpty; 8 | 9 | import cn.flaty.NettyPush.entity.persitence.Client; 10 | import cn.flaty.pushAdmin.entity.PushMessagePacket; 11 | 12 | /** 13 | * 14 | * 推送消处表单 15 | * 16 | * @author flatychen 17 | * 18 | */ 19 | public class PushMessageForm { 20 | 21 | private Client client; 22 | 23 | /** 24 | * 标题 25 | */ 26 | @NotEmpty 27 | private String title; 28 | 29 | /** 30 | * 内容 31 | */ 32 | @NotEmpty 33 | private String content; 34 | 35 | @NotEmpty 36 | private byte[] flags; 37 | 38 | private int pushAction; 39 | 40 | private String openUrl; 41 | 42 | private String openActivity; 43 | 44 | private Date expireDate; 45 | 46 | public Client getClient() { 47 | return client; 48 | } 49 | 50 | public void setClient(Client client) { 51 | this.client = client; 52 | } 53 | 54 | public Date getExpireDate() { 55 | return expireDate; 56 | } 57 | 58 | public void setExpireDate(Date expireDate) { 59 | this.expireDate = expireDate; 60 | } 61 | 62 | public byte[] getFlags() { 63 | return flags; 64 | } 65 | 66 | public void setFlags(byte[] flags) { 67 | this.flags = flags; 68 | } 69 | 70 | public int getPushAction() { 71 | return pushAction; 72 | } 73 | 74 | public void setPushAction(int pushAction) { 75 | this.pushAction = pushAction; 76 | } 77 | 78 | public void setPushAction(byte pushAction) { 79 | this.pushAction = pushAction; 80 | } 81 | 82 | public String getOpenUrl() { 83 | return openUrl; 84 | } 85 | 86 | public void setOpenUrl(String openUrl) { 87 | this.openUrl = openUrl; 88 | } 89 | 90 | public String getOpenActivity() { 91 | return openActivity; 92 | } 93 | 94 | public void setOpenActivity(String openActivity) { 95 | this.openActivity = openActivity; 96 | } 97 | 98 | public String getTitle() { 99 | return title; 100 | } 101 | 102 | public void setTitle(String title) { 103 | this.title = title; 104 | } 105 | 106 | public String getContent() { 107 | return content; 108 | } 109 | 110 | public void setContent(String content) { 111 | this.content = content; 112 | } 113 | 114 | public PushMessagePacket parsePushMessage() { 115 | PushMessagePacket pm = new PushMessagePacket(); 116 | 117 | for (int i = 0; i < this.flags.length; i++) { 118 | pm.setFlag(pm.getFlag() | this.flags[i]); 119 | } 120 | 121 | try { 122 | PropertyUtils.copyProperties(pm, this); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | return null; 126 | } 127 | 128 | return pm; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/config/jdbc.properties: -------------------------------------------------------------------------------- 1 | 2 | #mysql 3 | #jdbc.driverClassName=com.mysql.jdbc.Driver 4 | #jdbc.url=jdbc:mysql://127.0.0.1:3306/pushdb?characterEncoding=UTF-8 5 | #jdbc.username=root 6 | #jdbc.password=123456 7 | 8 | 9 | 10 | #h2 memdb 11 | #jdbc.driverClassName=org.h2.Driver 12 | #jdbc.url=jdbc:h2:mem:push 13 | #jdbc.username=sa 14 | #jdbc.password= 15 | 16 | 17 | #h2 Embed db 18 | jdbc.driverClassName=org.h2.Driver 19 | jdbc.url=jdbc:log4jdbc:h2:mem:push;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 20 | jdbc.username=sa 21 | jdbc.password= 22 | #h2 console 23 | h2_url=http://127.0.0.1:8080/push/console -------------------------------------------------------------------------------- /src/main/resources/config/server.properties: -------------------------------------------------------------------------------- 1 | server.port=11111 2 | server.host=0.0.0.0 -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [%level]%d{HH:mm:ss.SSS}[%thread -> %logger{40}] %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/nosql/applicationRedis.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/spring/applicationDataSource.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/resources/spring/applicationRescourse.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/spring/applicationServer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/springMvc/applicationMvc.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 18 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/resources/sql/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists tb_online_client; 2 | drop table if exists tb_sended_msg; 3 | drop table if exists tb_message; 4 | 5 | 6 | 7 | -- 在线用户表 8 | create table tb_online_client( 9 | appkey VARCHAR(100), 10 | did VARCHAR(100), 11 | appVer VARCHAR(20), 12 | os VARCHAR(20), 13 | expireTime BIGINT 14 | ); 15 | 16 | -- 已发送消息表 17 | create table tb_sended_msg( 18 | appkey VARCHAR(100), 19 | did VARCHAR(100), 20 | msgId BIGINT 21 | ); 22 | 23 | -- 消息日志表 24 | create table tb_message( 25 | appkey VARCHAR(100), 26 | msgId BIGINT, 27 | title VARCHAR(60), 28 | content VARCHAR(900), 29 | flag int, 30 | pushActionMixin VARCHAR(200), 31 | createTime BIGINT, 32 | expireTime BIGINT 33 | ); 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/task/applicationTask.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/decorators.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /console/* 5 | 6 | 7 | /* 8 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/common/base.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 | <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> 5 | <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fun" %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 |
28 |
29 | 后台管理模板 30 |
31 | 32 |
33 | 43 |
44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 | 63 |
64 | 65 | 66 | 67 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/common/menus.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 |
3 | 13 | 14 |
15 |
16 |

公告

17 |

时光静好,与君语;细水流年,与君同。—— Amaze UI

18 |
19 |
20 | 21 |
22 |
23 |

wiki

24 |

Welcome to the Amaze UI wiki!

25 |
26 |
27 |
-------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/pushMessage/new.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> 4 | <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fun"%> 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 消息推送 / 新消息 13 |
14 |
15 |
16 |
18 |
19 |

新消息

20 |
21 | 27 | 28 |
29 | 32 |
33 | 34 |
35 | 36 | 38 |
39 | 40 |
41 | 42 | 44 |
45 | 46 |
47 | 48 |
49 |

后续操作

50 |
51 | 54 | 57 | 60 |
61 |
62 | 65 |
66 |
67 | 70 |
71 | 72 | 73 | 74 |
75 |

提醒方式

76 |
77 | 80 | 83 | 86 |
87 | 88 |

89 | 90 |

91 |
92 |
93 | 94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/jsp/pushMessage/query.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> 4 | <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fun" %> 5 | 6 | 7 | 8 | 9 | 10 | a test 11 | 12 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | log4jConfigLocation 8 | classpath:log4j.properties 9 | 10 | 11 | 12 | contextConfigLocation 13 | classpath:applicationContext.xml 14 | 15 | 16 | org.springframework.web.context.ContextLoaderListener 17 | 18 | 19 | 20 | 21 | *.jsp 22 | true 23 | 24 | 25 | 26 | 27 | 28 | encodingFilter 29 | org.springframework.web.filter.CharacterEncodingFilter 30 | 31 | encoding 32 | UTF-8 33 | 34 | 35 | forceEncoding 36 | true 37 | 38 | 39 | 40 | 41 | sitemesh 42 | com.opensymphony.sitemesh.webapp.SiteMeshFilter 43 | true 44 | 45 | 46 | 47 | 48 | 49 | encodingFilter 50 | /* 51 | 52 | 53 | 54 | sitemesh 55 | /* 56 | 57 | 58 | 59 | 60 | 61 | springServlet 62 | org.springframework.web.servlet.DispatcherServlet 63 | 64 | contextConfigLocation 65 | classpath:springMvc/applicationMvc.xml 66 | 67 | 1 68 | 69 | 70 | springServlet 71 | / 72 | 73 | 74 | 75 | 76 | H2Console 77 | org.h2.server.web.WebServlet 78 | 2 79 | 80 | 81 | H2Console 82 | /console/* 83 | 84 | 85 | 86 | 87 | index.jsp 88 | 89 | -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> 2 | <% 3 | String url = "/pushMessage/new"; 4 | response.sendRedirect(request.getContextPath() + url); 5 | %> 6 | -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/css/admin.css: -------------------------------------------------------------------------------- 1 | /** 2 | * admin.css 3 | */ 4 | 5 | ul { 6 | margin-top: 0; 7 | } 8 | 9 | .admin-icon-yellow { 10 | color: #ffbe40; 11 | } 12 | 13 | .admin-header { 14 | font-size: 1.4rem; 15 | margin-bottom: 0; 16 | } 17 | 18 | .admin-header-list a:hover :after { 19 | content: none; 20 | } 21 | 22 | .admin-main { 23 | background: #f3f3f3; 24 | } 25 | 26 | .admin-menu { 27 | position: fixed; 28 | bottom: 30px; 29 | right: 20px; 30 | } 31 | 32 | .admin-sidebar { 33 | width: 260px; 34 | min-height: 100%; 35 | float: left; 36 | border-right: 1px solid #cecece; 37 | } 38 | 39 | .admin-sidebar-list { 40 | margin-bottom: 0; 41 | } 42 | 43 | .admin-sidebar-list li a { 44 | color: #5c5c5c; 45 | padding-left: 24px; 46 | } 47 | 48 | .admin-sidebar-list li:first-child { 49 | border-top: none; 50 | } 51 | 52 | .admin-sidebar-sub { 53 | margin-top: 0; 54 | margin-bottom: 0; 55 | box-shadow: 0 16px 8px -15px #e2e2e2 inset; 56 | background: #ececec; 57 | padding-left: 24px; 58 | } 59 | 60 | .admin-sidebar-sub li:first-child { 61 | border-top: 1px solid #dedede; 62 | } 63 | 64 | .admin-sidebar-panel { 65 | margin: 10px; 66 | } 67 | 68 | .admin-content { 69 | width: auto; 70 | overflow: hidden; 71 | height: 100%; 72 | background: #fff; 73 | } 74 | 75 | .admin-content-list { 76 | border: 1px solid #e9ecf1; 77 | margin-top: 0; 78 | } 79 | 80 | .admin-content-list li { 81 | border: 1px solid #e9ecf1; 82 | border-width: 0 1px; 83 | margin-left: -1px; 84 | } 85 | 86 | .admin-content-list li:first-child { 87 | border-left: none; 88 | } 89 | 90 | .admin-content-list li:last-child { 91 | border-right: none; 92 | } 93 | 94 | .admin-content-table a { 95 | color: #535353; 96 | } 97 | .admin-content-file { 98 | margin-bottom: 0; 99 | color: #666; 100 | } 101 | 102 | .admin-content-file p { 103 | margin: 0 0 5px 0; 104 | font-size: 1.4rem; 105 | } 106 | 107 | .admin-content-file li { 108 | padding: 10px 0; 109 | } 110 | 111 | .admin-content-file li:first-child { 112 | border-top: none; 113 | } 114 | 115 | .admin-content-file li:last-child { 116 | border-bottom: none; 117 | } 118 | 119 | .admin-content-file li .am-progress { 120 | margin-bottom: 4px; 121 | } 122 | 123 | .admin-content-file li .am-progress-bar { 124 | line-height: 14px; 125 | } 126 | 127 | .admin-content-task { 128 | margin-bottom: 0; 129 | } 130 | 131 | .admin-content-task li { 132 | padding: 5px 0; 133 | border-color: #eee; 134 | } 135 | 136 | .admin-content-task li:first-child { 137 | border-top: none; 138 | } 139 | 140 | .admin-content-task li:last-child { 141 | border-bottom: none; 142 | } 143 | 144 | .admin-task-meta { 145 | font-size: 1.2rem; 146 | color: #999; 147 | } 148 | 149 | .admin-task-bd { 150 | font-size: 1.4rem; 151 | margin-bottom: 5px; 152 | } 153 | 154 | .admin-content-comment { 155 | margin-bottom: 0; 156 | } 157 | 158 | .admin-content-comment .am-comment-bd { 159 | font-size: 1.4rem; 160 | } 161 | 162 | .admin-content-pagination { 163 | margin-bottom: 0; 164 | } 165 | .admin-content-pagination li a { 166 | padding: 4px 8px; 167 | } 168 | 169 | @media only screen and (min-width: 641px) { 170 | .admin-sidebar { 171 | display: block; 172 | position: static; 173 | background: none; 174 | } 175 | 176 | .admin-offcanvas-bar { 177 | position: static; 178 | width: auto; 179 | background: none; 180 | -webkit-transform: translate3d(0, 0, 0); 181 | -ms-transform: translate3d(0, 0, 0); 182 | transform: translate3d(0, 0, 0); 183 | } 184 | .admin-offcanvas-bar:after { 185 | content: none; 186 | } 187 | } 188 | 189 | @media only screen and (max-width: 640px) { 190 | .admin-sidebar { 191 | width: inherit; 192 | } 193 | 194 | .admin-offcanvas-bar { 195 | background: #f3f3f3; 196 | } 197 | 198 | .admin-offcanvas-bar:after { 199 | background: #BABABA; 200 | } 201 | 202 | .admin-sidebar-list a:hover, .admin-sidebar-list a:active{ 203 | -webkit-transition: background-color .3s ease; 204 | -moz-transition: background-color .3s ease; 205 | -ms-transition: background-color .3s ease; 206 | -o-transition: background-color .3s ease; 207 | transition: background-color .3s ease; 208 | background: #E4E4E4; 209 | } 210 | 211 | .admin-content-list li { 212 | padding: 10px; 213 | border-width: 1px 0; 214 | margin-top: -1px; 215 | } 216 | 217 | .admin-content-list li:first-child { 218 | border-top: none; 219 | } 220 | 221 | .admin-content-list li:last-child { 222 | border-bottom: none; 223 | } 224 | 225 | .admin-form-text { 226 | text-align: left !important; 227 | } 228 | 229 | } 230 | 231 | /* 232 | * user.html css 233 | */ 234 | .user-info { 235 | margin-bottom: 15px; 236 | } 237 | 238 | .user-info .am-progress { 239 | margin-bottom: 4px; 240 | } 241 | 242 | .user-info p { 243 | margin: 5px; 244 | } 245 | 246 | .user-info-order { 247 | font-size: 1.4rem; 248 | } 249 | 250 | /* 251 | * errorLog.html css 252 | */ 253 | 254 | .error-log .am-pre-scrollable { 255 | max-height: 40rem; 256 | } 257 | 258 | /* 259 | * table.html css 260 | */ 261 | 262 | .table-main { 263 | font-size: 1.4rem; 264 | padding: .5rem; 265 | } 266 | 267 | .table-main button { 268 | background: #fff; 269 | } 270 | 271 | .table-check { 272 | width: 30px; 273 | } 274 | 275 | .table-id { 276 | width: 50px; 277 | } 278 | 279 | @media only screen and (max-width: 640px) { 280 | .table-select { 281 | margin-top: 10px; 282 | margin-left: 5px; 283 | } 284 | } 285 | 286 | /* 287 | gallery.html css 288 | */ 289 | 290 | .gallery-list li { 291 | padding: 10px; 292 | } 293 | 294 | .gallery-list a { 295 | color: #666; 296 | } 297 | 298 | .gallery-list a:hover { 299 | color: #3bb4f2; 300 | } 301 | 302 | .gallery-title { 303 | margin-top: 6px; 304 | font-size: 1.4rem; 305 | } 306 | 307 | .gallery-desc { 308 | font-size: 1.2rem; 309 | margin-top: 4px; 310 | } 311 | 312 | /* 313 | 404.html css 314 | */ 315 | 316 | .page-404 { 317 | background: #fff; 318 | border: none; 319 | width: 200px; 320 | margin: 0 auto; 321 | } 322 | -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/css/app.css: -------------------------------------------------------------------------------- 1 | /* Write your styles */ -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatychen/pushServer/b408a02c698ba0b9547764a8fea592b93c2a2c8b/src/main/webapp/res/amazonUi/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatychen/pushServer/b408a02c698ba0b9547764a8fea592b93c2a2c8b/src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatychen/pushServer/b408a02c698ba0b9547764a8fea592b93c2a2c8b/src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatychen/pushServer/b408a02c698ba0b9547764a8fea592b93c2a2c8b/src/main/webapp/res/amazonUi/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/js/amazeui.widgets.helper.min.js: -------------------------------------------------------------------------------- 1 | /*! Amaze UI v2.1.0 ~ helper | by Amaze UI Team | (c) 2015 AllMobilize, Inc. | Licensed under MIT | 2015-01-13T02:01:01 UTC */ 2 | (function(){"use strict";var i=function(i){i.registerHelper("ifCond",function(i,n,t,a){switch(n){case"==":return i==t?a.fn(this):a.inverse(this);case"===":return i===t?a.fn(this):a.inverse(this);case"<":return t>i?a.fn(this):a.inverse(this);case"<=":return t>=i?a.fn(this):a.inverse(this);case">":return i>t?a.fn(this):a.inverse(this);case">=":return i>=t?a.fn(this):a.inverse(this);default:return a.inverse(this)}return a.inverse(this)})};"undefined"!=typeof module&&module.exports&&(module.exports=i),this.Handlebars&&i(this.Handlebars)}).call(this),function(){"use strict";var i=function(i){i.registerPartial("accordion",'{{#this}}\n
\n {{#each content}}\n
\n
\n {{{title}}}\n
\n
\n \n
\n {{{content}}}\n
\n
\n
\n {{/each}}\n
\n{{/this}}\n'),i.registerPartial("divider",'{{#this}}\n
\n{{/this}}\n'),i.registerPartial("duoshuo",'{{#this}}\n
\n
\n
\n
\n{{/this}}'),i.registerPartial("figure",'{{#this}}\n
\n {{#if content.link}}{{/if}}\n\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'top\'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{/if}}\n\n {{#if content.img}}\n {{#if content.imgAlt}}{{content.imgAlt}}{{else}}{{content.figcaption}}{{/if}}\n {{/if}}\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'bottom\'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{else}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/if}}\n\n {{#if content.link}}
{{/if}}\n
\n{{/this}}\n'),i.registerPartial("footer",'{{#this}}\n \n\n \n{{/this}}\n'),i.registerPartial("gallery",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("gotop",'{{#this}}\n \n{{/this}}'),i.registerPartial("header",'{{#this}}\n
\n {{#if content.left}}\n \n {{/if}}\n\n {{#if content.title}}\n

\n {{#if content.link}}\n \n {{{content.title}}}\n \n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.right}}\n \n {{/if}}\n
\n{{/this}}'),i.registerPartial("intro",'{{#this }}\n
\n {{#if content.title}}\n
\n

{{{content.title}}}

\n {{#if content.more.link}}\n {{#ifCond options.position \'==\' \'top\'}}\n {{content.more.title}}\n {{/ifCond}}\n {{/if}}\n
\n {{/if}}\n\n
\n {{#if content.left}}\n {{{content.left}}}
\n {{/if}}\n {{#if content.right}}\n {{{content.right}}}
\n {{/if}}\n \n {{#ifCond options.position \'==\' \'bottom\'}}\n \n {{/ifCond}}\n \n{{/this}}\n'),i.registerPartial("list_news",'{{#this}}\n
\n \n {{#if content.header.title}}\n \n {{/if}}\n\n
\n
    \n {{#ifCond options.type \'==\' \'thumb\'}}\n {{#ifCond options.thumbPosition \'==\' \'top\'}} \n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if img}}\n
    \n \n {{title}}\n \n {{#if thumbAddition}}\n
    {{{thumbAddition}}}
    \n {{/if}}\n
    \n {{/if}}\n\n
    \n {{#if title}}\n

    {{{title}}}

    \n {{/if}}\n\n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
    \n
  • \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition \'==\' \'bottom-left\'}} \n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n

    {{{title}}}

    \n {{/if}}\n {{#if img}}\n
    \n \n {{title}}\n \n {{#if thumbAddition}}\n
    {{{thumbAddition}}}
    \n {{/if}}\n
    \n {{/if}}\n\n
    \n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
    \n
  • \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition \'==\' \'bottom-right\'}} \n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n

    {{{title}}}

    \n {{/if}}\n\n
    \n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
    \n {{#if img}}\n
    \n \n {{title}}\n \n {{#if thumbAddition}}\n
    {{{thumbAddition}}}
    \n {{/if}}\n
    \n {{/if}}\n
  • \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition \'==\' \'left\'}} \n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if img}}\n
    \n \n {{title}}\n \n {{#if thumbAddition}}\n
    {{{thumbAddition}}}
    \n {{/if}}\n
    \n {{/if}}\n\n
    \n {{#if title}}\n

    {{{title}}}

    \n {{/if}}\n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
    \n
  • \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition \'==\' \'right\'}} \n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n
    \n {{#if title}}\n

    {{{title}}}

    \n {{/if}}\n\n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
    \n {{#if img}}\n
    \n \n {{title}}\n \n {{#if thumbAddition}}\n
    {{{thumbAddition}}}
    \n {{/if}}\n
    \n {{/if}}\n
  • \n {{/each}}\n {{/ifCond}}\n\n {{else}}{{!--不带缩略图--}}\n {{#each content.main}}\n
  • \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n {{{title}}}\n {{/if}}\n\n {{#if date}}\n {{date}}\n {{/if}}\n\n {{#if desc}}\n
    {{{desc}}}
    \n {{/if}}\n\n {{#if mainAddition}}\n
    {{{mainAddition}}}
    \n {{/if}}\n
  • \n {{/each}}\n {{/ifCond}}\n
\n
\n\n {{#ifCond content.header.morePosition \'==\' \'bottom\'}}\n {{#if content.header.link}}\n \n {{/if}}\n {{/ifCond}}\n
\n{{/this}}'),i.registerPartial("map",'{{#this}}\n
\n
\n
\n{{/this}}'),i.registerPartial("mechat",'{{#this}}\n
\n
\n
\n{{/this}}'),i.registerPartial("menu",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("navbar",'{{#this}}\n
\n {{#if content}}\n \n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("pagination",'{{#this}}\n
    \n\n {{#if content.firstTitle}}\n
  • \n {{{content.firstTitle}}}\n
  • \n {{/if}}\n\n {{#if content.prevTitle}}\n
  • \n {{{content.prevTitle}}}\n
  • \n {{/if}}\n\n {{! 移除 options.select,根据主题来判断结构,无奈 handlebars 逻辑处理...}}\n\n {{#if content.page}}\n\n {{#ifCond theme \'==\' \'select\'}}\n
  • \n \n
  • \n {{else}}\n {{#ifCond theme \'==\' \'one\'}}\n
  • \n \n
  • \n {{else}}\n {{#each content.page}}\n
  • \n {{{title}}}\n
  • \n {{/each}}\n {{/ifCond}}\n {{/ifCond}}\n\n {{/if}}\n\n {{#if content.nextTitle}}\n
  • \n {{{content.nextTitle}}}\n
  • \n {{/if}}\n\n {{#if content.lastTitle}}\n
  • \n {{{content.lastTitle}}}\n
  • \n {{/if}}\n
\n{{/this}}'),i.registerPartial("paragraph",'{{#this}}\n
\n\n {{#if content}}\n {{{ content.content }}}\n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("slider",'{{#this}}\n
\n \n
\n{{/this}}'),i.registerPartial("tabs",'{{#this}}\n
\n {{#if content}}\n \n
\n {{#each content}}\n
\n {{{content}}}\n
\n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("titlebar",'{{#this}}\n
\n {{#if content.title}}\n

\n {{#if content.link}}\n {{{content.title}}}\n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.nav}}\n \n {{/if}}\n
\n{{/this}}')};"undefined"!=typeof module&&module.exports&&(module.exports=i),this.Handlebars&&i(this.Handlebars)}.call(this); -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/js/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | 4 | $(function() { 5 | var $fullText = $('.admin-fullText'); 6 | $('#admin-fullscreen').on('click', function() { 7 | $.AMUI.fullscreen.toggle(); 8 | }); 9 | 10 | $(document).on($.AMUI.fullscreen.raw.fullscreenchange, function() { 11 | $.AMUI.fullscreen.isFullscreen ? $fullText.text('关闭全屏') : $fullText.text('开启全屏'); 12 | }); 13 | }); 14 | })(jQuery); 15 | -------------------------------------------------------------------------------- /src/main/webapp/res/amazonUi/js/polyfill/rem.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module: rem - v1.3.2 3 | * Description: A polyfill to parse CSS links and rewrite pixel equivalents into head for non supporting browsers 4 | * Date Built: 2014-07-02 5 | * Copyright (c) 2014 | Chuck Carpenter ,Lucas Serven ; 6 | **/ 7 | !function(a){"use strict";var b=function(){var a=document.createElement("div");return a.style.cssText="font-size: 1rem;",/rem/.test(a.style.fontSize)},c=function(){for(var a=document.getElementsByTagName("link"),b=[],c=0;c0?(r=[],q=[],n=[],d()):g()}},f=function(a,b){for(var c,d=k(a).replace(/\/\*[\s\S]*?\*\//g,""),e=/[\w\d\s\-\/\\\[\]:,.'"*()<>+~%#^$_=|@]+\{[\w\d\s\-\/\\%#:!;,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!;,.'"*()]*\}/g,f=d.match(e),g=/\d*\.?\d+rem/g,h=d.match(g),i=/(.*\/)/,j=i.exec(b)[0],l=/@import (?:url\()?['"]?([^'\)"]*)['"]?\)?[^;]*/gm;null!==(c=l.exec(a));)n.push(j+c[1]);null!==f&&0!==f.length&&(o=o.concat(f),p=p.concat(h))},g=function(){for(var a=/[\w\d\s\-\/\\%#:,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!,.'"*()]*[;}]/g,b=0;b #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b result = jedisTemplate.hgetAll("users"); 80 | watch.stop(); 81 | System.out.println("mapGet:" + watch.toString()); 82 | System.out.println(result.size()); 83 | } 84 | 85 | @Test 86 | public void setPutTest() throws InterruptedException { 87 | final CountDownLatch latch = new CountDownLatch(1); 88 | jedisTemplate.flushDB(); 89 | StopWatch watch = new StopWatch(); 90 | watch.start(); 91 | for (int i = 0; i < loopSize; i++) { 92 | final int j = i; 93 | es.submit(new Runnable() { 94 | @Override 95 | public void run() { 96 | jedisTemplate.sadd("clients", j + "baiwjgg"); 97 | if (j == (loopSize - 1)) { 98 | latch.countDown(); 99 | } 100 | } 101 | 102 | }); 103 | 104 | } 105 | latch.await(); 106 | watch.stop(); 107 | System.out.println("setSet:" + watch.toString()); 108 | } 109 | 110 | @Test 111 | public void setGetTest() throws InterruptedException { 112 | StopWatch watch = new StopWatch(); 113 | watch.start(); 114 | Set s = jedisTemplate.smembers("clients"); 115 | watch.stop(); 116 | System.out.println(s.size()); 117 | System.out.println("getSet:" + watch.toString()); 118 | Thread.sleep(600 * 1000); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/cn/flaty/spring/SpringContextTestCase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2005-2012 springside.org.cn 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | */ 6 | package cn.flaty.spring; 7 | 8 | import org.springframework.test.context.ActiveProfiles; 9 | import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 10 | 11 | import cn.flaty.Profiles; 12 | 13 | /** 14 | * Spring的支持依赖注入的JUnit4 集成测试基类, 相比Spring原基类名字更短. 15 | * 16 | * 子类需要定义applicationContext文件的位置,如: 17 | * 18 | * @ContextConfiguration(locations = { "/applicationContext-test.xml" }) 19 | * 20 | * @author calvin 21 | */ 22 | @ActiveProfiles(Profiles.DEFAULT_PROFILE) 23 | public abstract class SpringContextTestCase extends 24 | AbstractJUnit4SpringContextTests { 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/cn/flaty/spring/SpringTransactionalTestCase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2005-2012 springside.org.cn 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | */ 6 | package cn.flaty.spring; 7 | 8 | import javax.sql.DataSource; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; 13 | 14 | import cn.flaty.Profiles; 15 | 16 | /** 17 | * Spring的支持数据库访问, 事务控制和依赖注入的JUnit4 集成测试基类. 相比Spring原基类名字更短并保存了dataSource变量. 18 | * 19 | * 子类需要定义applicationContext文件的位置, 如: 20 | * 21 | * @ContextConfiguration(locations = { "/applicationContext.xml" }) 22 | * 23 | * @author calvin 24 | */ 25 | @ActiveProfiles(Profiles.DEVELOPMENT) 26 | public abstract class SpringTransactionalTestCase extends 27 | AbstractTransactionalJUnit4SpringContextTests { 28 | 29 | protected DataSource dataSource; 30 | 31 | @Override 32 | @Autowired 33 | public void setDataSource(DataSource dataSource) { 34 | super.setDataSource(dataSource); 35 | this.dataSource = dataSource; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/resources/log4jdbc.log4j2.properties: -------------------------------------------------------------------------------- 1 | log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator 2 | #Optional parameters 3 | #log4jdbc.debug.stack.prefix=^ 4 | #log4jdbc.sqltiming.warn.threshold= 5 | #log4jdbc.dump.sql.select=false 6 | #log4jdbc.dump.sql.insert=false 7 | #log4jdbc.dump.sql.update=false 8 | #log4jdbc.dump.sql.delete=false 9 | log4jdbc.dump.sql.addsemicolon=true 10 | log4jdbc.dump.sql.maxlinelength=0 11 | #log4jdbc.trim.sql.extrablanklines=false 12 | log4jdbc.trim.sql=true -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [%level]%d{HH:mm:ss.SSS}[%thread -> %logger{40}] %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------