手寫彈幕服務(wù)器—包看懂篇

socket.io

簡介

使用流行的 web 應(yīng)用技術(shù)棧 —— 比如 LAMP (PHP) —— 來編寫聊天應(yīng)用通常是很困難的幅虑。它包含了輪詢服務(wù)器以檢測變化褂微,還要追蹤時(shí)間戳都办,并且這種實(shí)現(xiàn)是比較慢的美侦。

大多數(shù)實(shí)時(shí)聊天系統(tǒng)通诚敌常基于 socket 來構(gòu)建巾陕。 Socket 為客戶端和服務(wù)器提供了雙向通信機(jī)制。

這意味著服務(wù)器可以 推送 消息給客戶端纪他。無論何時(shí)你發(fā)布一條消息鄙煤,服務(wù)器都可以接收到消息并推送給其他連接到服務(wù)器的客戶端。

web 框架

首先要制作一個(gè) HTML 頁面來提供表單和消息列表茶袒。我們使用了基于 Node.JS 的 web 框架 express 梯刚。 請(qǐng)確保安裝了 Node.JS。

首先創(chuàng)建一個(gè) package.json 來描述我們的項(xiàng)目薪寓。 推薦新建一個(gè)空目錄 (這里使用 chat-example)亡资。

express 已經(jīng)安裝好了澜共。我們現(xiàn)在新建一個(gè) index.js 文件來創(chuàng)建應(yīng)用。

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

http.listen(3000, function(){
  console.log('listening on *:4000');
});
    

這段代碼作用如下:

Express 初始化 app 作為 HTTP 服務(wù)器的回調(diào)函數(shù)沟于。

定義了一個(gè)路由 / 來處理首頁訪問咳胃。

使 http 服務(wù)器監(jiān)聽端口 4000。

HTML 服務(wù)器

目前在 index.js 中我們是通過 res.send 返回一個(gè) HTML 字符串旷太。 如果我們將整個(gè)應(yīng)用的 HTML 代碼都放到應(yīng)用代碼里展懈,代碼結(jié)構(gòu)將變得很混亂。 替代的方法是新建一個(gè) index.html 文件作為服務(wù)器響應(yīng)供璧。

現(xiàn)在我們用 sendFile 來重構(gòu)之前的回調(diào):

app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});

index.html 內(nèi)容如下:


<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

集成 Socket.IO

Socket.IO 由兩部分組成:

  • 一個(gè)服務(wù)端用于集成 (或掛載) 到 Node.JS HTTP 服務(wù)器: socket.io
  • 一個(gè)加載到瀏覽器中的客戶端: socket.io-client

這個(gè)兩部分都會(huì)運(yùn)用到

npm install --save socket.io

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  console.log('a user connected');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

我們通過傳入 http (HTTP 服務(wù)器) 對(duì)象初始化了 socket.io 的一個(gè)實(shí)例存崖。 然后監(jiān)聽 connection 事件來接收 sockets, 并將連接信息打印到控制臺(tái)睡毒。

在 index.html 的 </body> 標(biāo)簽中添加如下內(nèi)容:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

這樣就加載了 socket.io-client来惧。 socket.io-client 暴露了一個(gè) io 全局變量,然后連接服務(wù)器演顾。

請(qǐng)注意我們?cè)谡{(diào)用 io() 時(shí)沒有指定任何 URL供搀,因?yàn)樗J(rèn)將嘗試連接到提供當(dāng)前頁面的主機(jī)。

重新加載服務(wù)器和網(wǎng)站钠至,你將看到控制臺(tái)打印出 “a user connected”葛虐。

每個(gè) socket 還會(huì)觸發(fā)一個(gè)特殊的 disconnect 事件:

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});
    

觸發(fā)事件

Socket.IO 的核心理念就是允許發(fā)送、接收任意事件和任意數(shù)據(jù)棉钧。任意能被編碼為 JSON 的對(duì)象都可以用于傳輸屿脐。二進(jìn)制數(shù)據(jù) 也是支持的。

