Strophe简介与Openfire配置

Strophe.js是为XMPP写的一个js类库。因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接。

官方文档:

http://strophe.im/strophejs/doc/1.2.15/files/strophe-js.html

https://stackoverflow.com/questions/17311901/strophe-js-giving-authfail-status-always

Strophe操作相关插件:

https://github.com/ggozad/strophe.plugins/

https://github.com/gm19900510/strophejs-plugins

连接状态常量
发起一个链接后,会返回一个连接状态

Status.ERROR
错误
Status.CONNECTING
正在创建连接
Status.CONNFAIL
连接创建失败
Status.AUTHENTICATING
正在验证
Status.AUTHFAIL
验证失败
Status.CONNECTED
连接创建成功
Status.DISCONNECTED
连接已关闭
Status.DISCONNECTING
连接正在关闭

XMPP服务器通常会实现BOSH扩展,下面是Openfire和Tigase的BOSH默认URL:

Openfire:http://host:7070/http-bind
Tigase:http://host:5280

在使用Strophe.js的时候,需要使用对应的HTTP地址才能连接上XMPP服务器。

如果使用Opnefire,还需要在管理后台配置一下:

注意图中的pc-20170308pkrs是下图的服务器名称

XMPP协议简介:

XMPP服务器和客户端之间,是通过XML节(XML Stanza)来进行通讯。其中有三种非常重要的XML Stanza类型:<message>、<presence>、<iq>。

<message>:

聊天消息的发送和接收就是通过message节来实现。例如xxg1@host发送一条信息"你好"给xxg2@host,xxg1@host客户端会将下面的这段XML发送到XMPP服务器,服务器再推送给xxg2@host客户端。其中<message>的from属性是发送者,to属性是接收者,<body>子元素的内容就是聊天信息。

<message from="a@pc-20170308pkrs" to="b@pc-20170308pkrs" type="chat">
<body>你好</body>
</message>
<presence>:

可用于表明用户的状态,例如用户状态改变成“Do not disturb”(“请勿打扰”),会向服务器发送:

<presence from="xxg@host">
<status>Do not disturb</status>
<priority>0</priority>
<show>dnd</show>
</presence>
<iq>:

iq即Info/Query,采用“请求-响应”机制,类似于HTTP的机制。下面的例子是客户端通过<iq>请求获取联系人,XMPP服务器将结果返回:

客户端请求获取联系人:

<iq from='a@pc-20170308pkrs' id='bv1bs71f' type='get'>
<query xmlns='jabber:iq:roster'/>
</iq>

服务器结果返回:
  

<iq to='a@pc-20170308pkrs' id='bv1bs71f' type='result'>
<query xmlns='jabber:iq:roster'>
<item jid='b@pc-20170308pkrs'/>
<item jid='c@pc-20170308pkrs'/>
</query>
</iq>

构建WebIM

新建echobot.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Strophe.js Echobot Example</title>
<script type='text/javascript'
src='jquery-1.9.1.min.js'></script>
<script type='text/javascript'
src='strophe.min.js'></script>
<script type='text/javascript'
src='echobot.js'></script>
</head>
<body>
JID:<input type="text" id="input-jid" value="gm@pc-20170308pkrs">
<br>
密码:<input type="password" id="input-pwd">
<br>
<button id="btn-login">登录</button>
<div id="msg" style="height: 400px; width: 400px; overflow: scroll;"></div>
联系人JID:
<input type="text" id="input-contacts">
<br>
消息:
<br>
<textarea id="input-msg" cols="30" rows="4"></textarea>
<br>
<button id="btn-send">发送</button>
</body>
</html>

新建echobot.js

