使用 Openfire 和 Strophe 构建基于 XMPP 的 IM 示例

名词解释

XMPP:Extensible Messaging and Presence Protocol,前称Jabber,是一种以XML为基础的开放式实时通信协议。

Openfire:是开源的,基于XMPP,采用Java开发的即时通讯服务器。

Strophe:一个Javascript版的XMPP类库。

BOSH:Bidirectional-streams Over Synchronous HTTP,为双向同步数据提供一个模拟层,借助这个标准可以建立一个较长的HTTP连接,当有新数据时,马上返回数据并关闭,否则请求失效。无论哪种情况,都会重新建立一个请求。

插件

"strophe.js": "^1.3.5" "strophejs-plugin-rsm": "0.0.2"

demo

// Openfire.js
import { Strophe } from 'strophe.js';
// XMPP服务器BOSH地址
var BOSH_SERVICE = 'http://0.0.0.0:7070/http-bind/';
// XMPP连接
var connection = null;
// 当前状态是否连接
var connected = false;
// 存放添加好友请求
var addFriend_warn = []
var SERVER_DOMAIN = "ance.yixin.dk";

// 登陆
function login(username, password) {
    if (!connected) {
        connection = new Strophe.Connection(BOSH_SERVICE);
        console.log("login -> new Strophe", connection)
        connection.connect(username + `@${SERVER_DOMAIN}`, password, onConnect);
    }
}
// 接收到<message>
function onMessage(msg) {
    // 解析出<message>的from、type属性,以及body子元素
    var from = msg.getAttribute('from');
    // var from = msg.getAttribute('from').match(/(\S*)@/)[1];
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');
    if (type == "chat" && elems.length > 0) {
        var body = elems[0];
        var text = Strophe.getText(body)  // 文本
        console.log("onMessage -> text", text)
        if (text.search('data:image') !== -1) { // base64图片
            imReceiveMsg(text, "img")
        } else {
            imReceiveMsg(text, "text")
        }
    }
    return true;
}

// 发送
function sendMsg(otherId, myselfId, message) {
    if (connected) {
        var msg = $msg({
            to: otherId + `@${SERVER_DOMAIN}`,
            from: myselfId + `@${SERVER_DOMAIN}`,
            type: 'chat',
            xmlns: "jabber:client"
        }).c("body", null, message);
        connection.send(msg.tree());
        connection.send($pres().tree());
    }
}

// 发送前需要将图片转换成bese64
function sendImg(otherId, myselfId, message) {
    if (connected) {
        var msg = $msg({
            to: otherId + `@${SERVER_DOMAIN}`,
            from: myselfId + `@${SERVER_DOMAIN}`,
            type: 'chat',
            xmlns: "jabber:client"
        }).c("body", null, message);
        connection.send(msg.tree());
        connection.send($pres().tree());
    }
}

function onConnect(status) {
    console.log(status)
    console.log(Strophe.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) {
        console.log('连接成功')
        connected = true;
        // getFriends()
        // 当接收到<message>节,调用onMessage回调函数
        connection.addHandler(onMessage, null, 'message', null, null, null);
        connection.addHandler(on_subscription_request, null, "presence", "subscribe");
        // 首先要发送一个<presence>给服务器(initial presence)
        connection.send($pres().tree());
    }
}

// 查询好友列表
function findFriends() {
    var friends = [];
    var iq = $iq({ type: 'get' }).c('query', { xmlns: 'jabber:iq:roster' });
    connection.sendIQ(iq, function(a) {
        $(a).find('item').each(function() {
            var jid = $(this).attr('jid').match(/(\S*)@/)[1];
            friends.push(jid)
        });
    });
    return friends
}


// 添加好友
function getFriend(name) {
    console.log("getFriend -> name", name)
    connection.send($pres({ to: name + `@${SERVER_DOMAIN}`, type: "subscribe" }));
}

// 收到好友请求
function on_subscription_request(stanza) {
    if (stanza.getAttribute("type") == "subscribe") {
        var from = stanza.getAttribute("from").match(/(\S*)@/)[1]
        addFriend_warn.push(from)
    }
    addFriend_warn = Array.from(new Set(addFriend_warn));
    // imReceiveMsg(addFriend_warn, "friends")
}

// 接受邀请
function on_subscription_response(name) {
    connection.send($pres({ to: name + `@${SERVER_DOMAIN}`, type: "subscribed" }));
}
// 拒绝邀请
function on_subscription_reject(name) {
    connection.send($pres({ to: name + `@${SERVER_DOMAIN}`, type: "unsubscribed" }));
}
// 删除好友
function on_subscription_delete(name) {
    connection.send($pres({ to: name + `@${SERVER_DOMAIN}`, type: "unsubscribe" }));
}

