微信小程序之环信接入

IM 聊天接入思考过程

前期

初识IM聊天

带着问题去调研

  • 必须接入环信吗?除了环信是否可以接入其他即时通信?
  • 环信目前有哪些功能呢?支持微信小程序吗?
  • 如何接入小程序呢?

调研分析

  1. 必须接入环信吗?除了环信是否可以接入其他即时通信?

现状: 微信小程序API 提供了WebSocket 方法。
扩展: 如果服务端支持scoket通信,ios\android\H5 也全都支持Im聊天了
备注:专业第三方Im有融云、环信、云之讯等,底层实现均是基于scoket 通信。明白scoket通信后也可以自己写即时通信。

  1. 环信目前有哪些功能呢?支持微信小程序吗?
    错误想法: 环信就是做im聊天的,咱们上去按照接入文档,开发就能搞定!!!

这种想法是很致命的。在所有的第三方组件接入中,如果我们不能跳出来看待问题,只是为了完成任务而完成任务。那么我们永远是最底层的低级码农。
环信目前是同行业里面做的算不错的。那么他的官网、接入规范都应该有的。微信小程序也是支持的。在后面小编会带领大家一切怎么去阅读一个官网

  1. 如何接入小程序?

接入小程序是否需要申请一个账号呢?我直接运行他们的demo可以吗? 怎么去测试呢? 此时我们可以有很多的猜想。我认为在开始接入之前我们应该很好的进行一些思考,答案显而易见。

环信接入思考篇

快即时慢

在工作中,大家会经常遇到第三方组件的接入。当接收到任务后,为了尽快完成任务。上来就google,找攻略,找技巧。往往认为这样做速度是最快的。结果适得其反,做了很多无用的功。我们意识中的快,结果却变成了慢

慢即时快

image

逆向思维: 任何一个第三方的组件,特别是一个大点的平台,他们为了推出自己的产品,一定会有各种各样的功能支持,接入文档说明。我们放慢速度,将这些资源用上半天的时间进行简单的梳理。后期的开发进度会有很大的提升。
上图是我在接入环信Im后进行的反思。因为在接入环信之前,其他团队成员用了很长的时间联调。假如他们在接入环信聊天之前,了解环信拥有自己的后台,可以直接给用户端发送测试消息;可以直接创建用户、创建聊天室、创建群组。他们还会花费那么久的时间去联调吗?完全不用依赖服务端。不用依赖ios,依赖android。自己使用环信后台,轻轻松松完成各种测试。

环信接入

  1. 环信官网注册自己的即时通讯云,并登陆后台


    image
  2. 创建自己的应用,并记录关键信息


    image

    以下是关键信息哦!!!


    image

备注:

  1. 应用标识 应用接入时会使用
  2. IM 用户 可以创建、删除用户、发送消息
  3. 群组 可以创建、删除群组信息、发送消息
  4. 聊天室 可以创建、删除聊天室、发送消息
    tip 通过这个后台管理系统,就可以玩转环信的接入测试了。
  1. 从环信下载小程序demo,替换 appkey 进行联调测试


    image
  2. 测试走起
  1. 用户测试 在环信后台创建用户,在小程序端登录 (用户demo1 密码:123456)


    image
  2. 一对一会话测试
    ① 在环信后台创建用户demo2
    ② 点击操作,查看用户好友将demo1和demo2 添加为好友。
    ③ 在小程序端用demo1给demo2发送测试消息。
    ④ 退出demo1用户,登录demo2查看是否会接收到demo1发送的会话


    image

由于环信工程师们相信码农的实力,在群组测试和聊天室测试这块为大家留下了想象空间。demo 中群组测试和聊天室测试为明确写出。让我继续带大家飞

  1. 群组测试
    ① 创建群组记录群组id,并给群组添加成员(demo2)


    image

    ② 环信后台给群组发送测试消息


    image

    ③ 控制台能收到群组测试消息,怎么展示呢? 请阅读源码解析篇
  2. 聊天室测试
    ① 创建聊天室记录聊天室id,将demo1 设置为超级管理员,demo2设置为管理员
    ② 聊天室这里没有聊天室消息的发送。请阅读源码解析篇

通过以上4个简单的测试,android、ios、h5、小程序的聊天测试均可以参照以上4点进行顺利的测试。初期就此结束。下面带代价进行源码的解析

中期

看源码前期思考

image

核心源码阅读

image

以上是环信sdk 基础代码结构。 通过简单阅读会发现:

  1. 环信的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有对应的scoket通信)
  2. 环信的api包装在connection.js 组件中,如果某些api没有,咱们可以扩展connection 中的方法

环信核心代码阅读完成后,发现没有涉及到缓存。看来缓存的处理是在对应的业务逻辑中。