var BOSH_SERVICE = 'http://pc-20170308pkrs:7070/http-bind/';
// XMPP连接
var connection = null; // 当前状态是否连接
var connected = false; // 当前登录的JID
var jid = ""; // 连接状态改变的事件
function onConnect(status) {
    console.log('status: ' + status)
    if (status == Strophe.Status.CONNFAIL) {
        alert("连接失败!");
    } else if (status == Strophe.Status.AUTHFAIL) {
        alert("登录失败!");
    } else if (status == Strophe.Status.DISCONNECTED) {
        alert("连接断开!");
        connected = false;
    } else if (status == Strophe.Status.CONNECTED) {
        alert("连接成功,可以开始聊天了!");
console.log('pubsub',connection)
        connected = true;         // 当接收到<message>节,调用onMessage回调函数
        connection.addHandler(onMessage, null, null, null, null, null);         // 首先要发送一个<presence>给服务器(initial presence)
        connection.send($pres().tree()); //获取订阅的主题信息
connection.pubsub.getSubscriptions(onMessage,5000);     }
} // 接收到<message>
function onMessage(msg) {     console.log('--- msg ---', msg);     // 解析出<message>的from、type属性,以及body子元素
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');     if (type == "chat" && elems.length > 0) {
        var body = elems[0];
        $("#msg").append(from + ":<br>" + Strophe.getText(body) + "<br>")
    }
    return true;
} $(document).ready(function() {     // 通过BOSH连接XMPP服务器
    $('#btn-login').click(function() {
        if(!connected) {
            console.log('jid: ' + $("#input-jid").val());
            console.log('pwd: ' + $("#input-pwd").val());
            connection = new Strophe.Connection(BOSH_SERVICE);
            connection.connect($("#input-jid").val(), $("#input-pwd").val(), onConnect);
            jid = $("#input-jid").val();
        }
    });     // 发送消息
    $("#btn-send").click(function() {
        if(connected) {
            if($("#input-contacts").val() == '') {
                alert("请输入联系人!");
                return;
            }             // 创建一个<message>元素并发送
            var msg = $msg({
                to: $("#input-contacts").val(),
                from: jid, 
                type: 'chat'
            }).c("body", null, $("#input-msg").val());
            connection.send(msg.tree());             $("#msg").append(jid + ":<br>" + $("#input-msg").val() + "<br>");
            $("#input-msg").val('');
        } else {
            alert("请先登录!");
        }
    });
});

strophe.plugins插件使用(connection+“插件名称”+ “对应方法”)

connection.pubsub.getSubscriptions(onMessage,5000);

新建strophe.pubsub.js