這里的實(shí)現(xiàn)方案是宪卿,當(dāng)用戶輸入消息時(shí)的诵,服務(wù)器接收一個(gè) chat message 事件。index.html 文件中的 script 部分現(xiàn)在應(yīng)該內(nèi)容如下:

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
  });
</script>

廣播

接下來的目標(biāo)就是讓服務(wù)器將消息發(fā)送給其他用戶佑钾。

要將事件發(fā)送給每個(gè)用戶西疤,Socket.IO 提供了 io.emit 方法:

io.emit('some event', { for: 'everyone' });

為了簡單起見,我們將消息發(fā)送給所有用戶次绘,包括發(fā)送者瘪阁。

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

用法總結(jié)

服務(wù)端

io.on('connection',function(socket));

監(jiān)聽客戶端連接,回調(diào)函數(shù)會(huì)傳遞本次連接的socket

io.sockets.emit('String',data);

給所有客戶端廣播消息

socket.broadcast.emit("msg",{data:"hello,everyone"}); 

給除了自己以外的客戶端廣播消息

io.sockets.socket(socketid).emit('String', data);

給指定的客戶端發(fā)送消息

socket.on('String',function(data));

監(jiān)聽客戶端發(fā)送的信息

socket.emit('String', data);

給該socket的客戶端發(fā)送消息

io.of('/some').on('connection', function (socket) {
    socket.on('test', function (data) {
        socket.broadcast.emit('event_name',{});
    });
});

分組

進(jìn)階——處理用戶發(fā)送的數(shù)據(jù)

image

一、redis

什么是Redis邮偎?

REmote DIctionary Server(Redis) 是一個(gè)由SalvatoreSanfilippo寫的key-value(鍵值對(duì))存儲(chǔ)系統(tǒng)。

Redis是一個(gè)開源的使用ANSI C語言編寫义黎、遵守BSD協(xié)議禾进、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型廉涕、Key-Value數(shù)據(jù)庫泻云,并提供多種語言的API艇拍。

它通常被稱為數(shù)據(jù)結(jié)構(gòu)服務(wù)器,因?yàn)橹担╲alue)可以是字符串(String), 哈希(Map), 列表(list), 集合(sets) 和有序集合(sorted sets)等類型宠纯。

<html>
<blockquote>
Redis中的數(shù)據(jù)類型

哈希(Map hashmap):散列表(Hash table卸夕,也叫哈希表),是根據(jù)鍵(Key)而直接訪問在內(nèi)存存儲(chǔ)位置的數(shù)據(jù)結(jié)構(gòu)婆瓜。

列表(list):列表是一種數(shù)據(jù)項(xiàng)構(gòu)成的有限序列,即按照一定的線性順序,排列而成的數(shù)據(jù)項(xiàng)的集合快集。(redis中使用雙向鏈表實(shí)現(xiàn))

集合(sets):和中學(xué)時(shí)學(xué)習(xí)的概念是相似的。特點(diǎn)是集合中元素不能重復(fù)是唯一的廉白。切內(nèi)部是無序的

有序集合(sorted sets):也是一種集合个初,但是內(nèi)部數(shù)據(jù)是經(jīng)過排序的。
</blockquote>
</html>

redis安裝

Redis 安裝鏈接

npm redis

redis使用方法

0猴蹂、建立node-redis的client端連接
npm i redis --save

// redis 鏈接
var redis = require('redis');
var client = redis.createClient('6379', '127.0.0.1');

// redis 鏈接錯(cuò)誤
client.on("error", function(error) {
    console.log(error);
});
// redis 驗(yàn)證 (reids.conf未開啟驗(yàn)證院溺,此項(xiàng)可不需要)
// client.auth("foobared");
module.exports = {
    client:client
}

1、set的存取

const {client} = require('./redis')

client.set('key001', 'AAA', function (err, response) {
    if (err) {
        console.log("err:", err);
    } else {
        console.log(response);
        client.get('key001', function (err, res) {
            if (err) {
                console.log("err:", err);
            } else {
                console.log(res);
                client.end(true);
            }
        });
    }
});