设想:

  1. 消息应该在哪里缓存
  2. 哪里进行会话链接的监听注册

环信demo 代码阅读

会话、群组

通过前面提到的方式,大家可以在小程序控制台抓取到用户收到的会话和群组消息

会话

app.js

环信scoket 注册监听代码在app.js 中
核心代码如下:

{
        //调用API从本地缓存中获取数据
        var that = this
        var logs = wx.getStorageSync('logs') || []
        logs.unshift(Date.now())
        wx.setStorageSync('logs', logs)

        WebIM.conn.listen({
            onOpened: function (message) {//连接成功回调
            // 如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息
            // 手动上线指的是调用conn.setPresence(); 如果conn初始化时已将isAutoLogin设置为true
            // 则无需调用conn.setPresence();             
                WebIM.conn.setPresence()
            },
            onPresence: function (message) { //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息
                switch(message.type){
                    case "unsubscribe":
                        pages[0].moveFriend(message);
                        break;
                    case "subscribe":
                        if (message.status === '[resp:true]') {
                            return
                        } else {
                            pages[0].handleFriendMsg(message)
                        }
                        break;
                    case "joinChatRoomSuccess":
                        console.log('Message: ', message);
                        wx.showToast({
                            title: "JoinChatRoomSuccess",
                        });
                        break;
                    case "memberJoinChatRoomSuccess":
                        console.log('memberMessage: ', message);
                        wx.showToast({
                            title: "memberJoinChatRoomSuccess",
                        });
                        break;
                    case "memberLeaveChatRoomSuccess":
                        console.log("LeaveChatRoom");
                        wx.showToast({
                            title: "leaveChatRoomSuccess",
                        });
                        break;
                }
            },
            onRoster: function (message) { //处理好友申请
                var pages = getCurrentPages()
                if (pages[0]) {
                    pages[0].onShow()
                }
            },

            onVideoMessage: function(message){ //视频处理
                console.log('onVideoMessage: ', message);
                var page = that.getRoomPage()
                if (message) {
                    if (page) {
                        page.receiveVideo(message, 'video')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'video',
                                data: message.url
                            },
                            style: '',
                            time: time,
                            mid: 'video' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },

            onAudioMessage: function (message) { // 音频处理
                console.log('onAudioMessage', message)
                var page = that.getRoomPage()
                console.log(page)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'audio')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'audio',
                                data: value
                            },
                            style: '',
                            time: time,
                            mid: 'audio' + message.id
                        }
                        console.log("Audio msgData: ", msgData);
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },

            onLocationMessage: function (message) { // 收到位置信息
                console.log("Location message: ", message);
            },

            onTextMessage: function (message) {//收到文本消息
                var page = that.getRoomPage()
                console.log(page)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'txt')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'txt',
                                data: value
                            },
                            style: '',
                            time: time,
                            mid: 'txt' + message.id
                        }
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            onEmojiMessage: function (message) { //收到表情信息
                //console.log('onEmojiMessage',message)
                var page = that.getRoomPage()
                //console.log(pages)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'emoji')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'emoji',
                                data: message.data
                            },
                            style: '',
                            time: time,
                            mid: 'emoji' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || [] //tip 从本地缓存中获取用户的消息  发消息+来源 适用于单人会话 msgData.yourname + message.to+当前登录人 群组/聊天室
                         
                        chatMsg.push(msgData)
                        //console.log(chatMsg)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            onPictureMessage: function (message) {//收到图片信息
                //console.log('Picture',message);
                var page = that.getRoomPage()
                if (message) {
                    if (page) {
                        //console.log("wdawdawdawdqwd")
                        page.receiveImage(message, 'img')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'img',
                                data: message.url
                            },
                            style: '',
                            time: time,
                            mid: 'img' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            // 各种异常
            onError: function (error) {
                // 16: server-side close the websocket connection
                if (error.type == WebIM.statusCode.WEBIM_CONNCTION_DISCONNECTED) {
                    if (WebIM.conn.autoReconnectNumTotal < WebIM.conn.autoReconnectNumMax) {
                        return;
                    }

                    wx.showToast({
                        title: 'server-side close the websocket connection',
                        duration: 1000
                    });
                    wx.redirectTo({
                        url: '../login/login'
                    });
                    return;
                }

                // 8: offline by multi login
                if (error.type == WebIM.statusCode.WEBIM_CONNCTION_SERVER_ERROR) {
                    wx.showToast({
                        title: 'offline by multi login',
                        duration: 1000
                    })
                    wx.redirectTo({
                        url: '../login/login'
                    })
                    return;
                }
            },
        })

        
    }