/*
This program is distributed under the terms of the MIT license.
Please see the LICENSE file for details.
Copyright 2008, Stanziq Inc.
Overhauled in October 2009 by Liam Breck [How does this affect copyright?]
*/ /** File: strophe.pubsub.js
* A Strophe plugin for XMPP Publish-Subscribe.
*
* Provides Strophe.Connection.pubsub object,
* parially implementing XEP 0060.
*
* Strophe.Builder.prototype methods should probably move to strophe.js
*/ /** Function: Strophe.Builder.form
* Add an options form child element.
*
* Does not change the current element.
*
* Parameters:
* (String) ns - form namespace.
* (Object) options - form properties.
*
* Returns:
* The Strophe.Builder object.
*/
Strophe.Builder.prototype.form = function (ns, options)
{
var aX = this.node.appendChild(Strophe.xmlElement('x', {"xmlns": "jabber:x:data", "type": "submit"}));
aX.appendChild(Strophe.xmlElement('field', {"var":"FORM_TYPE", "type": "hidden"}))
.appendChild(Strophe.xmlElement('value'))
.appendChild(Strophe.xmlTextNode(ns)); for (var i in options) {
aX.appendChild(Strophe.xmlElement('field', {"var": i}))
.appendChild(Strophe.xmlElement('value'))
.appendChild(Strophe.xmlTextNode(options[i]));
}
return this;
}; /** Function: Strophe.Builder.list
* Add many child elements.
*
* Does not change the current element.
*
* Parameters:
* (String) tag - tag name for children.
* (Array) array - list of objects with format:
* { attrs: { [string]:[string], ... }, // attributes of each tag element
* data: [string | XML_element] } // contents of each tag element
*
* Returns:
* The Strophe.Builder object.
*/
Strophe.Builder.prototype.list = function (tag, array)
{
for (var i=0; i < array.length; ++i) {
this.c(tag, array[i].attrs)
this.node.appendChild(array[i].data.cloneNode
? array[i].data.cloneNode(true)
: Strophe.xmlTextNode(array[i].data));
this.up();
}
return this;
}; Strophe.Builder.prototype.children = function (object) {
var key, value;
for (key in object) {
if (!object.hasOwnProperty(key)) continue;
value = object[key];
if (Array.isArray(value)) {
this.list(key, value);
} else if (typeof value === 'string') {
this.c(key, {}, value);
} else if (typeof value === 'number') {
this.c(key, {}, ""+value);
} else if (typeof value === 'object') {
this.c(key).children(value).up();
} else {
this.c(key).up();
}
}
return this;
}; // TODO Ideas Adding possible conf values?
/* Extend Strophe.Connection to have member 'pubsub'.
*/
Strophe.addConnectionPlugin('pubsub', {
/*
Extend connection object to have plugin name 'pubsub'.
*/
_connection: null,
_autoService: true,
service: null,
jid: null, //The plugin must have the init function.
init: function(conn) { this._connection = conn; /*
Function used to setup plugin.
*/ /* extend name space
* NS.PUBSUB - XMPP Publish Subscribe namespace
* from XEP 60.
*
* NS.PUBSUB_SUBSCRIBE_OPTIONS - XMPP pubsub
* options namespace from XEP 60.
*/
Strophe.addNamespace('PUBSUB',"http://jabber.org/protocol/pubsub");
Strophe.addNamespace('PUBSUB_SUBSCRIBE_OPTIONS',
Strophe.NS.PUBSUB+"#subscribe_options");
Strophe.addNamespace('PUBSUB_ERRORS',Strophe.NS.PUBSUB+"#errors");
Strophe.addNamespace('PUBSUB_EVENT',Strophe.NS.PUBSUB+"#event");
Strophe.addNamespace('PUBSUB_OWNER',Strophe.NS.PUBSUB+"#owner");
Strophe.addNamespace('PUBSUB_AUTO_CREATE',
Strophe.NS.PUBSUB+"#auto-create");
Strophe.addNamespace('PUBSUB_PUBLISH_OPTIONS',
Strophe.NS.PUBSUB+"#publish-options");
Strophe.addNamespace('PUBSUB_NODE_CONFIG',
Strophe.NS.PUBSUB+"#node_config");
Strophe.addNamespace('PUBSUB_CREATE_AND_CONFIGURE',
Strophe.NS.PUBSUB+"#create-and-configure");
Strophe.addNamespace('PUBSUB_SUBSCRIBE_AUTHORIZATION',
Strophe.NS.PUBSUB+"#subscribe_authorization");
Strophe.addNamespace('PUBSUB_GET_PENDING',
Strophe.NS.PUBSUB+"#get-pending");
Strophe.addNamespace('PUBSUB_MANAGE_SUBSCRIPTIONS',
Strophe.NS.PUBSUB+"#manage-subscriptions");
Strophe.addNamespace('PUBSUB_META_DATA',
Strophe.NS.PUBSUB+"#meta-data");
Strophe.addNamespace('ATOM', "http://www.w3.org/2005/Atom"); if (conn.disco)
conn.disco.addFeature(Strophe.NS.PUBSUB); }, // Called by Strophe on connection event
statusChanged: function (status, condition) {
var that = this._connection;
if (this._autoService && status === Strophe.Status.CONNECTED) {
this.service = 'pubsub.'+Strophe.getDomainFromJid(that.jid);
this.jid = that.jid;
}
}, /***Function
Parameters:
(String) jid - The node owner's jid.
(String) service - The name of the pubsub service.
*/
connect: function (jid, service) {
var that = this._connection;
if (service === undefined) {
service = jid;
jid = undefined;
}
this.jid = jid || that.jid;
this.service = service || null;
this._autoService = false;
}, /***Function
Create a pubsub node on the given service with the given node
name.
Parameters:
(String) node - The name of the pubsub node.
(Dictionary) options - The configuration options for the node.
(Function) call_back - Used to determine if node
creation was sucessful.
Returns:
Iq id used to send subscription.
*/
createNode: function(node,options, call_back) {
var that = this._connection; var iqid = that.getUniqueId("pubsubcreatenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', {xmlns:Strophe.NS.PUBSUB})
.c('create',{node:node});
if(options) {
iq.up().c('configure').form(Strophe.NS.PUBSUB_NODE_CONFIG, options);
} that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree());
return iqid;
}, /** Function: deleteNode
* Delete a pubsub node.
*
* Parameters:
* (String) node - The name of the pubsub node.
* (Function) call_back - Called on server response.
*
* Returns:
* Iq id
*/
deleteNode: function(node, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubdeletenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', {xmlns:Strophe.NS.PUBSUB_OWNER})
.c('delete', {node:node}); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /** Function
*
* Get all nodes that currently exist.
*
* Parameters:
* (Function) success - Used to determine if node creation was sucessful.
* (Function) error - Used to determine if node
* creation had errors.
*/
discoverNodes: function(success, error, timeout) { //ask for all nodes
var iq = $iq({from:this.jid, to:this.service, type:'get'})
.c('query', { xmlns:Strophe.NS.DISCO_ITEMS }); return this._connection.sendIQ(iq.tree(),success, error, timeout);
}, /** Function: getConfig
* Get node configuration form.
*
* Parameters:
* (String) node - The name of the pubsub node.
* (Function) call_back - Receives config form.
*
* Returns:
* Iq id
*/
getConfig: function (node, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubconfigurenode"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', {xmlns:Strophe.NS.PUBSUB_OWNER})
.c('configure', {node:node}); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /**
* Parameters:
* (Function) call_back - Receives subscriptions.
*
* http://xmpp.org/extensions/tmp/xep-0060-1.13.html
* 8.3 Request Default Node Configuration Options
*
* Returns:
* Iq id
*/
getDefaultNodeConfig: function(call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubdefaultnodeconfig"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER})
.c('default'); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /***Function
Subscribe to a node in order to receive event items.
Parameters:
(String) node - The name of the pubsub node.
(Array) options - The configuration options for the node.
(Function) event_cb - Used to recieve subscription events.
(Function) success - callback function for successful node creation.
(Function) error - error callback function.
(Boolean) barejid - use barejid creation was sucessful.
Returns:
Iq id used to send subscription.
*/
subscribe: function(node, options, event_cb, success, error, barejid) {
var that = this._connection;
var iqid = that.getUniqueId("subscribenode"); var jid = this.jid;
if(barejid)
jid = Strophe.getBareJidFromJid(jid); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', { xmlns:Strophe.NS.PUBSUB })
.c('subscribe', {'node':node, 'jid':jid});
if(options) {
iq.up().c('options').form(Strophe.NS.PUBSUB_SUBSCRIBE_OPTIONS, options);
} //add the event handler to receive items
that.addHandler(event_cb, null, 'message', null, null, null);
that.sendIQ(iq.tree(), success, error);
return iqid;
}, /***Function
Unsubscribe from a node.
Parameters:
(String) node - The name of the pubsub node.
(Function) success - callback function for successful node creation.
(Function) error - error callback function.
*/
unsubscribe: function(node, jid, subid, success, error) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubunsubscribenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', { xmlns:Strophe.NS.PUBSUB })
.c('unsubscribe', {'node':node, 'jid':jid});
if (subid) iq.attrs({subid:subid}); that.sendIQ(iq.tree(), success, error);
return iqid;
}, /***Function
Publish and item to the given pubsub node.
Parameters:
(String) node - The name of the pubsub node.
(Array) items - The list of items to be published.
(Function) call_back - Used to determine if node
creation was sucessful.
*/
publish: function(node, items, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubpublishnode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', { xmlns:Strophe.NS.PUBSUB })
.c('publish', { node:node, jid:this.jid })
.list('item', items); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /*Function: items
Used to retrieve the persistent items from the pubsub node.
*/
items: function(node, success, error, timeout) {
//ask for all items
var iq = $iq({from:this.jid, to:this.service, type:'get'})
.c('pubsub', { xmlns:Strophe.NS.PUBSUB })
.c('items', {node:node}); return this._connection.sendIQ(iq.tree(), success, error, timeout);
}, /** Function: getSubscriptions
* Get subscriptions of a JID.
*
* Parameters:
* (Function) call_back - Receives subscriptions.
*
* http://xmpp.org/extensions/tmp/xep-0060-1.13.html
* 5.6 Retrieve Subscriptions
*
* Returns:
* Iq id
*/
getSubscriptions: function(call_back, timeout) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubsubscriptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', {'xmlns':Strophe.NS.PUBSUB})
.c('subscriptions'); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree());
console.log('-- iq pubsub --',iq.tree())
return iqid;
}, /** Function: getNodeSubscriptions
* Get node subscriptions of a JID.
*
* Parameters:
* (Function) call_back - Receives subscriptions.
*
* http://xmpp.org/extensions/tmp/xep-0060-1.13.html
* 5.6 Retrieve Subscriptions
*
* Returns:
* Iq id
*/
getNodeSubscriptions: function(node, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubsubscriptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER})
.c('subscriptions', {'node':node}); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /** Function: getSubOptions
* Get subscription options form.
*
* Parameters:
* (String) node - The name of the pubsub node.
* (String) subid - The subscription id (optional).
* (Function) call_back - Receives options form.
*
* Returns:
* Iq id
*/
getSubOptions: function(node, subid, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubsuboptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', {xmlns:Strophe.NS.PUBSUB})
.c('options', {node:node, jid:this.jid});
if (subid) iq.attrs({subid:subid}); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /**
* Parameters:
* (String) node - The name of the pubsub node.
* (Function) call_back - Receives subscriptions.
*
* http://xmpp.org/extensions/tmp/xep-0060-1.13.html
* 8.9 Manage Affiliations - 8.9.1.1 Request
*
* Returns:
* Iq id
*/
getAffiliations: function(node, call_back) {
var that = this._connection;
var iqid = that.getUniqueId("pubsubaffiliations"); if (typeof node === 'function') {
call_back = node;
node = undefined;
} var attrs = {}, xmlns = {'xmlns':Strophe.NS.PUBSUB};
if (node) {
attrs.node = node;
xmlns = {'xmlns':Strophe.NS.PUBSUB_OWNER};
} var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid})
.c('pubsub', xmlns).c('affiliations', attrs); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /**
* Parameters:
* (String) node - The name of the pubsub node.
* (Function) call_back - Receives subscriptions.
*
* http://xmpp.org/extensions/tmp/xep-0060-1.13.html
* 8.9.2 Modify Affiliation - 8.9.2.1 Request
*
* Returns:
* Iq id
*/
setAffiliation: function(node, jid, affiliation, call_back) {
var that = this._connection;
var iqid = thiat.getUniqueId("pubsubaffiliations"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid})
.c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER})
.c('affiliations', {'node':node})
.c('affiliation', {'jid':jid, 'affiliation':affiliation}); that.addHandler(call_back, null, 'iq', null, iqid, null);
that.send(iq.tree()); return iqid;
}, /** Function: publishAtom
*/
publishAtom: function(node, atoms, call_back) {
if (!Array.isArray(atoms))
atoms = [atoms]; var i, atom, entries = [];
for (i = 0; i < atoms.length; i++) {
atom = atoms[i]; atom.updated = atom.updated || (new Date()).toISOString();
if (atom.published && atom.published.toISOString)
atom.published = atom.published.toISOString(); entries.push({
data: $build("entry", { xmlns:Strophe.NS.ATOM })
.children(atom).tree(),
attrs:(atom.id ? { id:atom.id } : {}),
});
}
return this.publish(node, entries, call_back);
}, });

