Web統合mpush開発

32752 ワード

サービスの導入方法の参照:https://github.com/mywiki/mpush-doc/blob/master/SUMMARY.md mpushの公式詳細開発ドキュメントを完了します.http://mpush.mydoc.io/?t=134820
redisはデフォルトではネイティブのみアクセスできます.プロファイルを変更する必要があります.参照してください.https://blog.csdn.net/weiyangdong/article/details/79916445変更
完全なWebプロジェクトdemol接続:https://download.csdn.net/download/qq_16758997/10943141
Mpush-Allocサービスをインストールするときは、mpush.configプロファイルのws-server-portのポート番号(アナログクライアントのポート番号)を変更してください.

一、普通のmaven webプロジェクトを新規作成するか、新しいwebプロジェクトを新規作成してmavenプロジェクトに変換する(文章は後者の方式を採用し、jdk 1.8、tomcat 9.0)
二、pom.xmlファイルを以下のように変更する.

  4.0.0
  webmpush
  webmpush
  0.0.1-SNAPSHOT
  war
  
	
		com.github.mpusher
		mpush-client
		0.8.0
	
  
  
    src
    
      
        src
        
          **/*.java
        
      
    
    
      
        maven-compiler-plugin
        3.7.0
        
          1.8
          1.8
        
      
      
        maven-war-plugin
        3.2.1
        
          WebContent
        
      
    
  

新しいラベルセクション、mpush関連jarパッケージをインポート
             com.github.mpusher         mpush-client         0.8.0       
srcディレクトリの下にアプリケーション.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起動は使用する必要がある)、サーブレットContextListener(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 start() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CompletableFuture 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 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ファイルを修正する


  webpush
  
    index.html
    index.htm
    index.jsp
    default.html
    default.htm
    default.jsp
  
  
    
    com.webpush.service.ServiceManager
  

カスタムWebプロジェクトのリスナーを追加します(ここでのクラスパスは、手順3で新規作成したリスナークラスパスです).
    com.webpush.service.ServiceManager  
ここまでmpush継承環境の開発構築は完了しており,以下の内容はテストのservletを追加することである.
五、2つの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 users=new ArrayList();
        if(userIds!=null)
        	for(int i=0,len=userIds.length;i 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);
	}
}

2番目のservletクラス:
2番目の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>> getRedisdata() {
		Jedis jedis = getJedis();
		Map>> map = new HashMap>>();
		for(String key : jedis.keys("mp:ur:*")) {
			//System.err.println(key+"\t");
			Map> maplist=new HashMap>();
			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ファイルの変更


  webpush
  
    index.html
    index.htm
    index.jsp
    default.html
    default.htm
    default.jsp
  
  
    com.webpush.service.ServiceManager
  
  
    htmlpushmesg
    com.webpush.pushmessage.Htmlpushmesg
  
  
    htmlpushmesg
    /htmlpushmesg.do
  
  
    userStutas
    com.webpush.pushmessage.UserStutas
  
  
    userStutas
    /userStutas.do
  

Web Socketクライアントは、メッセージテストを受けます(appは公式サイトでダウンロードしてください.Android 7.0以上のバージョンがバインドをクリックして終了したら、Android 6.0以下のバージョンに変更してテストをインストールしてください).



    
    MPush WebSocket Client




    (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
"); } }; 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) }



クライアントのすべてのユーザー・リスト





Insert title here



	

>> map = (Map>>) request.getAttribute("users"); Set set = map.keySet(); for (String user : set) { Set se = map.get(user).keySet(); Iterator it = se.iterator(); %>
m = JSON.parseObject(list, Map.class); %> " : " "%>
" : " "%>

Webプロジェクトを実行し、効果を表示します.
もしあなたに役に立つならいいねを覚えていますよ.
ようこそグループ:517413713討論