2磅轻、hash存取

hash set的設(shè)值和抽取數(shù)據(jù)都有單個(gè)key和多個(gè)key兩種方式:

const {client} = require('./redis')

client.hset('filed002', 'key001', 'wherethersisadoor', function (err, res) {
    if (err) {
        console.log(err);
    } else {
        console.log('res:', res);
        client.hget('filed002', 'key001', function (err, getRslt) {
            if (err) {
                console.log(err);
            } else {
                console.log('getRslt:', getRslt);
                client.end(true);
            }
        });
    }
});

注意:當(dāng)hget方法在指定field下找不到指定的key時(shí)珍逸,會(huì)傳給回調(diào)函數(shù)null,而非空字符或undefined聋溜。

※ 設(shè)定多個(gè)key的值谆膳,取值時(shí)獲取指定field下指定單個(gè)或多個(gè)key的值

const {client} = require('./redis')

var qe = {a: 2, b:3, c:4};
client.hmset('field003', qe, function(err, response) {
    console.log("err:", err);
    console.log("response:", response);
    client.hmget('field003', ['a', 'c'], function (err, res) {
        console.log(err);
        console.log(res);
        client.end(true);
    });
});

hmset方法的設(shè)定值可以是JSON格式的數(shù)據(jù),但是redis中key的值是以字符串形式存儲(chǔ)的勤婚,如果JSON數(shù)據(jù)層數(shù)超過一層摹量,會(huì)出現(xiàn)值是'[object Object]'的情況。

hmget方法的返回值是個(gè)數(shù)組馒胆,其中元素的順序?qū)?yīng)于參數(shù)的key數(shù)組中的順序缨称,如果參數(shù)數(shù)組中有在field內(nèi)不存在的key,返回結(jié)果數(shù)組的對(duì)應(yīng)位置會(huì)是null祝迂,也即無論是否能取到值睦尽,結(jié)果數(shù)組中的元素位置始終與參數(shù)的key數(shù)組中元素位置一一對(duì)應(yīng)。

獲取hash中所有key的方法是client.keys(fieldname, callback); 需要注意的是如果hash中key的數(shù)目很多型雳,這個(gè)方法的可能耗費(fèi)很長時(shí)間当凡。

3.鏈表
適合存儲(chǔ)社交網(wǎng)站的新鮮事
lpush key value [value ...] 向鏈表key左邊添加元素
rpush key value [value...] 向鏈表key右邊添加元素
lpop key 移除key鏈表左邊第一個(gè)元素
rpop key 移除key鏈表右邊第一元素

const {client} = require('./redis')

client.lpush('test', 12345, function(err, response) {
    if(err){
        console.log("err:", err);
    }else{
        console.log("response:", response);
        client.rpop('test',function (err, res){
            if(err){
                console.log(err);
            }else{
                console.log(res);
                client.end(true);
            }
        });
    }
});

[圖片上傳失敗...(image-354cf6-1519888571721)]

socket.io中接入redis 并創(chuàng)建多個(gè)命名空間

How to use

const io = require('socket.io')(3000);
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));

將index.js修改為

const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const redis = require('socket.io-redis');
const {client} = require('./test/redis')
const moment = require('moment')


app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
});

io.adapter(redis({host: 'localhost', port: 6379}));

var nameBox = ['/chatroom','/live','/vod','/wechat','/broadcast'];

for(var item in nameBox){
    var nsp = io.of(nameBox[item])
    socketMain(nsp,nameBox[item])
}

function socketMain(nsp,roomName) {
    nsp.on('connection',function (socket) {
        console.log('a user connected')
        socket.on('disconnect', function(){
            console.log('user disconnected');
        });
        socket.on('chat message', function(msg){
            var data = {"socketid":socket.id,"cid":roomName,"msg":msg,createTime:moment().unix()};
            client.lpush('message',JSON.stringify(data),redis.print)
            console.log('message: ' + msg);
        });
    })
}

http.listen(4000, function(){
    console.log('listening on *:4000');
});