效果:

XMPP即时通讯协议使用(七)——利用Strophe实现WebIM及strophe.plugins插件使用的更多相关文章

  1. XMPP即时通讯协议使用(前传)——协议详解

    XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...

  2. XMPP即时通讯协议使用(六)——开发Openfire聊天记录插件

    转载地址:http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html 开发环境: Sys ...

  3. XMPP即时通讯协议使用(十二)——基于xmpp搭建简单的局域网WebRTC

    创建HTML和JS ofwebrtc.html <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" ...

  4. xmpp即时通讯协议的特性---长处和缺点!

    xmpp协议的定义? XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性. 因此.基于XMPP的应用具有超强的可扩展性.经过扩展以后的XMPP能够通过发送扩展的信 ...

  5. XMPP即时通讯协议使用(十)——好友关系状态

    sub  ask  recv 订阅 询问 接受 含义 substatus -1-  应该删除这个好友          Indicates that the roster item should be ...

  6. XMPP即时通讯协议使用(四)——Openfire服务器源码编译与添加消息记录保存

    下载Openfire源码 下载地址:https://www.igniterealtime.org/downloads/index.jsp,当前最新版本为:4.2.3 Eclipse上部署Openfir ...

  7. XMPP即时通讯协议使用(三)——订阅发布、断开重连与Ping

    package com.testV3; import java.util.List; import org.jivesoftware.smack.ConnectionListener; import ...

  8. XMPP即时通讯协议使用(十三)——获取当前在线用户或关闭指定用户

    1.开启REST API插件或根据需求修改其插件源码: 2.添加服务器->服务器管理->系统属性中添加 plugin.restapi.enabled=true 3.pom依赖 <de ...

  9. XMPP即时通讯协议使用(二)——基于Smack相关操作

    package com.test; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator ...

