精华内容
下载资源
问答
  • SpringBoot2.0集成WebSocket,实现后台向前端推送信息

    万次阅读 多人点赞 2018-05-10 22:54:29
    什么是WebSocket? - 初次接触 WebSocket 的人,都会问同样的问题:我们...它能带来什么好处? - 答案很简单,因为 HTTP 协议有一个缺陷:***通信只能由客户端发起***,HTTP 协议做不到服务器主动向客户端推送信息。 ...

    什么是WebSocket?

    这里写图片描述
    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

    为什么需要 WebSocket?

    初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

    • 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。
      这里写图片描述
      举例来说,我们想要查询当前的排队情况,只能是页面轮询向服务器发出请求,服务器返回查询结果。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此WebSocket 就是这样发明的。

    前言

    2020-10-20 教程补充:

    • 补充关于@Component@ServerEndpoint关于是否单例模式等的解答,感谢大家热心提问和研究。
    • Vue版本的websocket连接方法

    2020-01-05 教程补充:

    感谢大家的支持和留言,14W访问量是满满的动力!接下来还会有websocket+redis集群优化篇针对多ws服务器做简单优化处理,敬请期待!

    话不多说,马上进入干货时刻。

    maven依赖

    SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入

    		<dependency>  
               <groupId>org.springframework.boot</groupId>  
               <artifactId>spring-boot-starter-websocket</artifactId>  
           </dependency> 
    

    WebSocketConfig

    启用WebSocket的支持也是很简单,几句代码搞定

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     * 开启WebSocket支持
     * @author zhengkai.blog.csdn.net
     */
    @Configuration  
    public class WebSocketConfig {  
    	
        @Bean  
        public ServerEndpointExporter serverEndpointExporter() {  
            return new ServerEndpointExporter();  
        }  
      
    } 
    

    WebSocketServer

    这就是重点了,核心都在这里。

    1. 因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

    2. 直接@ServerEndpoint("/imserver/{userId}")@Component启用即可,然后在里面实现@OnOpen开启连接,@onClose关闭连接,@onMessage接收消息等方法。

    3. 新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便IM之间对userId进行推送消息。单机版实现到这里就可以。

    4. 集群版(多个ws节点)还需要借助mysql或者redis等进行处理,改造对应的sendMessage方法即可。

    package com.softdev.system.demo.config;
    
    import java.io.IOException;
    import java.util.concurrent.ConcurrentHashMap;
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.stereotype.Component;
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    
    
    /**
     * @author zhengkai.blog.csdn.net
     */
    @ServerEndpoint("/imserver/{userId}")
    @Component
    public class WebSocketServer {
    
        static Log log=LogFactory.get(WebSocketServer.class);
        /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
        private static int onlineCount = 0;
        /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
        private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
        /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
        private Session session;
        /**接收userId*/
        private String userId="";
    
        /**
         * 连接建立成功调用的方法*/
        @OnOpen
        public void onOpen(Session session,@PathParam("userId") String userId) {
            this.session = session;
            this.userId=userId;
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                webSocketMap.put(userId,this);
                //加入set中
            }else{
                webSocketMap.put(userId,this);
                //加入set中
                addOnlineCount();
                //在线数加1
            }
    
            log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
    
            try {
                sendMessage("连接成功");
            } catch (IOException e) {
                log.error("用户:"+userId+",网络异常!!!!!!");
            }
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                //从set中删除
                subOnlineCount();
            }
            log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息*/
        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("用户消息:"+userId+",报文:"+message);
            //可以群发消息
            //消息保存到数据库、redis
            if(StringUtils.isNotBlank(message)){
                try {
                    //解析发送的报文
                    JSONObject jsonObject = JSON.parseObject(message);
                    //追加发送人(防止串改)
                    jsonObject.put("fromUserId",this.userId);
                    String toUserId=jsonObject.getString("toUserId");
                    //传送给对应toUserId用户的websocket
                    if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
                        webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                    }else{
                        log.error("请求的userId:"+toUserId+"不在该服务器上");
                        //否则不在这个服务器上,发送到mysql或者redis
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    
        /**
         *
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
            error.printStackTrace();
        }
        /**
         * 实现服务器主动推送
         */
        public void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }
    
    
        /**
         * 发送自定义消息
         * */
        public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
            log.info("发送消息到:"+userId+",报文:"+message);
            if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
                webSocketMap.get(userId).sendMessage(message);
            }else{
                log.error("用户"+userId+",不在线!");
            }
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            WebSocketServer.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketServer.onlineCount--;
        }
    }
    

    消息推送

    至于推送新信息,可以再自己的Controller写个方法调用WebSocketServer.sendInfo();即可

    
    import com.softdev.system.demo.config.WebSocketServer;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.ModelAndView;
    import java.io.IOException;
    
    /**
     * WebSocketController
     * @author zhengkai.blog.csdn.net
     */
    @RestController
    public class DemoController {
    
        @GetMapping("index")
        public ResponseEntity<String> index(){
            return ResponseEntity.ok("请求成功");
        }
    
        @GetMapping("page")
        public ModelAndView page(){
            return new ModelAndView("websocket");
        }
    
        @RequestMapping("/push/{toUserId}")
        public ResponseEntity<String> pushToWeb(String message, @PathVariable String toUserId) throws IOException {
            WebSocketServer.sendInfo(message,toUserId);
            return ResponseEntity.ok("MSG SEND SUCCESS");
        }
    }
    
    

    页面发起

    页面用js代码调用websocket,当然,太古老的浏览器是不行的,一般新的浏览器或者谷歌浏览器是没问题的。还有一点,记得协议是ws的,如果使用了一些路径类,可以replace(“http”,“ws”)来替换协议。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>websocket通讯</title>
    </head>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        var socket;
        function openSocket() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else{
                console.log("您的浏览器支持WebSocket");
                //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
                //等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
                //var socketUrl="${request.contextPath}/im/"+$("#userId").val();
                var socketUrl="http://localhost:9999/demo/imserver/"+$("#userId").val();
                socketUrl=socketUrl.replace("https","ws").replace("http","ws");
                console.log(socketUrl);
                if(socket!=null){
                    socket.close();
                    socket=null;
                }
                socket = new WebSocket(socketUrl);
                //打开事件
                socket.onopen = function() {
                    console.log("websocket已打开");
                    //socket.send("这是来自客户端的消息" + location.href + new Date());
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    console.log(msg.data);
                    //发现消息进入    开始处理前端触发逻辑
                };
                //关闭事件
                socket.onclose = function() {
                    console.log("websocket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    console.log("websocket发生了错误");
                }
            }
        }
        function sendMessage() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else {
                console.log("您的浏览器支持WebSocket");
                console.log('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
                socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
            }
        }
    </script>
    <body>
    <p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
    <p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
    <p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
    <p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
    <p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
    </body>
    
    </html>
    

    运行效果

    • v20200105,加入开源项目spring-cloud-study-websocket,更新运行效果,更方便理解。
    • v1.1的效果,刚刚修复了日志,并且支持指定监听某个端口,代码已经全部更新,现在是这样的效果
    1. 打开两个页面,按F12调出控控制台查看测试效果:
    页面 参数
    http://localhost:9999/demo/page fromUserId=10,toUserId=20
    http://localhost:9999/demo/page fromUserId=20,toUserId=10

    分别开启socket,再发送消息
    在这里插入图片描述
    在这里插入图片描述
    2. 向前端推送数据:

    • http://localhost:9999/demo/push/10?message=123123

    在这里插入图片描述
    通过调用push api,可以向指定的userId推送信息,当然报文这里乱写,建议规定好格式。

    后续

    针对简单IM的业务场景,进行了一些优化,可以看后续的文章SpringBoot2+WebSocket之聊天应用实战(优化版本)(v20201005已整合)

    主要变动是CopyOnWriteArraySet改为ConcurrentHashMap,保证多线程安全同时方便利用map.get(userId)进行推送到指定端口。

    相比之前的Set,Set遍历是费事且麻烦的事情,而Map的get是简单便捷的,当WebSocket数量大的时候,这个小小的消耗就会聚少成多,影响体验,所以需要优化。在IM的场景下,指定userId进行推送消息更加方便。

    Websocker注入Bean问题

    关于这个问题,可以看最新发表的这篇文章,在参考和研究了网上一些攻略后,项目已经通过该方法注入成功,大家可以参考。
    关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法

    netty-websocket-spring-boot-starter

    Springboot2构建基于Netty的高性能Websocket服务器(netty-websocket-spring-boot-starter)
    只需要换个starter即可实现高性能websocket,赶紧使用吧

    Springboot2+Netty+Websocket

    Springboot2+Netty实现Websocket,使用官方的netty-all的包,比原生的websocket更加稳定更加高性能,同等配置情况下可以handle更多的连接。

    代码样式全部已经更正,也支持websocket连接url带参数功能,另外也感谢大家的阅读和评论,一起进步,谢谢!~~

    ServerEndpointExporter错误

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘serverEndpointExporter’ defined in class path resource [com/xxx/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

    感谢@来了老弟儿 的反馈:

    如果tomcat部署一直报这个错,请移除 WebSocketConfig@Bean ServerEndpointExporter 的注入 。

    ServerEndpointExporter 是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig配置类和@ServerEndpoint注解实例。使用规则也很简单:

    1. 如果使用默认的嵌入式容器 比如Tomcat 则必须手工在上下文提供ServerEndpointExporter
    2. 如果使用外部容器部署war包,则不需要提供提供ServerEndpointExporter,因为此时SpringBoot默认将扫描服务端的行为交给外部容器处理,所以线上部署的时候要把WebSocketConfig中这段注入bean的代码注掉。

    正式项目的前端WebSocket框架 GoEasy

    感谢kkatrina的补充,正式的项目中,一般是用第三方websocket框架来做,稳定性、实时性有保证的多,也会包括一些心跳、重连机制。

    GoEasy专注于服务器与浏览器,浏览器与浏览器之间消息推送,完美兼容世界上的绝大多数浏览器,包括IE6, IE7之类的非常古老的浏览器。支持Uniapp,各种小程序,react,vue等所有主流Web前端技术。
    GoEasy采用 发布/订阅 的消息模式,帮助您非常轻松的实现一对一,一对多的通信。
    https://www.goeasy.io/cn/doc/

    @Component@ServerEndpoint关于是否单例模式,能否使用static Map等一些问题的解答

    看到大家都在热心的讨论关于是否单例模式这个问题,请大家相信自己的直接,如果websocket是单例模式,还怎么服务这么多session呢。

    1. websocket是原型模式@ServerEndpoint每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式。
    2. Spring的@Component默认是单例模式,请注意,默认 而已,是可以被改变的。
    3. 这里的@Component仅仅为了支持@Autowired依赖注入使用,如果不加则不能注入任何东西,为了方便。
    4. 什么是prototype 原型模式? 基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例B的话,就需要使用原型模式。
    5. 关于在原型模式下使用static 的webSocketMap,请注意这是ConcurrentHashMap ,也就是线程安全/线程同步的,而且已经是静态变量作为全局调用,这种情况下是ok的,或者大家如果有顾虑或者更好的想法的化,可以进行改进。 例如使用一个中间类来接收和存放session。
    6. 为什么每次都@OnOpen都要检查webSocketMap.containsKey(userId) ,首先了为了代码强壮性考虑,假设代码以及机制没有问题,那么肯定这个逻辑是废的对吧。但是实际使用的时候发现偶尔会出现重连失败或者其他原因导致之前的session还存在,这里就做了一个清除旧session,迎接新session的功能。

    Vue版本的websocket连接

    感谢**@GzrStudy**的贡献,供大家参考。

    <script>
    export default {
        data() {
            return {
                socket:null,
                userId:localStorage.getItem("ms_uuid"),
                toUserId:'2',
                content:'3'
            }
        },
      methods: {
        openSocket() {
          if (typeof WebSocket == "undefined") {
            console.log("您的浏览器不支持WebSocket");
          } else {
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            //等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
            //var socketUrl="${request.contextPath}/im/"+$("#userId").val();
            var socketUrl =
              "http://localhost:8081/imserver/" + this.userId;
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            console.log(socketUrl);
            if (this.socket != null) {
              this.socket.close();
              this.socket = null;
            }
            this.socket = new WebSocket(socketUrl);
            //打开事件
            this.socket = new WebSocket(socketUrl);
            //打开事件
            this.socket.onopen = function() {
              console.log("websocket已打开");
              //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            this.socket.onmessage = function(msg) {
              console.log(msg.data);
              //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            this.socket.onclose = function() {
              console.log("websocket已关闭");
            };
            //发生了错误事件
            this.socket.onerror = function() {
              console.log("websocket发生了错误");
            };
          }
        },
        sendMessage() {
          if (typeof WebSocket == "undefined") {
            console.log("您的浏览器不支持WebSocket");
          } else {
            console.log("您的浏览器支持WebSocket");
            console.log(
              '{"toUserId":"' +
                 this.toUserId +
                '","contentText":"' +
                 this.content +
                '"}'
            );
            this.socket.send(
              '{"toUserId":"' +
                 this.toUserId +
                '","contentText":"' +
                 this.content +
                '"}'
             );
        
        }
    }
    
    展开全文
  • iOS 开发之实现 App 消息推送(最新)

    万次阅读 多人点赞 2014-11-19 21:13:10
    今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:...

    好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:网上看了一下虽然有很多讲述推送的好教程,我也是看着一步步学会的,但是这些教程的时间都是去年或者更早时期的,对引导新手来说不是很合适)

    第一部分

    首先第一步当然是介绍一下苹果的推送机制(APNS)咯(ps:其实每一篇教程都有),先来看一张苹果官方对其推送做出解释的概要图。

    Provider是给你手机应用发出推送消息的服务器,而APNS(Apple Push Notification Service)则是苹果消息推送服务器。你本地的服务器当需要给应用推送一条消息的时候,先要将消息发出到苹果推送服务器,然后再由苹果推送服务器将消息发到安装了该应用的手机。

    接下来再看一张解释图:

     

    根据上图的逻辑我来给大家解释一下:

    1.你的IOS应用需要去注册APNS消息推送功能。

    2.当苹果APNS推送服收到来自你应用的注册消息就会返回一串device token给你(很重要)

    3.将应用收到的device Token传给你本地的Push服务器。

    4.当你需要为应用推送消息的时候,你本地的推送服务器会将消息,以及Device Token打包发送到苹果的APNS服

    5.APNS再将消息推送给目的iphone

     

    第二部分

    1.从证书颁发机构颁发证书

    打开你mac的钥匙串访问: 然后点击钥匙串访问

     

    随后它会弹出一个窗口用户电子邮件信息就填写你苹果开发者账号的名称即可(应该是一个邮件名称),点击保存到磁盘的选

    项,点击继续,显示如下:

     

    点击存储,文件名为:CertificateSigningRequest.certSigningRequest 随后将他放在一个文件夹中我们取名push吧!

     

    第三部分

    访问苹果开发者网址:https://developer.apple.com/

     

    选中MemberCenter选项,进入登陆页面,用你的苹果开发者账号登陆,过一会网页就会自动跳转到下图。

    点击红色所选部分内容进行下一步的操作。

    选择Certificates选项,设置证书,如图所示先解释一下:

     

     

    Development选项的作用顾名思义就是用来作为开发使用的证书,Production选项则是用来发布产品使用的,名称很陌生是不是,之

    前的开发者网页是没有这一选项的,可能是苹果把他修改了,用这个名称更加能让人理解吧(字面上解释就是产品么)。两个选项生

    成证书的步骤是一样的,现在我们使用开发者的选项进行证书的制作,步骤如下:选择Development选项:

     

    点击上面的加号选项,

     

    选择APNS选项(开发么当然是在沙盒环境下了,模拟真实情况),然后Continue

     

    这个AppID我们在下一部分讲如何生成,现在我用的是已经生成好的一个应用ID,继续Continue

     

    这边就要选择在钥匙串访问环节下载下来的CertificateSigningRequest.certSigningRequest文件了,选择并生成:

    点击下载,得到aps_development .cer,保存到push文件中去。

     

    第四部分

    新建一个AppID,选择网页上的AppIDs,然后点击右上角的 “加号”

     

    App的取名只要按照苹果要求的就可以了

     

    然后BundleID是比较重要的,在提交审核以及测试(苹果的TestFlight)和付费环节都需要用到,也只需按照苹果要求来写就好了。

     

    接下来就是对你的应用需要使用苹果的哪些服务进行选择就行了,例如广告,游戏中心,推送,付费等等情况。

    最后选择“Submit”选项,在下一个界面中选择“done”选项,这样我们设置AppID的步骤我们就完成了。


    第五部分:生成Provisioning Profiles

    这个配置概要文件分为两种,一种是为开发使用的,还有一种则是为发布到appStore上面。

    创建发布版的ProvisioningProfile与开发版的流程相同,点击Development然后点击右上角的加号

     

    会进入选择何种配置概要文件的界面:

     

    我们现在时测试,所以选择“IOS App Development”的选项,在下面的Distribution发布选项中有两个选择,“App Store”以及

    “Ad hoc”,你可以根据下面的描述选择你发布所需的选项。点击Continue进入下一步。

    选择你上一步创建的AppID,点击Continue 进行下一步

    选择你的开发者账号,Continue进行下一步

    在这一步上选择你的设备(你只有在这一步上勾选了你的设备,你才能在设备上用这个签名进行调试)。关于如何将你的设备号添加

    进去也是非常简单的,选择左侧的"Devices",然后点击右上角的加号,在随后出来的页面上添加你设备的UUID(在XCode中可以查

    看到)以及name( 可以随便取,自己看的懂就行)然后Register一下,照着流程走到最后一步就完成了。

    好咱们继续回到上面的Provisioning Profile配置环节,当你选好了你的设备后点击“Continue”进入下一页,

     

    输入一个文件名(最好是起的能看懂是干嘛的,当然也可以随便起),点击“Generate”进入下一个页面,在这个页面中就会有一个下

    载按钮让你下载这个文件,我们把它下载下来放在Push文件夹中。

     

    第六部分

    好了,前期的准备工作都已经做完了,现在让我们开始推送吧!(吼吼)

    首先双击我们生成的 “aps_development .cer” 文件,进入钥匙串访问,找到我们的专用秘钥(根据在第二部分中从证书机构颁发证书操作中填写的常用名)

     

    我在第二部分填写的是“silicon”,由于换了一台mac之前安装的没有了,之前没有截图,所以随便找了个图给大家看一下,凭大家的聪明才智应该不难理解吧。

    然后右击导出,会弹出如下所示的图。

     

    将他存储到push文件夹中,命名为“push.p12”,在这一步中导出会让你输入密码并验证,你可以自定义一个密码,例如abc123

    现在push文件夹中应该有几个文件“aps_development .cer” ,"push.p12",“CertificateSigningRequest.certSigningRequest”以及

    刚才下下来的配置概要文件。

     

    接下来我们打开终端将他们生成.pem文件

    1.把aps_development .cer文件生成.pcm文件,cd到push文件夹下

     

    2.把push.p12文件生成为.pem文件

    上边输入的密码则是你导出证书所设的密码,即abc123.接着还会让你输入.pem文件的密码,还是使用abc123好了,防止混淆。

    这样我们在push文件夹中就又得到了两个文件,PushChatCert.pem和PushChatKey.pem。

     

    3.把PushChatCert.pem和PushChatKey.pem合并为一个pem文件,

     

    在push文件夹中又多了一个ck.pem文件,以上我们把需要使用的文件都准备好了

     

    接下来就要测试一下啦,是不是很激动~

    为了测试证书工作的状况,我们可以使用“telnet gateway.sandbox.push.apple.com 2195”来检测一下,如果显示下图则表示成功了。

     

    然后,我们使用我们生成的证书和私钥来设置一个安全的链接去链接苹果服务器

    在终端输入如下命令:openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushChatKey.pem

    需要输入密码(abc123 我们刚才所设置的)。

    然后他会返回一系列的数据,这里我就粘贴一部分啦:

     

    CONNECTED(00000003)

    depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C

    verify error:num=20:unable to get local issuer certificate

    verify return:0

    ---

    Certificate chain

    。。。。。(省略)

    。。。。。(省略)

    。。。。。(省略)

     

        Start Time: 1416389389

        Timeout   : 300 (sec)

        Verify return code: 0 (ok)

    ---

    测试就到这里啦。。。

     

    第七部分

    1.建立推送项目

     

    //
    //  AppDelegate.m
    //  TestPushNotifiy
    //
    //  Created by silicon on 14-10-30.
    //  Copyright (c) 2014年 silicon. All rights reserved.
    //
    
    #import "AppDelegate.h"
    
    @implementation AppDelegate
    @synthesize mainView = _mainView;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
        {
            //IOS8
            //创建UIUserNotificationSettings,并设置消息的显示类类型
            UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];
            
            [application registerUserNotificationSettings:notiSettings];
            
        } else{ // ios7
            [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge										 |UIRemoteNotificationTypeSound										 |UIRemoteNotificationTypeAlert)];
        }
        
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        
        self.mainView = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
        self.window.rootViewController = self.mainView;
        return YES;
    }
    
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
        NSLog(@"---Token--%@", pToken);
    }
    
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
        
        NSLog(@"userInfo == %@",userInfo);
        NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];
        
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        
        [alert show];
    }
    
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    
        NSLog(@"Regist fail%@",error);
    }
    
    - (void)applicationWillResignActive:(UIApplication *)application
    {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }
    
    - (void)applicationDidEnterBackground:(UIApplication *)application
    {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }
    
    - (void)applicationWillEnterForeground:(UIApplication *)application
    {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }
    
    - (void)applicationWillTerminate:(UIApplication *)application
    {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }
    
    @end
    

     

    在appdelegate.m中加入以上代码,

     

     

     

    if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
        {
            //IOS8
            //创建UIUserNotificationSettings,并设置消息的显示类类型
            UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];
            
            [application registerUserNotificationSettings:notiSettings];
            
        } else{ // ios7
            [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge										 |UIRemoteNotificationTypeSound										 |UIRemoteNotificationTypeAlert)];
        }

    由于ios8的推送跟ios7及以下的不一样,所以需要加判断来注册消息推送。

    函数:

     

    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
        NSLog(@"---Token--%@", pToken);
    }

     

    会接收来自苹果服务器给你返回的deviceToken,然后你需要将它添加到你本地的推送服务器上。(很重要,决定你的设备能不能接收到推送消息)。

     

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
        
        NSLog(@"userInfo == %@",userInfo);
        NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];
        
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        
        [alert show];
    }

     

    这个函数则是当设备接收到来自苹果推送服务器的消息时触发的,用来显示推送消息。

     

     

    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    
        NSLog(@"Regist fail%@",error);
    }

    当注册失败时,触发此函数。

     

     

    2.PHP服务端

    将simplepush.php这个推送脚本也放在push文件夹中

     

    <?php
    
    // ??????????deviceToken???????????????
    $deviceToken = 'c95f661371b085e2517b4c12cc76293522775e5fd9bb1dea17dd80fe85583b41';
    
    // Put your private key's passphrase here:
    $passphrase = 'abc123';
    
    // Put your alert message here:
    $message = 'My first push test!';
    
    
    
    $ctx = stream_context_create();
    stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
    stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
    
    // Open a connection to the APNS server
    //??????????
     //$fp = stream_socket_client(?ssl://gateway.push.apple.com:2195?, $err, $errstr, 60, //STREAM_CLIENT_CONNECT, $ctx);
    //?????????????appstore??????
    $fp = stream_socket_client(
    'ssl://gateway.sandbox.push.apple.com:2195', $err,
    $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
    
    if (!$fp)
    exit("Failed to connect: $err $errstr" . PHP_EOL);
    
    echo 'Connected to APNS' . PHP_EOL;
    
    // Create the payload body
    $body['aps'] = array(
    'alert' => $message,
    'sound' => 'default'
    );
    
    // Encode the payload as JSON
    $payload = json_encode($body);
    
    // Build the binary notification
    $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
    
    // Send it to the server
    $result = fwrite($fp, $msg, strlen($msg));
    
    if (!$result)
    echo 'Message not delivered' . PHP_EOL;
    else
    echo 'Message successfully delivered' . PHP_EOL;
    
    // Close the connection to the server
    fclose($fp);
    ?>

    deviceToken填写你接收到的token,passPhrase则填写你的ck.pem设置的密码。

    此刻就是见证奇迹的时候了,使用终端进入到push文件夹,在终端输入 php simplepush.php

     

    若显示以上提示则表示推送成功了。附上一张成功图。

     

    推送就到这边吧!

    感谢这篇博客的指导:http://blog.csdn.net/showhilllee/article/details/8631734

     

    simplepush.php以及XCode工程我会上传到我的资源里去,大家可以去那边下载。有什么不明白的地方大家可以留言或者私信我,我会第一之间回复的~

     

     

    大家可以关注我的微信公众号与我互动,相关问题也可以直接用公众号联系我:

     

     

     

     

     

     

     

     

     

     

    展开全文
  • androidpn消息推送

    千次下载 热门讨论 2012-07-04 16:32:15
    androidpn消息推送 其中包含客户端 服务器端包含tomcat和jetty版本
  • 最近需要开发微信和小程序的推送功能,需要用java后台实现推送,自己本身java和小程序都做,所以就自己动手实现下小程序的模版推送功能推送。 实现思路 1 小程序获取用户openid,收集formid传给java...

    本文是比较老得技术来实现,用最新技术实现小程序推送,请看
    5行java代码实现微信小程序模版消息推送 (含springboot后台和小程序源码)
    也可以看最新的视频讲解:https://edu.csdn.net/course/detail/23750

    最近需要开发微信和小程序的推送功能,需要用java后台实现推送,自己本身java和小程序都做,所以就自己动手实现下小程序的模版推送功能推送。

    实现思路

    • 1 小程序获取用户openid,收集formid传给java后台
    • 2 java推送消息给指定小程序用户

    老规矩,还是先看效果图

    在这里插入图片描述
    我的这个是跑腿抢单推送,当用户新下单时,会给跑腿员推送消息。

    下面开始讲解实现步骤

    一,微信小程序管理后台开通模版推送

    在这里插入图片描述
    这里的模版id很重要,接下来我们推送的都是这个模版。

    二,java后台实现推送所需字段

    • 1 看微信官方推送消息所需要的字段
      在这里插入图片描述
      在这里插入图片描述
    • 2 有了官方说明,我门接下来就去拿到官方所需要的这些字段,来组装请求数据就可以了。

    三,下面讲解实现步骤

    我的java后台是基于springboot开发的,如果你不了解spring boot,建议你先去了解下springboot再回来接着学习。
    还有RestTemplate是我们java后台做get和post请求必须的,我们和微信服务器交互就用的RestTemplate

    • 1 首先根据官方推送所需字段组装java-bean
      这里用到两个javabean
    /*
    * 小程序推送所需数据
    * qcl 微信:2501902696
    * */
    @Data
    public class WxMssVo {
        private String touser;//用户openid
        private String template_id;//模版id
        private String page = "index";//默认跳到小程序首页
        private String form_id;//收集到的用户formid
        private String emphasis_keyword = "keyword1.DATA";//放大那个推送字段
        private Map<String, TemplateData> data;//推送文字
    }
    
    /*
    * 设置推送的文字和颜色
    * qcl 微信:2501902696
    * */
    @Data
    public class TemplateData {
        //keyword1:订单类型,keyword2:下单金额,keyword3:配送地址,keyword4:取件地址,keyword5备注
        private String value;//,,依次排下去
    //    private String color;//字段颜色(微信官方已废弃,设置没有效果)
    }
    

    到这里请求推送的数据就组装好了,解下来我们去实现推送功能。
    奥不对,还有一个重要的字段需要获取到:access_token

    access_token的获取

    /*
        * 获取access_token
        * appid和appsecret到小程序后台获取,当然也可以让小程序开发人员给你传过来
        * */
        public String getAccess_token(String appid, String appsecret) {
            //获取access_token
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
                    "&appid=" + appid + "&secret=" + appsecret;
            String json = restTemplate.getForObject(url, String.class);
            AccessToken accessToken = new Gson().fromJson(json, AccessToken.class);
            return accessToken.getAccess_token();
        }
    

    这次是真正的可以来请求微信服务器来实现消息推送了

    /*
        * 微信小程序推送单个用户
        * */
        public String pushOneUser(String openid, String formid) {
    
    
            //获取access_token
            String access_token = getAccess_token(ConstantUtils.SCHOOL_APPID, ConstantUtils.SCHOOL_APPSECRET);
            String url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send" +
                    "?access_token=" + access_token;
    
            //拼接推送的模版
            WxMssVo wxMssVo = new WxMssVo();
            wxMssVo.setTouser(openid);//用户openid
            wxMssVo.setTemplate_id("LzeDP0G5PLgHoOjCMfhu44wfUluhW11Zeezu3r_dC24");//模版id
            wxMssVo.setForm_id(formid);//formid
    
    
            Map<String, TemplateData> m = new HashMap<>(5);
    
            //keyword1:订单类型,keyword2:下单金额,keyword3:配送地址,keyword4:取件地址,keyword5备注
            TemplateData keyword1 = new TemplateData();
            keyword1.setValue("新下单待抢单");
            m.put("keyword1", keyword1);
    
            TemplateData keyword2 = new TemplateData();
            keyword2.setValue("这里填下单金额的值");
            m.put("keyword2", keyword2);
            wxMssVo.setData(m);
    
            TemplateData keyword3 = new TemplateData();
            keyword3.setValue("这里填配送地址");
            m.put("keyword3", keyword3);
            wxMssVo.setData(m);
    
            TemplateData keyword4 = new TemplateData();
            keyword4.setValue("这里填取件地址");
            m.put("keyword4", keyword4);
            wxMssVo.setData(m);
    
            TemplateData keyword5 = new TemplateData();
            keyword5.setValue("这里填备注");
            m.put("keyword5", keyword5);
            wxMssVo.setData(m);
    
            ResponseEntity<String> responseEntity =
                    restTemplate.postForEntity(url, wxMssVo, String.class);
            log.error("小程序推送结果={}", responseEntity.getBody());
            return responseEntity.getBody();
        }
    

    openid可以让小程序开发人员给你传过来,也可以自己获取。
    formid需要小程序开发给你传过来,你也可以把formid存到数据库里,什么时候需要直接拿出来用就可以了。
    注意:formid必须和用户openid对应。

    下面贴出来完整代码

    package com.qcl.paotuischool.wechat;
    
    import com.google.gson.Gson;
    import com.qcl.userwechat.bean.AccessToken;
    import com.qcl.utils.ConstantUtils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * Created by qcl on 2018/9/11.
     * 微信小程序推送服务,
     * 包含获取access_token的服务
     */
    @Service
    @Slf4j
    public class WxPushServiceQcl {
        //用来请求微信的get和post
        @Autowired
        private RestTemplate restTemplate;
    
    
        /*
        * 微信小程序推送单个用户
        * */
        public String pushOneUser(String openid, String formid) {
    
    
            //获取access_token
            String access_token = getAccess_token(ConstantUtils.SCHOOL_APPID, ConstantUtils.SCHOOL_APPSECRET);
            String url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send" +
                    "?access_token=" + access_token;
    
            //拼接推送的模版
            WxMssVo wxMssVo = new WxMssVo();
            wxMssVo.setTouser(openid);//用户openid
            wxMssVo.setTemplate_id("LzeDP0G5PLgHoOjCMfhu44wfUluhW11Zeezu3r_dC24");//模版id
            wxMssVo.setForm_id(formid);//formid
    
    
            Map<String, TemplateData> m = new HashMap<>(5);
    
            //keyword1:订单类型,keyword2:下单金额,keyword3:配送地址,keyword4:取件地址,keyword5备注
            TemplateData keyword1 = new TemplateData();
            keyword1.setValue("新下单待抢单");
            m.put("keyword1", keyword1);
    
            TemplateData keyword2 = new TemplateData();
            keyword2.setValue("这里填下单金额的值");
            m.put("keyword2", keyword2);
            wxMssVo.setData(m);
    
            TemplateData keyword3 = new TemplateData();
            keyword3.setValue("这里填配送地址");
            m.put("keyword3", keyword3);
            wxMssVo.setData(m);
    
            TemplateData keyword4 = new TemplateData();
            keyword4.setValue("这里填取件地址");
            m.put("keyword4", keyword4);
            wxMssVo.setData(m);
    
            TemplateData keyword5 = new TemplateData();
            keyword5.setValue("这里填备注");
            m.put("keyword5", keyword5);
            wxMssVo.setData(m);
    
            ResponseEntity<String> responseEntity =
                    restTemplate.postForEntity(url, wxMssVo, String.class);
            log.error("小程序推送结果={}", responseEntity.getBody());
            return responseEntity.getBody();
        }
    
        /*
        * 获取access_token
        * appid和appsecret到小程序后台获取,当然也可以让小程序开发人员给你传过来
        * */
        public String getAccess_token(String appid, String appsecret) {
            //获取access_token
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
                    "&appid=" + appid + "&secret=" + appsecret;
            String json = restTemplate.getForObject(url, String.class);
            AccessToken accessToken = new Gson().fromJson(json, AccessToken.class);
            return accessToken.getAccess_token();
        }
    
    }
    

    在需要做推送的地方调用WxPushServiceQcl类中的pushOneUser方法,并传入openid, formid参数即可。
    在这里插入图片描述
    这是我推送成功后打印的log

    下面来讲小程序端开发需要做些什么

    可以看出,我们的formid有效期是7天,并且一个form_id只能使用一次,所以我们小程序端所需要做的就是尽可能的多拿些formid,然后传个后台,让后台存到数据库中,这样7天有效期内,想怎么用就怎么用了。

    在这里插入图片描述

    所以接下来要讲的就是小程序开发怎么尽可能多的拿到formid了。

    在这里插入图片描述
    看下官方提供的,只有在表单提交时把report-submit设为true时才能拿到formid,比如这样

     <form report-submit='true' >
       <button  form-type='submit'>获取formid</button>
      </form>
    

    所以我们就要在这里下功夫了,既然只能在form组件获取,我们能不能把我们小程序里用到最多的地方用form来伪装呢。
    在这里插入图片描述
    我的小程序是跑腿小程序,消息也主要推送给跑腿员的,而跑腿员点击最多的也就是这两个条目,所以我们就用from组件来伪装这两个条目,让用户在点击的同时就可以收集到用的formid。

     <view class='button_item'>
      <form class="form_item" bindsubmit='gorRunnerLobby' report-submit='true' data-type="1">
       <button class="button" form-type='submit'>
        <text>抢单大厅</text>
        <text class='runner_desc'>(兼职也可月入万元)</text>
       </button>
      </form>
      <view class='right_arrow' />
     </view>
    

    这样就可以在用户点击条目时,收集到用户formid了
    在这里插入图片描述
    由于上面的botton有默认样式,所以我们就通过修改css来去除botton默认样式。

    .button_item {
     width: 100%;
     display: flex;
     flex-direction: row;
     align-items: center;
     padding: 2px 20px;
     background: white;
     border-bottom: 1px solid gainsboro;
    }
    /* 主要通过这里去除botton默认样式 */
    .button {
     width: 100%;
     background: white;
     border: none;
     text-align: left;
     padding: 6px 0px;
     margin: 0px;
     line-height: 1.5;
    }
    
    /* 主要通过这里去除botton边框 */
    .button::after {
     border: none;
    }
    
    /* 用button伪装获取formid */
    
    .form_item {
     width: 100%;
    }
    

    到这里我们小程序端也圆满完成自己的任务了。

    补充知识点

    这样我们java后台和小程序开发就可以开开心心的完成微信小程序的消息推送功能了。

    如果有java或小程序相关的问题可以加我微信交流学习2501902696(备注小程序或java)

    视频讲解:https://edu.csdn.net/course/detail/23750

    展开全文
  • git 推送本地分支到远程分支 git push origin

    万次阅读 多人点赞 2018-07-27 18:32:57
    转载自:... 推送本地分支local_branch到远程分支 remote_branch并建立关联关系 a.远程已有remote_branch分支并且已经关联本地分支local_branch且本地已经切换到local_branch git...

     

    转载自:https://blog.csdn.net/u013749540/article/details/78295420

     

    创建本地分支看这里 https://blog.csdn.net/torpidcat/article/details/105550496

     

    推送本地分支local_branch到远程分支 remote_branch并建立关联关系

          a.远程已有remote_branch分支并且已经关联本地分支local_branch且本地已经切换到local_branch

              git push

         b.远程已有remote_branch分支但未关联本地分支local_branch且本地已经切换到local_branch

             git push -u origin/remote_branch

         c.远程没有remote_branch分支并,本地已经切换到local_branch

            git push origin local_branch:remote_branch

    展开全文
  • 小米推送,华为推送,个推,阿里云推送集成(服务端JAVA开发)
  • Android推送 百度云推送 入门篇

    万次阅读 多人点赞 2014-05-27 20:52:27
    现在app基本都有推送的功能,于是看了下百度云的推送,官方文档和Demo都很到位,记录下使用过程,目标是利用百度云推送最为服务器写个及时通讯的例子~当然了,这是第一篇入门~ 1、第一步就是在百度开发者服务管理中...
  • 在使用git推送项目时候出现 "fatal: The remote end hung up unexpectedly " 原因是推送的文件太大。 解决方案: 在克隆/创建版本库生成的 .git目录下面修改生成的config文件增加如下: [http] postBuffer = ...
  • android 之推送(本地推送远程推送

    千次阅读 2016-05-16 10:11:41
    在现在的app中推送技术很普遍,推送可以提高用户活跃度,也可以进行一些活动推送,今天就跟大家聊一下推送 推送分为远程推送和本地推送,都可以通过推送消息跳转到一些应用或者活动界面,本地推送几行代码就可以...
  • iOS 推送,删除指定推送消息 远程推送经常会出现收到重复推送的问题,或者想删除某条推送消息的问题,本文将详细说明 静默推送 在 iOS10 之后 Apple 新增了静默推送的功能,使 App 可以在收到推送之后执行一段...
  • 在Android开发中,消息推送功能的使用非常常见。为了降低开发成本,使用第三方推送是现今较为流行的解决方案。 今天,我将手把手教大家如何在你的应用里集成小米推送 该文档基于小米推送官方Demo,并给出简易推送...
  • 一直更新了iOS8,但是一直没有开始研究这个iOS8,今天因为项目用到了推送,于是体验了iOS8的推送,先讲讲这个推送。目前分为四个推送:用户推送,本地推送,远程推送,地理位置推送。 ...
  • 极光推送在众多的消息推送里,口碑算是很好的,项目中我负责的是这一块,就整理了这篇博客帮助记忆; 极光推送官方SDK文档:https://docs.jiguang.cn/jpush/server/sdk/java_sdk/ 错误码信息:...
  • Git 推送和删除远程标签

    万次阅读 2013-01-31 09:24:20
    事实上Git 的推送和删除远程标签命令是相同的,删除操作实际上就是推送空的源标签refs: git push origin 标签名 相当于 git push origin refs/tags/源标签名:refs/tags/目的标签名 git push 文档中有解释: ...
  • 本地推送.极光推送.APNs推送

    千次阅读 2015-12-30 14:47:38
    1.本地推送的简单应用 2.APNs介绍 3.使用极光推送向你的APP推送一条信息 一.本地推送 本地通知推送也叫闹钟通知, 这是通知中比较简单的一部分. 如果将app比喻成一个人, 某个controller比喻成身体的某一部分(手臂), ...
  • 极光推送中有指定Registration ID推送或根据唯一标识推送的方法吗?文档我看了没有针对这个问题的解释。同时也没有看到文档中提供的相关接口。
  • JPush极光推送详解

    万次阅读 2016-10-19 22:26:52
    JPush是一种专业而免费的第三方云推送推送平台,又名极光推送,在同行中开发者对JPush较为青睐,哈哈,不仅仅是因为它是免费的,服务也是非常到位的。 2.JPush的集成步骤 1.新建一个工程,复制自己的应用包名。 2....
  • 微信小程序 消息推送功能实现详解

    万次阅读 多人点赞 2018-09-05 19:30:57
    在小程序开发中,如果想实现:用户发给小程序的消息以及开发者需要的事件推送,在小程序项目中,我们想要实现这样一个功能, 比如我们小程序中的客服功能,我不想要使用小程序后台的在线客服功能,但我又想实现客服...
  • 个推推送模板及推送方式

    千次阅读 2017-05-07 17:58:52
    四种推送模板 点击通知打开应用模板 NotificationTemplate • 场景 1:针对沉默用户,发送推送消息,点击消息栏的通知可直接激活启动应用,提升应用的转化率 点击通知打开网页模板 LinkTemplate • 场景 1:推广...
  • 1. Java服务器端推送,web端接收推送信息 2. Web端推送,web端接收推送信息
  • 1.本地推送 关于本地推送的作用及意义:【后期完善】 实现步骤 大致4步:1创建通知、2注册通知、3实现函数、4删除通知 注意! 下面的内容中没有提到:在使用通知的时候,为了避免重复注册:最好在生命周期的函数...
  • 转载请以链接形式标明出处:  ...本文出自:【lxk_1993的博客】;  推送SDK 信鸽 个推 SDK占用空间 armeabi:1.76M 全部:2.31M (都包含android-support
  • 最近公司需要 Android 的消息推送,在网上找了很多消息推送的例子,都是无法运行. 经过一段时间的研究,终于把例子运行起来了。现在共享出来,共同学习。 在 androidpn 的官网下载...
  • Java调用极光推送推送消息

    千次阅读 2017-02-25 17:37:30
    极光推送
  • 个推推送模板详解

    千次阅读 2019-12-23 18:04:42
    作为专业的消息推送服务商,个推为开发者提供了不同种类的推送模板,以实现相应的推送功能。推送模板可以单用,也可以组合使用。为帮助APP开发者更快速地找到适合的推送模板,实现所想要的推送效果,我们写下这篇...
  • Android消息推送:第三方消息推送平台详细解析

    万次阅读 多人点赞 2016-10-19 19:33:50
    消息推送在Android开发中应用的场景是越来越多了,比如说电商产品进行活动宣传、资讯类产品进行新闻推送等等,如下图: 本文将介绍Android中实现消息推送的第三方推送的详细解决方案 阅读本文前,建议先阅读我的写...
  • 市面上目前做免费推送服务的有很多,友盟、极光、百度、小米、华为等,由于android机型的多样性,在使用单独的一种推送时,往往会造成一些机型无法获取(当然,内部原因可能很复杂)。Android 第三方 Push 推送方案...
  • 在上一章节,我们了解了推送的基本概念,在本章节,我们开始使用百度推送、小米推送、华为推送,并分析他们在使用上的不同第三方推送的官方网址 友盟推送 极光推送 百度推送 小米推送 华为推送 Pushbullet 集成第三...
  • uniapp集成unipush推送后,会遇到ios保持应用在前台或后台时无法收到通知栏消息问题,只能在应用中监听推送然后创建本地通知,此篇文章根据这个问题进行处理推送监听分别执行不同的业务。 完整处理示例 var ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,786
精华内容 37,914
关键字:

推送