index.html

 var socket = io.connect("http://127.0.0.1:4000/live");

接入redis
client.lpush('message',JSON.stringify(msg),redis.print)

二、另起一個(gè)服務(wù)端拿redis數(shù)據(jù)進(jìn)行處理

修改redis.js

module.exports = {
    client:client,
    ip:'http://127.0.0.1:4000'
}

新建sclient.js

const io = require('socket.io-client');
const async = require('async');
const moment = require('moment');
const redis = require('redis');

const {client,ip} = require('./test/redis');
const domain = require('domain');
const debug = require('debug')('socket-client:main');

var origin = io.connect(ip+'/', {reconnect: true});
var chatroom = io.connect(ip+'/chatroom', {reconnect: true});
var live = io.connect(ip+'/live', {reconnect: true});
var vod = io.connect(ip+'/vod', {reconnect: true});
var wechat = io.connect(ip+'/wechat', {reconnect: true});
var broadcast = io.connect(ip+'/broadcast', {reconnect: true});

var namBox = {root:origin,chatroom:chatroom,live:live,vod:vod,wechat:wechat,broadcast:broadcast};

var reqDomain = domain.create();
reqDomain.on('error', function (err) {
    console.log(err);
    try {
        var killTimer = setTimeout(function () {
            process.exit(1);
        }, 100);
        killTimer.unref();
    } catch (e) {
        console.log('error when exit', e.stack);
    }
});

reqDomain.run(function () {
    compute();
});

process.on('uncaughtException', function (err) {
    console.log(err);
    try {
        var killTimer = setTimeout(function () {
            process.exit(1);
        }, 100);
        killTimer.unref();
    } catch (e) {
        console.log('error when exit', e.stack);
    }
});

function compute() {
    client.llen('message', function(error, count){
        if(error){
            console.log(error);
        }else{
            if(count){
                //console.log('-------------has count',time);
                popLogs();
                process.nextTick(compute);
            }else{
                //console.log('-------------empty',time);
                setTimeout(function(){
                    compute();
                },100);
            }
        }
    });
}

function popLogs(){
    var time = moment().unix();
    console.log('-------------dealStart-------------',time);
    client.rpop('message',function(err,result){
        if(err){
            console.log(err);
        }else{
            var result = JSON.parse(result);
            try{
                var cid = result.cid;
                //console.log('place',result.place);
            }catch(e){
                console.log('empty data cid',result);
                return;
            }
            console.log(' start '+' nsp: '+cid +' time: '+time);
            if(namBox[cid]){
                console.log(result);
                namBox[cid].emit('redisCome',result);
            }
        }
    });
}

修改index.js 增加redisCome監(jiān)聽事件

/*接收redis發(fā)來的消息*/
socket.on('redisCome',function (data) {
    console.log('-------------redisCome',data.msg);
    try{
        var msg = data.msg
    }catch(e){
        var msg = '';
    }
    console.log(data);
    nsp.emit('message.add',msg);
});

修改index.html

socket.on('message.add',function (msg) {
    $('#messages').append($('<li>').text(msg));
})

三纠俭、增加用戶發(fā)送信息校驗(yàn)

增加信息的安全性沿量,我們可以對(duì)用戶發(fā)送的信息進(jìn)行敏感詞、sql注入攻擊冤荆、xss攻擊等進(jìn)行過濾
使用async一步步操作流程

修改sclient.js

async.waterfall([
    function (done) {
        user.messageDirty({msg:result.msg},function(err,res){
            //console.log('sql done'/*,res*/);
            done(err,res);
        });
    },
    function (res,done) {
        user.messageValidate({msg:result.msg},function(err,res){
            //console.log('key done'/*,res*/);
            done(err,res);
        });
    }
],function (err,res) {
    if(err){
        console.log('err!!!!',err,result);
        namBox[cid].emit('messageError',err);
    }else{
        if(namBox[cid]) {
            console.log(result);
            namBox[cid].emit('redisCome', result);
        }
    }
})

修改index.js