随机推荐

  1. ps:图像尺寸

    在课程#01中我们知道了显示器上的图像是由许多点构成的,这些点称为像素,意思就是“构成图像的元素”.但是要明白一点:像素作为图像的一种尺寸,只存在于电脑中,如同RGB色彩模式一样只存在于电脑中.像素是 ...

  2. POJ 3889 Fractal Streets(逼近模拟)

    $ POJ~3889~Fractal~Streets $(模拟) $ solution: $ 这是一道淳朴的模拟题,最近发现这种题目总是可以用逼近法,就再来练练手吧. 首先对于每个编号我们可以用逼近法 ...

  3. js 鼠标效果

    一. 鼠标悬停效果和离开效果 鼠标效果和v-if 配合使用效果很好 <a class="all btn" href="#" v-on:mouseover= ...

  4. k8s手动安装-1

    1.组网master可以使用双网卡,一个外网网卡连接外网,并且做proxy server,一个host-only网卡和node连接. 新版vitualbox配置host-only需要在主机网络管理器中 ...

  5. w = tf.Variable(<initial-value>, name=<optional-name>)

    w = tf.Variable(<initial-value>, name=<optional-name>)

  6. 对webpack的初步研究4

    Mode string module.exports = { mode: 'production' }; webpack --mode=production The following string ...

  7. proxyTable-后端代理-跨域请求数据

    config >>> index.js  配置 proxyTable: { '/api': { target:'https://api.jisuapi.com', // 你请求的第三 ...

  8. CentOS 7.5 通过kubeadm部署k8s-1.15.0

    kubeadm是Kubernetes官方提供的用于快速安装Kubernetes集群的工具,伴随Kubernetes每个版本的发布都会同步更新,kubeadm会对集群配置方面的一些实践做调整,通过实验k ...

  9. Netty精进01

    为什么要学习Netty? 目前基于Netty实现的一些优秀的开源框架:Dubbo.RocketMQ.Spark.Spring5.Flink.ElasticSearch.gRPC……这些还说明不了为什么 ...

  10. RESTful三理解

    目录 目录 前言 Web应用的会话状态 Cookie 资源的表现形式 HATEOAS RESTful 资源 URI 前言 最近看了一篇很赞的RESTful博客,传送门:http://www.cnblo ...