转载 https://blog.youkuaiyun.com/qq_16758997/article/details/84561537
服务部署方式参照:https://github.com/mywiki/mpush-doc/blob/master/SUMMARY.md 完成
mpush官方详细开发文档:http://mpush.mydoc.io/?t=134820(ps:不太适合编程初学者查看)
redis默认只能本机访问,需要修改配置文件,请参考https://blog.youkuaiyun.com/weiyangdong/article/details/79916445进行修改
完整web项目demol连接:https://download.youkuaiyun.com/download/qq_16758997/10943141
一、新建一个普通的maven web工程或新建一个web工程再转换为maven工程(文章采用后一种方式,jdk1.8,tomcat 9.0)
二、修改pom.xml文件如下:
-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> -
<modelVersion>4.0.0</modelVersion> -
<groupId>webmpush</groupId> -
<artifactId>webmpush</artifactId> -
<version>0.0.1-SNAPSHOT</version> -
<packaging>war</packaging> -
<dependencies> -
<dependency> -
<groupId>com.github.mpusher</groupId> -
<artifactId>mpush-client</artifactId> -
<version>0.8.0</version> -
</dependency> -
</dependencies> -
<build> -
<sourceDirectory>src</sourceDirectory> -
<resources> -
<resource> -
<directory>src</directory> -
<excludes> -
<exclude>**/*.java</exclude> -
</excludes> -
</resource> -
</resources> -
<plugins> -
<plugin> -
<artifactId>maven-compiler-plugin</artifactId> -
<version>3.7.0</version> -
<configuration> -
<source>1.8</source> -
<target>1.8</target> -
</configuration> -
</plugin> -
<plugin> -
<artifactId>maven-war-plugin</artifactId> -
<version>3.2.1</version> -
<configuration> -
<warSourceDirectory>WebContent</warSourceDirectory> -
</configuration> -
</plugin> -
</plugins> -
</build> -
</project>
新加标签部分,导入mpush相关的jar包
<dependencies>
<dependency>
<groupId>com.github.mpusher</groupId>
<artifactId>mpush-client</artifactId>
<version>0.8.0</version>
</dependency>
</dependencies>
在src目录下新建application.conf文件,文件内容如下
-
################################################################################################################## -
# -
# NOTICE: -
# -
# 系统配置文件,所有列出的项是系统所支持全部配置项 -
# 如果要覆盖某项的值可以添加到mpush.conf中。 -
# -
# 配置文件格式采用HOCON格式。解析库由https://github.com/typesafehub/config提供。 -
# 具体可参照说明文档,比如含有特殊字符的字符串必须用双引号包起来。 -
# -
################################################################################################################## -
mp { -
#基础配置 -
home=${user.dir} //程序工作目录 -
#日志配置 -
log-level=warn -
log-dir=${mp.home}/logs -
log-conf-path=${mp.home}/conf/logback.xml -
#核心配置 -
core { -
max-packet-size=10k //系统允许传输的最大包的大小 -
compress-threshold=10k //数据包启用压缩的临界值,超过该值后对数据进行压缩 -
min-heartbeat=3m //最小心跳间隔 -
max-heartbeat=3m //最大心跳间隔 -
max-hb-timeout-times=2 //允许的心跳连续超时的最大次数 -
session-expired-time=1d //用于快速重连的session 过期时间默认1天 -
epoll-provider=netty //nio:jdk自带,netty:由netty实现 -
} -
#安全配置 -
security { -
#rsa 私钥、公钥key长度为1024;可以使用脚本bin/rsa.sh生成, @see com.mpush.tools.crypto.RSAUtils#main -
private-key="MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA=" -
public-key="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB" -
aes-key-length=16 //AES key 长度 -
} -
#网络配置 -
net { -
local-ip="127.0.0.1" //本地ip, 默认取第一个网卡的本地IP -
public-ip="127.0.0.1" //外网ip, 默认取第一个网卡的外网IP -
connect-server-bind-ip="" //connSrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0) -
connect-server-register-ip=${mp.net.public-ip} //公网ip, 注册到zk中的ip, 默认是public-ip -
connect-server-port=3000 //长链接服务对外端口, 公网端口 -
connect-server-register-attr { //注册到zk里的额外属性,比如配置权重,可在alloc里排序 -
weight:1 -
} -
gateway-server-bind-ip="" //gatewaySrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0) -
gateway-server-register-ip=${mp.net.local-ip} //本地ip, 注册到zk中的ip, 默认是local-ip -
gateway-server-port=3001 //网关服务端口, 内部端口 -
gateway-server-net=tcp //网关服务使用的网络类型tcp/udp/sctp/udt -
gateway-client-port=4000 //UDP 客户端端口 -
gateway-server-multicast="239.239.239.88" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效 -
gateway-client-multicast="239.239.239.99" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效 -
gateway-client-num=1 //网关客户端连接数 -
admin-server-port=3002 //控制台服务端口, 内部端口 -
ws-server-port=0 //websocket对外端口, 公网端口, 0表示禁用websocket -
ws-path="/" //websocket path -
public-host-mapping { //本机局域网IP和公网IP的映射关系, 该配置后续会被废弃 -
//"10.0.10.156":"111.1.32.137" -
//"10.0.10.166":"111.1.33.138" -
} -
snd_buf { //tcp/udp 发送缓冲区大小 -
connect-server=32k -
gateway-server=0 -
gateway-client=0 //0表示使用操作系统默认值 -
} -
rcv_buf { //tcp/udp 接收缓冲区大小 -
connect-server=32k -
gateway-server=0 -
gateway-client=0 //0表示使用操作系统默认值 -
} -
write-buffer-water-mark { //netty 写保护 -
connect-server-low=32k -
connect-server-high=64k -
gateway-server-low=10m -
gateway-server-high=20m -
} -
traffic-shaping { //流量整形配置 -
gateway-client { -
enabled:false -
check-interval:100ms -
write-global-limit:30k -
read-global-limit:0 -
write-channel-limit:3k -
read-channel-limit:0 -
} -
gateway-server { -
enabled:false -
check-interval:100ms -
write-global-limit:0 -
read-global-limit:30k -
write-channel-limit:0 -
read-channel-limit:3k -
} -
connect-server { -
enabled:false -
check-interval:100ms -
write-global-limit:0 -
read-global-limit:100k -
write-channel-limit:3k -
read-channel-limit:3k -
} -
} -
} -
#Zookeeper配置 -
zk { -
server-address="127.0.0.1:2181" //多台机器使用","分隔如:"10.0.10.44:2181,10.0.10.49:2181" @see org.apache.zookeeper.ZooKeeper#ZooKeeper() -
namespace=mpush -
digest=mpush //zkCli.sh acl 命令 addauth digest mpush -
watch-path=/ -
retry { -
#initial amount of time to wait between retries -
baseSleepTimeMs=3s -
#max number of times to retry -
maxRetries=3 -
#max time in ms to sleep on each retry -
maxSleepMs=5s -
} -
connectionTimeoutMs=5s -
sessionTimeoutMs=5s -
} -
#Redis集群配置 -
redis { -
cluster-model=single //single,cluster,sentinel -
sentinel-master:"", -
nodes:["127.0.0.1:6379"] //["127.0.0.1:6379"]格式ip:port -
password="" //your password -
config { -
maxTotal:8, -
maxIdle:4, -
minIdle:1, -
lifo:true, -
fairness:false, -
maxWaitMillis:5000, -
minEvictableIdleTimeMillis:300000, -
softMinEvictableIdleTimeMillis:1800000, -
numTestsPerEvictionRun:3, -
testOnCreate:false, -
testOnBorrow:false, -
testOnReturn:false, -
testWhileIdle:false, -
timeBetweenEvictionRunsMillis:60000, -
blockWhenExhausted:true, -
jmxEnabled:false, -
jmxNamePrefix:pool, -
jmxNameBase:pool -
} -
} -
#HTTP代理配置 -
http { -
proxy-enabled=true//启用Http代理 -
max-conn-per-host=5 //每个域名的最大链接数, 建议web服务nginx超时时间设长一点, 以便保持长链接 -
default-read-timeout=10s //请求超时时间 -
max-content-length=5m //response body 最大大小 -
dns-mapping { //域名映射外网地址转内部IP, 域名部分不包含端口号 -
//"mpush.com":["127.0.0.1:8080", "127.0.0.1:8081"] -
} -
} -
#线程池配置 -
thread { -
pool { -
conn-work:0 //接入服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu) -
gateway-server-work:0 //网关服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu) -
http-work:0 //http proxy netty client work pool size,0表示线程数根据cpu核数动态调整(2*cpu) -
ack-timer:1 //处理ACK消息超时 -
push-task:0 //消息推送中心,推送任务线程池大小, 如果为0表示使用Gateway Server的work线程池,tcp下推荐0 -
gateway-client-work:0 //网关客户端线程池大小,0表示线程数根据cpu核数动态调整(2*cpu),该线程池在客户端运行 -
push-client:2 //消息推送回调处理,该线程池在客户端运行 -
event-bus { //用户处理内部事件分发 -
min:1 -
max:16 -
queue-size:10000 //大量的online,offline -
} -
mq { //用户上下线消息, 踢人等 -
min:1 -
max:4 -
queue-size:10000 -
} -
} -
} -
#推送消息流控 -
push { -
flow-control { //qps = limit/(duration) -
global:{ //针对非广播推送的流控,全局有效 -
limit:5000 //qps = 5000 -
max:0 //UN limit -
duration:1s //1s -
} -
broadcast:{ //针对广播消息的流控,单次任务有效 -
limit:3000 //qps = 3000 -
max:100000 //10w -
duration:1s //1s -
} -
} -
} -
#系统监控配置 -
monitor { -
dump-dir=${mp.home}/tmp -
dump-stack=false //是否定时dump堆栈 -
dump-period=1m //多久监控一次 -
print-log=true //是否打印监控日志 -
profile-enabled=false //开启性能监控 -
profile-slowly-duration=10ms //耗时超过10ms打印日志 -
} -
#SPI扩展配置 -
spi { -
thread-pool-factory:"com.mpush.tools.thread.pool.DefaultThreadPoolFactory" -
dns-mapping-manager:"com.mpush.common.net.HttpProxyDnsMappingManager" -
} -
}
三、新建一个普通的类,需要实现两个接口 PushSender(mpush启动是需要使用), ServletContextListener(web项目的监听器需要继承的类)
-
import java.util.concurrent.CompletableFuture; -
import java.util.concurrent.FutureTask; -
import javax.servlet.ServletContextEvent; -
import javax.servlet.ServletContextListener; -
import com.mpush.api.push.PushContext; -
import com.mpush.api.push.PushResult; -
import com.mpush.api.push.PushSender; -
import com.mpush.api.service.Listener; -
public class ServiceManager implements PushSender, ServletContextListener { -
public static PushSender pushSender = null; -
// 在tomcat启动是启动消息发送服务 -
// 启动一个定时器 -
@Override -
public void contextInitialized(ServletContextEvent arg0) { -
// PushClient PushClient=new PushClient(); -
if (pushSender == null) -
pushSender = PushSender.create(); -
pushSender.start().join(); -
} -
public static PushSender getPushSender() { -
if (pushSender == null) -
pushSender = PushSender.create(); -
return pushSender; -
} -
@Override -
public void contextDestroyed(ServletContextEvent arg0) { -
pushSender.stop(); -
} -
@Override -
public void start(Listener listener) { -
// TODO Auto-generated method stub -
} -
@Override -
public void stop(Listener listener) { -
// TODO Auto-generated method stub -
} -
@Override -
public CompletableFuture<Boolean> start() { -
// TODO Auto-generated method stub -
return null; -
} -
@Override -
public CompletableFuture<Boolean> stop() { -
// TODO Auto-generated method stub -
return null; -
} -
@Override -
public boolean syncStart() { -
// TODO Auto-generated method stub -
return false; -
} -
@Override -
public boolean syncStop() { -
// TODO Auto-generated method stub -
return false; -
} -
@Override -
public void init() { -
// TODO Auto-generated method stub -
} -
@Override -
public boolean isRunning() { -
// TODO Auto-generated method stub -
return false; -
} -
@Override -
public FutureTask<PushResult> send(PushContext context) { -
// TODO Auto-generated method stub -
return null; -
} -
}
在tomcat启动是就启动mpush消息发送服务,采用单例模式,增加静态公用get方法获取详细推送服务实例,共其它类调用
// 在tomcat启动是启动消息发送服务
// 启动一个定时器
@Override
public void contextInitialized(ServletContextEvent arg0) {
// PushClient PushClient=new PushClient();
if (pushSender == null)
pushSender = PushSender.create();
pushSender.start().join();
}
public static PushSender getPushSender() {
if (pushSender == null)
pushSender = PushSender.create();
return pushSender;
}
四、增加web项目监听器配置,修改web.xml文件
-
<?xml version="1.0" encoding="UTF-8"?> -
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> -
<display-name>webpush</display-name> -
<welcome-file-list> -
<welcome-file>index.html</welcome-file> -
<welcome-file>index.htm</welcome-file> -
<welcome-file>index.jsp</welcome-file> -
<welcome-file>default.html</welcome-file> -
<welcome-file>default.htm</welcome-file> -
<welcome-file>default.jsp</welcome-file> -
</welcome-file-list> -
<listener> -
<!-- 这里的类路径是第三步新建的监听器类路径 --> -
<listener-class>com.webpush.service.ServiceManager</listener-class> -
</listener> -
</web-app>
新增自定义web项目的监听器(这里的类路径是第三步新建的监听器类路径)
<listener>
<listener-class>com.webpush.service.ServiceManager</listener-class>
</listener>
到这里就已经完成mpush继承环境的开发搭建了,以下的内容是增加测试的servlet
五、新建两个servlet类并添加web.xml文件配置:
第一个servlet类:
-
package com.webpush.pushmessage; -
import java.io.IOException; -
import java.util.ArrayList; -
import java.util.List; -
import java.util.concurrent.TimeUnit; -
import java.util.concurrent.locks.LockSupport; -
import javax.servlet.ServletException; -
import javax.servlet.annotation.WebServlet; -
import javax.servlet.http.HttpServlet; -
import javax.servlet.http.HttpServletRequest; -
import javax.servlet.http.HttpServletResponse; -
import com.mpush.api.push.AckModel; -
import com.mpush.api.push.MsgType; -
import com.mpush.api.push.PushCallback; -
import com.mpush.api.push.PushContext; -
import com.mpush.api.push.PushMsg; -
import com.mpush.api.push.PushResult; -
import com.mpush.api.push.PushSender; -
import com.webpush.service.ServiceManager;//刚才第三步新建servlet监听器的类(修改为自己的类路径) -
/** -
* Servlet implementation class Htmlpushmesg -
*/ -
@WebServlet("/htmlpushmesg") -
public class Htmlpushmesg extends HttpServlet { -
private static final long serialVersionUID = 1L; -
PushResult pushResult=null;//消息推送结果 -
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { -
request.setCharacterEncoding("UTF-8"); -
int msgType=Integer.valueOf(request.getParameter("msgtype")); -
String pushMsg=request.getParameter("pushMsg"); -
String broadcast=request.getParameter("broadcast"); -
String[] userIds=request.getParameterValues("userId"); -
List<String> users=new ArrayList<String>(); -
if(userIds!=null) -
for(int i=0,len=userIds.length;i<len;i++) users.add(userIds[i]); -
PushSender sender = ServiceManager.getPushSender();//刚才第三步新建servlet监听器的类,过去消息发送服务的实例 -
PushMsg msg = PushMsg.build(msgType==1?MsgType.NOTIFICATION:msgType==2?MsgType.MESSAGE:MsgType.NOTIFICATION_AND_MESSAGE, pushMsg); -
//msg.setMsgId(msgId); -
msg.setContent(pushMsg); -
PushContext context = PushContext.build(msg) -
.setAckModel(AckModel.BIZ_ACK) -
.setBroadcast("true".equals(broadcast))//是否进行广播 -
.setUserIds(users)//多用户推送 -
.setTimeout(3000) -
.setCallback(new PushCallback() { -
@Override -
public void onResult(PushResult result) { -
pushResult=result; -
} -
}).setTaskId("123456"); -
/*FutureTask<PushResult> future = */sender.send(context); -
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10)); -
request.setAttribute("pushResult", pushResult); -
request.getRequestDispatcher("/push.jsp").forward(request, response); -
} -
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { -
doGet(request, response); -
} -
}
第二个servlet类:
在新建第二个servlet类之前先建一个redis的配置文件,文件名:redis.properties,放在src目录下
-
jedis.pool.maxActive=1024 -
jedis.pool.maxIdle=200 -
jedis.pool.maxWait=10000 -
jedis.pool.testOnBorrow=true -
jedis.pool.testOnReturn=true -
jedis.pool.timeout=10000 -
# ip地址必须和文件application.conf中的redis的IP地址相同 -
redisReadURL=127.0.0.1 -
redisReadPort=6379 -
# ip地址必须和文件application.conf中的redis的IP地址相同 -
redisWriteURL=127.0.0.1 -
redisWritePort=6379 -
# 你的redis密码,同文件application.conf中的redis中的密码 -
password=
servlet类
-
package com.webpush.pushmessage; -
import java.io.File; -
import java.io.IOException; -
import java.util.ArrayList; -
import java.util.HashMap; -
import java.util.List; -
import java.util.Map; -
import java.util.stream.Collectors; -
import javax.servlet.ServletException; -
import javax.servlet.annotation.WebServlet; -
import javax.servlet.http.HttpServlet; -
import javax.servlet.http.HttpServletRequest; -
import javax.servlet.http.HttpServletResponse; -
import com.mpush.tools.config.CC; -
import com.mpush.tools.config.data.RedisNode; -
import com.typesafe.config.Config; -
import com.typesafe.config.ConfigFactory; -
import redis.clients.jedis.Jedis; -
import redis.clients.jedis.JedisPool; -
import redis.clients.jedis.JedisPoolConfig; -
/** -
* 获取所有在线用户的信息 -
*/ -
@WebServlet("/userStutas") -
public class UserStutas extends HttpServlet { -
private static final long serialVersionUID = 1L; -
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { -
request.setAttribute("users", getRedisdata()); -
request.getRequestDispatcher("/userlist.jsp").forward(request, response); -
} -
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { -
doGet(request, response); -
} -
/** -
* redis操作部分 -
*/ -
// 连接实例的最大连接数 -
private static int MAX_ACTIVE = 1024; -
// 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 -
private static int MAX_IDLE = 200; -
// 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException -
private static int MAX_WAIT = 10000; -
// 连接超时的时间 -
private static int TIMEOUT = 10000; -
// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; -
private static boolean TEST_ON_BORROW = true; -
private static JedisPool jedisPool = null; -
//加载配置文件 -
static void load() { -
Config config = ConfigFactory.load();//扫描加载所有可用的配置文件 -
String custom_conf = "mp.conf";//加载自定义配置, 值来自jvm启动参数指定-Dmp.conf -
if (config.hasPath(custom_conf)) { -
File file = new File(config.getString(custom_conf)); -
if (file.exists()) { -
Config custom = ConfigFactory.parseFile(file); -
config = custom.withFallback(config); -
} -
} -
Config cfg = CC.cfg.getObject("mp").toConfig().getObject("redis").toConfig(); -
try { -
JedisPoolConfig redisconf = new JedisPoolConfig(); -
redisconf.setMaxTotal(MAX_ACTIVE); -
redisconf.setMaxIdle(MAX_IDLE); -
redisconf.setMaxWaitMillis(MAX_WAIT); -
redisconf.setTestOnBorrow(TEST_ON_BORROW); -
RedisNode redisnode=cfg.getList("nodes") -
.stream()//第一纬度数组 -
.map(v -> RedisNode.from(v.unwrapped().toString())) -
.collect(Collectors.toCollection(ArrayList::new)).get(0); -
jedisPool = new JedisPool(redisconf, redisnode.getHost(), redisnode.getPort(), TIMEOUT, cfg.getString("password")); -
} catch (Exception e) { -
e.printStackTrace(); -
} -
} -
public Map<String, Map<String,List<String>>> getRedisdata() { -
Jedis jedis = getJedis(); -
Map<String, Map<String,List<String>>> map = new HashMap<String, Map<String,List<String>>>(); -
for(String key : jedis.keys("mp:ur:*")) { -
//System.err.println(key+"\t"); -
Map<String,List<String>> maplist=new HashMap<String,List<String>>(); -
for(String ke : jedis.hkeys(key)) { -
maplist.put(ke, jedis.hmget(key,ke)); -
//System.out.print(ke+"\t"); -
//System.out.println(jedis.hmget(key,ke)); -
} -
map.put(key, maplist); -
} -
returnResource(jedis); -
return map; -
} -
/** -
* 初始化Redis连接池 -
*/ -
static { -
load(); -
} -
/** -
* 获取Jedis实例 -
*/ -
public synchronized static Jedis getJedis() { -
try { -
if (jedisPool != null) { -
Jedis resource = jedisPool.getResource(); -
return resource; -
} else { -
return null; -
} -
} catch (Exception e) { -
e.printStackTrace(); -
return null; -
} -
} -
/*** -
* -
* 释放资源 -
*/ -
public static void returnResource(final Jedis jedis) { -
if (jedis != null) { -
jedisPool.returnResource(jedis); -
} -
} -
}
修改web.xml文件
-
<?xml version="1.0" encoding="UTF-8"?> -
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> -
<display-name>webpush</display-name> -
<welcome-file-list> -
<welcome-file>index.html</welcome-file> -
<welcome-file>index.htm</welcome-file> -
<welcome-file>index.jsp</welcome-file> -
<welcome-file>default.html</welcome-file> -
<welcome-file>default.htm</welcome-file> -
<welcome-file>default.jsp</welcome-file> -
</welcome-file-list> -
<listener> -
<listener-class>com.webpush.service.ServiceManager</listener-class> -
</listener> -
<servlet> -
<servlet-name>htmlpushmesg</servlet-name> -
<servlet-class>com.webpush.pushmessage.Htmlpushmesg</servlet-class> -
</servlet> -
<servlet-mapping> -
<servlet-name>htmlpushmesg</servlet-name> -
<url-pattern>/htmlpushmesg.do</url-pattern> -
</servlet-mapping> -
<servlet> -
<servlet-name>userStutas</servlet-name> -
<servlet-class>com.webpush.pushmessage.UserStutas</servlet-class> -
</servlet> -
<servlet-mapping> -
<servlet-name>userStutas</servlet-name> -
<url-pattern>/userStutas.do</url-pattern> -
</servlet-mapping> -
</web-app>
web Socket客户端,接受消息测试(app请在官网下载,如果Android7.0及以上版本点击绑定退出,请换成Android6.0及以上版本安装测试)
-
<!DOCTYPE html> -
<html lang="en"> -
<head> -
<meta charset="UTF-8"> -
<title>MPush WebSocket Client</title> -
</head> -
<body> -
<script type="text/javascript"> -
(function (window) { -
let socket, session = {}, ID_SEQ = 1; -
let config = {listener: null, log: console}; -
let listener = { -
onOpened: function (event) { -
if (config.listener != null) { -
config.listener.onOpened(event); -
} -
handshake(); -
}, -
onClosed: function (event) { -
if (config.listener != null) { -
config.listener.onClosed(event); -
} -
session = {}; -
ID_SEQ = 1; -
socket = null; -
}, -
onHandshake: function () { -
session.handshakeOk = true; -
if (config.listener != null) { -
config.listener.onHandshake(); -
} -
if (config.userId) { -
bindUser(config.userId, config.tags); -
} -
}, -
onBindUser: function (success) { -
if (config.listener != null) { -
config.listener.onBindUser(success); -
} -
}, -
onReceivePush: function (message, messageId) { -
if (config.listener != null) { -
config.listener.onReceivePush(message, messageId); -
} -
}, -
onKickUser: function (userId, deviceId) { -
if (config.listener != null) { -
config.listener.onKickUser(userId, deviceId); -
} -
doClose(-1, "kick user"); -
} -
}; -
const Command = { -
HANDSHAKE: 2, -
BIND: 5, -
UNBIND: 6, -
ERROR: 10, -
OK: 11, -
KICK: 13, -
PUSH: 15, -
ACK: 23, -
UNKNOWN: -1 -
}; -
function Packet(cmd, body, sessionId) { -
return { -
cmd: cmd, -
flags: 16, -
sessionId: sessionId || ID_SEQ++, -
body: body -
} -
} -
function handshake() { -
send(Packet(Command.HANDSHAKE, { -
deviceId: config.deviceId, -
osName: config.osName, -
osVersion: config.osVersion, -
clientVersion: config.clientVersion -
}) -
); -
} -
function bindUser(userId, tags) { -
if (userId && userId != session.userId) { -
session.userId = userId; -
session.tags = tags; -
send(Packet(Command.BIND, {userId: userId, tags: tags})); -
} -
} -
function ack(sessionId) { -
send(Packet(Command.ACK, null, sessionId)); -
} -
function send(message) { -
if (!socket) { -
return; -
} -
if (socket.readyState == WebSocket.OPEN) { -
socket.send(JSON.stringify(message)); -
} else { -
config.log.error("The socket is not open."); -
} -
} -
function dispatch(packet) { -
switch (packet.cmd) { -
case Command.HANDSHAKE: { -
config.log.debug(">>> handshake ok."); -
listener.onHandshake(); -
break; -
} -
case Command.OK: { -
if (packet.body.cmd == Command.BIND) { -
config.log.debug(">>> bind user ok."); -
listener.onBindUser(true); -
} -
break; -
} -
case Command.ERROR: { -
if (packet.body.cmd == Command.BIND) { -
config.log.debug(">>> bind user failure."); -
listener.onBindUser(false); -
} -
break; -
} -
case Command.KICK: { -
if (session.userId == packet.body.userId && config.deviceId == packet.body.deviceId) { -
config.log.debug(">>> receive kick user."); -
listener.onKickUser(packet.body.userId, packet.body.deviceId); -
} -
break; -
} -
case Command.PUSH: { -
config.log.debug(">>> receive push, content=" + packet.body.content); -
let sessionId; -
if ((packet.flags & 8) != 0) { -
ack(packet.sessionId); -
} else { -
sessionId = packet.sessionId -
} -
listener.onReceivePush(packet.body.content, sessionId); -
break; -
} -
} -
} -
function onReceive(event) { -
config.log.debug(">>> receive packet=" + event.data); -
dispatch(JSON.parse(event.data)) -
} -
function onOpen(event) { -
config.log.info("Web Socket opened!"); -
listener.onOpened(event); -
} -
function onClose(event) { -
config.log.info("Web Socket closed!"); -
listener.onClosed(event); -
} -
function onError(event) { -
config.log.info("Web Socket receive, error"); -
doClose(); -
} -
function doClose(code, reason) { -
if (socket) socket.close(); -
config.log.info("try close web socket client, reason=" + reason); -
} -
function doConnect(cfg) { -
config = copy(cfg); -
socket = new WebSocket(config.url); -
socket.onmessage = onReceive; -
socket.onopen = onOpen; -
socket.onclose = onClose; -
socket.onerror = onError; -
config.log.debug("try connect to web socket server, url=" + config.url); -
} -
function copy(cfg) { -
for (let p in cfg) { -
if (cfg.hasOwnProperty(p)) { -
config[p] = cfg[p]; -
} -
} -
return config; -
} -
window.mpush = { -
connect: doConnect, -
close: doClose, -
bindUser: bindUser -
} -
})(window); -
function $(id) { -
return document.getElementById(id); -
} -
let log = { -
log: function () { -
$("responseText").value += (Array.prototype.join.call(arguments, "") + "\r\n"); -
} -
}; -
log.debug = log.info = log.warn = log.error = log.log; -
function connect() { -
mpush.connect({ -
url: $("url").value, -
userId: $("userId").value, -
deviceId: "test-1001", -
osName: "web " + navigator.userAgent, -
osVersion: "55.2", -
clientVersion: "1.0", -
log: log -
}); -
} -
function bind() { -
mpush.bindUser($("userId").value) -
} -
</script> -
<form οnsubmit="return false;"> -
<label> 推送服务地址: -
<input type="text" id="url" readonly="readonly" value="ws://192.168.10.24:8100/"> -
</label> -
<input type="button" value="连接服务" οnclick="connect()"> -
<br> -
<label> 绑定用户: -
<input type="text" id="userId" value="ttkx"> -
</label> -
<input type="button" value="绑定" οnclick="bind()"> -
<h3><label for="responseText">接收到的消息</label></h3> -
<textarea id="responseText" style="width:100%;height:500px;"></textarea> -
</form> -
</body> -
</html>
消息推送页面
-
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.webpush.redis.RedisUtils,java.util.*"%> -
<!DOCTYPE html> -
<html> -
<head> -
<meta charset="UTF-8"> -
<title>Insert title here</title> -
</head> -
<body> -
<h1>消息推送</h1> -
<div>推送结果:<%=request.getAttribute("pushResult")%></div> -
<form action="<%=request.getContextPath() %>/htmlpushmesg.do" method="post"> -
消息类型:<select name="msgtype"> -
<option value="1">提醒(会在通知栏显示)</option> -
<option value="2">消息(不会在通知栏显示,业务自定义消息)</option> -
<option value="3">提醒+消息</option> -
</select><br/> -
是否广播:<input type="radio" value="true" name="broadcast">广播 -
<input type="radio" value="false" name="broadcast" checked="checked">指定用户推送<br/> -
推送用户:<%Map<String, Map<String,List<String>>> users=new RedisUtils().getRedisdata(); -
for(String user : users.keySet()){%> -
<input type="checkbox" name="userId" value="<%=user.split("mp:ur:")[1]%>"><%=user.split("mp:ur:")[1]%> -
<%}%> -
</select><br/> -
消息内容:<textArea name="pushMsg">魔推mPush魔推mPush是由移石创想(北京)科技有限公司(mRocker)推出的一款针对企业移动应用程序推送市场的一款集消息类推送与数据服务的开发者服务产品。</textArea> -
<br/><input type="submit" value="确定" /> -
</form> -
</body> -
</html>
客户端所有用户列表
-
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*,com.alibaba.fastjson.JSON"%> -
<!DOCTYPE html> -
<html> -
<head> -
<meta charset="UTF-8"> -
<title>Insert title here</title> -
<style type="text/css"> -
th,td{border:solid 1px #999} -
</style> -
</head> -
<body> -
<h1 style='text-align: center'>所有用户列表数据</h1> -
<table border="0" cellspacing="0" width="100%"> -
<tr> -
<th style="width: 100px;">用户名</th> -
<th style="width: 70px;">版本号</th> -
<th style="width: 155px;">主机</th> -
<th style="width: 70px;">在线情况</th> -
<th>客户端类型</th> -
</tr> -
<% -
Map<String, Map<String, List<String>>> map = (Map<String, Map<String, List<String>>>) request.getAttribute("users"); -
Set<String> set = map.keySet(); -
for (String user : set) { -
Set<String> se = map.get(user).keySet(); -
Iterator<String> it = se.iterator(); -
%> -
<tr> -
<td rowspan="<%=se.size()%>"><%=user.split("mp:ur:")[1]%></td> -
<% -
String list = map.get(user).get(it.next()).get(0); -
Map<String, Object> m = JSON.parseObject(list, Map.class); -
%> -
<td><%=m.get("clientVersion")%></td> -
<td><%=m.get("hostAndPort")%></td> -
<td><%="true".equals(m.get("online").toString()) ? "<text style=\"color:#0f0\">在线</text>" : "<text style=\"color:#999\">离线</text>"%></td> -
<td><%=m.get("osName")%></td> -
</tr> -
<% -
while (it.hasNext()) { -
%> -
<tr> -
<% -
list = map.get(user).get(it.next()).get(0); -
m = JSON.parseObject(list, Map.class); -
%> -
<td><%=m.get("clientVersion")%></td> -
<td><%=m.get("hostAndPort")%></td> -
<td><%="true".equals(m.get("online").toString()) ? "<text style=\"color:#0f0\">在线</text>" : "<text style=\"color:#999\">离线</text>"%></td> -
<td><%=m.get("osName")%></td> -
</tr> -
<% -
} -
} -
%> -
</table> -
</body> -
</html>
然后运行web项目,查看效果
ps:欢迎加群:517413713 讨论
526

被折叠的 条评论
为什么被折叠?