/*接收redis錯(cuò)誤信息返回*/
socket.on('messageError',function(err){
    console.log('messageError');
    try{
        nsp.emit('message.error',err.msg);
    }catch(e){

    }
});

修改index.html

mysql入庫

1.在本地安裝mysql數(shù)據(jù)庫
2.下載node mysql包

npm install mysql --save

3.連接數(shù)據(jù)庫 建立連接池

var mysql      = require('mysql');
var pool = mysql.createPool({
    host: 'localhost',
    user:'root',
    password:'123456',
    database : 'danmaku'
});

var query = function(sql,options,callback){
    pool.getConnection(function(err,conn){
        if(err){
            callback(err,null,null);
        }else{
            conn.query(sql,options,function(err,results,fields){
                //釋放連接
                conn.release();
                //事件驅(qū)動(dòng)回調(diào)
                callback(err,results,fields);
            });
        }
    });
};

新建query.js

var {query} = require("./test/redis");

query("select * from demo", function(err,results,fields){
    //do something
    if(err){
        console.log(err)
    }else {
        console.log(results)
    }
});

新建insert.js

var {query} = require("./test/redis");
const moment = require('moment')

query('insert into demo(message,createTime) values(?,?)',[123,moment().unix()],function(err,results,fields){
    //do something
    if(err){
        console.log(err)
    }else {
        console.log(results)
    }
});

mysql -u root -p
use danmaku;
select * from demo;

4.在程序中添加入庫步驟

彈幕播放器

ABPlayerHTML5

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朴则,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钓简,更是在濱河造成了極大的恐慌乌妒,老刑警劉巖汹想,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撤蚊,居然都是意外死亡古掏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門侦啸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來槽唾,“玉大人,你說我怎么就攤上這事匹中∠氖” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵顶捷,是天一觀的道長挂绰。 經(jīng)常有香客問我,道長服赎,這世上最難降的妖魔是什么葵蒂? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮重虑,結(jié)果婚禮上践付,老公的妹妹穿的比我還像新娘。我一直安慰自己缺厉,他們只是感情好永高,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著提针,像睡著了一般命爬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辐脖,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天饲宛,我揣著相機(jī)與錄音,去河邊找鬼嗜价。 笑死艇抠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的久锥。 我是一名探鬼主播家淤,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瑟由!你這毒婦竟也來了媒鼓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤错妖,失蹤者是張志新(化名)和其女友劉穎绿鸣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暂氯,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潮模,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痴施。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擎厢。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辣吃,靈堂內(nèi)的尸體忽然破棺而出动遭,到底是詐尸還是另有隱情,我是刑警寧澤神得,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布厘惦,位于F島的核電站,受9級(jí)特大地震影響哩簿,放射性物質(zhì)發(fā)生泄漏宵蕉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一节榜、第九天 我趴在偏房一處隱蔽的房頂上張望羡玛。 院中可真熱鬧,春花似錦宗苍、人聲如沸稼稿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽让歼。三九已至,卻和暖如春挪钓,著一層夾襖步出監(jiān)牢的瞬間是越,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工碌上, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倚评,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓馏予,卻偏偏與公主長得像天梧,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霞丧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理呢岗,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • 我想后豫,大概這一次我會(huì)哭悉尾,即使最開始的我無論如何也想不到會(huì)陷入這般地步 兩個(gè)人了,我真的害怕挫酿,可是不免憧憬构眯,所以又是...
    不想逃避閱讀 199評(píng)論 0 0
  • 圖片發(fā)自簡書App 一切皆投資 投資的本質(zhì)是是什么?就是用你現(xiàn)在有資產(chǎn)換取你未來資產(chǎn)早龟。 成功投資的本質(zhì)是什么惫霸?就是...
    草稿記錄閱讀 200評(píng)論 0 1
  • 08六項(xiàng)日精進(jìn)打卡姓名:劉海北京多禾餐飲管理有限公司組別 249期謙虛1組【日精進(jìn)打卡第0092天】【知~學(xué)習(xí)】誦...
    七天樂餐閱讀 187評(píng)論 0 0