实际开发过程中,在微信中,退出小程序,重新进入时,webscoket 通信并没有重新创建链接。存在用户收到不到消息的情况。可以将以上代码封装,例如addHXLIstener(...)。当用户重新打开后,再次注册环信监听即可。

环信登录 例如 initLoginHX();

    var uin=wx.getStorageSync('hxuin');
    var pwd=wx.getStorageSync('hxpwd');
    console.log('initHX:' + uin+"||"+pwd);
    var options = {
      apiUrl: '服务器url',
      user: '用户名',// 用户名要是字符
      pwd: '密码',
      grant_type: 'password',
      appKey: 'appkey',
      success: function (res) {
        console.log("环信创建连接成功")
      },
      error: function (res) {
        console.log("环信创建连接失败")
      }
    };
    WebIM.conn.open(options);

chat 会话

环信的会话列表存储在本地,并没有调用服务器端数据

 var that = this
        var member = wx.getStorageSync('member')
        var myName = wx.getStorageSync('myUsername')
        var array = []
        for (var i = 0; i < member.length; i++) {
            if (wx.getStorageSync(member[i].name + myName) != '') {
                array.push(wx.getStorageSync(member[i].name + myName)[wx.getStorageSync(member[i].name + myName).length - 1])
            }
        }
        //console.log(array,'1')
        this.setData({
            arr: array
        })

通过以上代码得出结论: 环信的会话是通过遍历用户id+对方id 构成的数据。

那群组和聊天室的怎么处理呢?
环信小程序demo中只提供了聊天室列表的获取接口我们可以轻松实现聊天室列表,并没有提供群组列表的获取方式。我们需要在conection中扩展调用群组列表的接口,来实现群组列表。参照聊天室列表获取即可实现。聊天室列表实现方式如下:

connection.prototype.getChatRooms = function (options) {

    var conn = this,
        token = options.accessToken || this.context.accessToken;

    if (token) {
        var apiUrl = this.apiUrl;
        var appName = this.context.appName;
        var orgName = this.context.orgName;

        if (!appName || !orgName) {
            conn.onError({
                type: _code.WEBIM_CONNCTION_AUTH_ERROR
            });
            return;
        }

        var suc = function (data, xhr) {
            typeof options.success === 'function' && options.success(data);
        };

        var error = function (res, xhr, msg) {
            if (res.error && res.error_description) {
                conn.onError({
                    type: _code.WEBIM_CONNCTION_LOAD_CHATROOM_ERROR,
                    msg: res.error_description,
                    data: res,
                    xhr: xhr
                });
            }
        };

        var pageInfo = {
            pagenum: parseInt(options.pagenum) || 1,
            pagesize: parseInt(options.pagesize) || 20
        };
        // 想要实现群组列表,修改对应接口即可
        var opts = {
            url: apiUrl + '/' + orgName + '/' + appName + '/chatrooms',
            dataType: 'json',
            type: 'GET',
            header: {'Authorization': 'Bearer ' + token},
            data: pageInfo,
            success: suc || _utils.emptyfn,
            fail: error || _utils.emptyfn
        };
        wx.request(opts);
    } else {
        conn.onError({
            type: _code.WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR
        });
    }

chatroom

从本地缓存中获取聊天记录,并展示

// 环信demo 发送消息

 sendMessage: function () {

        if (!this.data.userMessage.trim()) return;


        var that = this
        // //console.log(that.data.userMessage)
        // //console.log(that.data.sendInfo)
        var myName = wx.getStorageSync('myUsername')
        var id = WebIM.conn.getUniqueId();
        var msg = new WebIM.message('txt', id);
        msg.set({
            msg: that.data.sendInfo,
            to: that.data.yourname,
            roomType: false,
            success: function (id, serverMsgId) {
                console.log('send text message success')
            }
        });
        // //console.log(msg)
        console.log("Sending textmessage")
        msg.body.chatType = 'singleChat'; // 群组聊天 groupRoom
        WebIM.conn.send(msg.body);
        // 消息发送完成

        if (msg) {
            var value = WebIM.parseEmoji(msg.value.replace(/\n/mg, '')) // 环信表情处理
            var time = WebIM.time()
            var msgData = {
                info: {
                    to: msg.body.to
                },
                username: that.data.myName,
                yourname: msg.body.to,
                msg: {
                    type: msg.type,
                    data: value
                },
                style: 'self',
                time: time,
                mid: msg.id
            }
            that.data.chatMsg.push(msgData)
            // console.log(that.data.chatMsg)
            
            // 存储聊天记录 
            // 注: 单独单聊天 key 对方环信uin+自己的uin
            // 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin
            wx.setStorage({
                key: that.data.yourname + myName,
                data: that.data.chatMsg,
                success: function () {
                    //console.log('success', that.data)
                    that.setData({
                        chatMsg: that.data.chatMsg,
                        emojiList: [],
                        inputMessage: ''
                    })
                    setTimeout(function () {
                        that.setData({
                            toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
                        })
                    }, 100)
                }
            })
            that.setData({
                userMessage: ''
            })
        }
    },

// 环信demo 收到消息
    receiveMsg: function (msg, type) {
        var that = this
        var myName = wx.getStorageSync('myUsername')
        if (msg.from == that.data.yourname || msg.to == that.data.yourname) {
            if (type == 'txt') {
                var value = WebIM.parseEmoji(msg.data.replace(/\n/mg, ''))
            } else if (type == 'emoji') {
                var value = msg.data
            } else if(type == 'audio'){
                // 如果是音频则请求服务器转码
                console.log('Audio Audio msg: ', msg);
                var token = msg.accessToken;
                console.log('get token: ', token)
                var options = {
                    url: msg.url,
                    header: {
                        'X-Requested-With': 'XMLHttpRequest',
                        'Accept': 'audio/mp3',
                        'Authorization': 'Bearer ' + token
                    },
                    success: function(res){
                        console.log('downloadFile success Play', res);
                        // wx.playVoice({
                            // filePath: res.tempFilePath
                        // })
                        msg.url = res.tempFilePath
                        var msgData = {
                            info: {
                                from: msg.from,
                                to: msg.to
                            },
                            username: '',
                            yourname: msg.from,
                            msg: {
                                type: type,
                                data: value,
                                url: msg.url
                            },
                            style: '',
                            time: time,
                            mid: msg.type + msg.id
                        }

                        if (msg.from == that.data.yourname) {
                            msgData.style = ''
                            msgData.username = msg.from
                        } else {
                            msgData.style = 'self'
                            msgData.username = msg.to
                        }

                        var msgArr = that.data.chatMsg;
                        msgArr.pop();
                        msgArr.push(msgData);

                        that.setData({
                            chatMsg: that.data.chatMsg,
                        })
                        console.log("New audio");
                    },
                    fail: function(e){
                        console.log('downloadFile failed', e);
                    }
                };
                console.log('Download');
                wx.downloadFile(options);
            }
            //console.log(msg)
            //console.log(value)
            var time = WebIM.time()
            var msgData = {
                info: {
                    from: msg.from,
                    to: msg.to
                },
                username: '',
                yourname: msg.from,
                msg: {
                    type: type,
                    data: value,
                    url: msg.url
                },
                style: '',
                time: time,
                mid: msg.type + msg.id
            }
            console.log('Audio Audio msgData: ', msgData);
            if (msg.from == that.data.yourname) {
                msgData.style = ''
                msgData.username = msg.from
            } else {
                msgData.style = 'self'
                msgData.username = msg.to
            }
            //console.log(msgData, that.data.chatMsg, that.data)
            that.data.chatMsg.push(msgData)

            // 存储聊天记录 
            // 注: 单独单聊天 key 对方环信uin+自己的uin
            // 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin

            wx.setStorage({
                key: that.data.yourname + myName,
                data: that.data.chatMsg,
                success: function () {
                    if(type == 'audio')
                        return;
                    //console.log('success', that.data)
                    that.setData({
                        chatMsg: that.data.chatMsg,
                    })
                    setTimeout(function () {
                        that.setData({
                            toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
                        })
                    }, 100)
                }
            })
        }
    },

环信聊天页面,聊天数据全部存储在缓存当中,跟进聊天类型的不同,主要需要调整缓存的key。详情如下:

  1. 单对单聊天 对方uin+自己的uin
  2. 群组聊天(针对某个商品,不需要好友关系,只需要临时聊天) 群组id+对方uin+自己的uin
  3. 聊天室(同群组聊天)

问题大杂烩

  1. 群组聊天缓存如何存储?

答: 缓存key 设置为 群组id+对方uin+自己的uin

  1. 聊天时,如何在聊天中携带扩展信息

答: 消息内容中,ext 支持用户自定义参数传递

var option = {
  msg: data.userMessage.trim(),          // 消息内容
  to: data.groupId,            // 接收消息对象(聊天室id)
  roomType: true,
  chatType: 'groupRoom',
  from: data.myuin,
  ext: {
   //todo 需要补充的字符哦
  },
  success: function () {
    console.log('send room text success');
  },
  fail: function () {
    console.log('failed');
  }
};
```
  1. 会话列表如何实现?

答: 通过接口获取环信的群组列表,通过自己的服务器端补全对应的会话信息。

回顾

整个环信接入,整体围绕 假设-->猜想-->实践完成的。仔细阅读官网,会为大家节约很多时间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容