export default {
    login,
    sendMsg,
    sendImg,
    findFriends,
    getFriend,
    on_subscription_response,
    on_subscription_request,
    on_subscription_reject,
    addFriend_warn,
    on_subscription_delete
}
<template>
    <div class='test'>
         <!-- 登录 -->
        <Row> <span>账号:</span>
            <Input v-model="userId" placeholder="Enter something..." style="width: 300px" /></Row>
        <Row class="marginTop10"><span>密码:</span>
            <Input v-model="password" placeholder="Enter something..." style="width: 300px" /></Row>
        <!-- 选择聊天对象 -->
        <Row class="marginTop10"><span>选择好友:</span>
            <RadioGroup v-model="chooseFriend">
                <Radio v-for="(ele,index) in friendsList" :key="ele" :label="ele"></Radio> 
            </RadioGroup>
        </Row>
        <Button class="marginTop10" type="primary" @click="login">登录</Button>
        <Button class="marginTop10" type="primary" @click="getFriends">获取好友列表</Button>
        <div class="imgchoose">
            <Button class="marginTop10 imgButton" type="primary">选择图片</Button>
            <input class="imgInput" type="file" @change="sendImgMsg">
        </div>
        <Input class="marginTop10" v-model="msgInfo" type="textarea" :rows="4" placeholder="Enter something..." />
        <Button class="marginTop10" type="primary" @click="sendMsg">发送</Button>
        <!--聊天对话框-->
        <ul class="chatwrap">
            <li :class="{'right':ele.from=='r' }" v-for="(ele,index) in chatList" :key="index">
                <span></span>
                <p></p>
                <img :src="ele.img" alt="" @click="bigImg(ele.img)">
            </li>
        </ul>
        <!-- 查看大图 -->
        <Modal v-model="showBigImg" title="查看图片" @on-cancel="showBigImg=false" footer-hide width="800">
            <img :src="selectImg" alt="" @click="bigImg(ele.img)">
        </Modal>
    </div>
</template>

<script>
    import IM from "./openfire"
    export default {
        name: 'test',
        data() {
            return {
                friendsList: [],
                dataSource: [
                    { key: "订单编号", value: "123121312" },
                    { key: "订单编号", value: "123121312" },
                    { key: "订单编号", value: "123121312" },
                    { key: "订单编号", value: "大口径阿萨德那是扩大绿色大使,dasd 马拉松的马路上的马上到" }
                ],
                selectImg: "",
                showBigImg: false,
                userId: "",
                password: "",
                msgInfo: "",
                chooseFriend: "",
                chatList: [{
                    text: "你好啊",
                    from: "l",
                    time: "2012-05-20",
                    type: "msg"
                }]
            };
        },
        mounted() {
            window.imReceiveMsg = this.imReceiveMsg;
            window.getFriends = this.getFriends;
            window.login = this.login;
            // IM.login("abc", "abc")
        },
        methods: {
            // 获取好友列表
            getFriends() {
                this.friendsList = IM.findFriends();
            },
            // 登录
            login() {
                IM.login(this.userId, this.password)
            },
            // 发送文字消息
            sendMsg() {
                this.chatList.push({
                    text: this.msgInfo,
                    from: "r",
                    time: "2012-05-20"
                })
                IM.sendMsg(this.chooseFriend, this.userId, this.msgInfo)
            },
            // 接收消息
            imReceiveMsg(text, type) {
                if (type == "img") {
                    this.chatList.push({
                        img: text,
                        from: "l",
                        time: "2012-05-20"
                    })
                } else {
                    this.chatList.push({
                        text: text,
                        from: "l",
                        time: "2012-05-20"
                    })
                }
            },
            // 发送图片消息
            sendImgMsg(e) {
                // 文件转base64
                var reader = new FileReader()
                reader.readAsDataURL(e.target.files[0])
                let _this = this;
                reader.onload = function(e) {
                    _this.chatList.push({
                        img: reader.result,
                        from: "r",
                        time: "2012-05-20"
                    })
                    IM.sendMsg(_this.chooseFriend, _this.userId, reader.result)
                }
            },
            // 查看大图
            bigImg(src) {
                this.selectImg = src;
                this.showBigImg = true;
            }
        },
    }
</script>
<style lang='less' scoped>
    .test {
        padding: 10px;
        .marginTop10 {
            margin-top: 10px;
        }
        .chatwrap {
            margin-top: 30px;

            .right {
                text-align: right;
            }
            img {
                width: 100px;
            }
        }
        .imgchoose {
            position: relative !important;
            .imgInput {
                position: absolute !important;
                left: 0;
                top: 10px;
                height: 30px;
                opacity: 0;
                width: 80px;
            }
        }
        .orderTemplate {
            width: 400px;
            padding: 10px;
            background: #fff;
        }
    }
</style>