socketio

写了一个socket.io服务,实现了用户区分、公聊、私聊等。。。码云地址https://gitee.com/liuoomei/so... app.js //app.jsvar express = require('express')var app = express();var server = require('http').Server(app);// var io = require('socket.io')(server);var path = require('path');app.use(express.static(path.join(__dirname, 'public')))app.get('/', function(req, res){ res.sendFile(path.join(__dirname, 'index.html'));});module.exports = app;bin/www #!/usr/bin/env node/** * Module dependencies. */let app = require('../app');let debug = require('debug')('mysocket:server');let http = require('http');let _ = require('underscore');/** * Get port from environment and store in Express. */let userLs = []let port = normalizePort(process.env.PORT || '3000');app.set('port', port);/** * Create HTTP server. */let server = http.createServer(app);/** * Listen on provided port, on all network interfaces. */server.listen(port);let io = require('socket.io')(server);io.on('connection', function(socket){ socket.on('login', function(userid){ let obj = { userid, id: socket.id } userLs.push(obj) console.log(userid + '建立了链接'); socket.emit('userLs',userLs) socket.emit('login',socket.id) }); // 判断用户离线事件可以通过socket.io自带的disconnect事件完成,当一个用户断开连接,disconnect事件就会触发 socket.on('disconnect', function(){ let _user = _.where(userLs,{id:socket.id}) console.log('_user',_user) console.log(socket.id + '中断了链接'); userLs = userLs.filter(it =>{ return _user.every(item =>{ return it.id != item.id }) }) // do somethings console.log('del',userLs) }); socket.on('message', function (data) { //服务端像所以也没发送数据 let _user = _.where(userLs,{id:socket.id}) io.sockets.emit('message', {id:_user[0].id,userid:_user[0].userid,message:data.message}); //给所有人(包括自己)发送消息 // socket.broadcast.emit('message', data.message); //给所有人(不包括自己)发送消息 }); socket.on('sayTo', function (data) { let toMsg = data.message; let toId = data.id; // nodejs的underscore扩展中的findWhere方法,可以在对象集合中,通过对象的属性值找到该对象并返回。 let _user = _.where(userLs,{id:toId}) if(_user){ let toSocket = _.findWhere(io.sockets.sockets, {id: toId}); // 通过该连接对象(toSocket)与链接到这个对象的客户端进行单独通信 socket.emit('message', {id:socket.id,message:toMsg}) //向建立该连接的客户端广播 toSocket.emit('message', {id:socket.id,message:toMsg}); }else{ socket.emit('error','该用户不在线') } // socket.emit() :向建立该连接的客户端广播 // socket.broadcast.emit() :向除去建立该连接的客户端的所有客户端广播 // io.sockets.emit() : 向所有客户端广播,等同于上面两个的和 });});server.on('error', onError);server.on('listening', onListening);/** * Normalize a port into a number, string, or false. */function normalizePort(val) { let port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false;}/** * Event listener for HTTP server "error" event. */function onError(error) { if (error.syscall !== 'listen') { throw error; } let bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; }}/** * Event listener for HTTP server "listening" event. */function onListening() { let addr = server.address(); let bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); console.log(`服务已启动,端口:${addr.port}`)}public/index.html ...

June 17, 2019 · 3 min · jiezi

批量导出某个简书用户的所有文章列表和文章超链接

简书改版后,根据文章标题搜索文章的功能就不见了。 虽然简书提供了批量下载文章的功能,但是下载到本地的文章都是markdown格式的,不包含文章的链接,这不满足我的需求。 既然我是程序员,没有这个功能我就自己实现一个。 打开简书首页,发现默认只显示8篇文章,用鼠标滑动到屏幕底部后,会触发一个懒加载事件,到后台读取更多的文章列表,所以文章读取在服务器端是采取的分页实现。 打开Chrome开发者工具,观察网络请求,请求url中99b8712e8850是我简书用户id,page=2,3,4这些是分页代码。 每页的文章内容以html格式包含在响应结构里: 我关心的只是文章标题和文章链接,如上图高亮字段所示。 最开始我写了一个nodejs应用,代码如下: var request = require('request');var jsdom = require("jsdom");var JSDOM = jsdom.JSDOM;const PREFIX = "https://www.jianshu.com";const PAGE = "https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";const MAX = 2;var mArticleResult = new Map();var pageNumber;/* a given article: https://www.jianshu.com/p/963cd23fb092 value got from API: /p/5c1d0319dc42*/var lastPageReached = false;var url = "";var aHandlers = [];// use limited for loop to ease testingfor(var i = 0; i < MAX; i++){ pageNumber = i + 1; var url = PAGE + pageNumber; // console.log("current page: " + url); var pageOptions = { url: url, method: "GET", headers: { "Accept": "text/html" } }; aHandlers.push(getArticles(pageOptions, pageNumber)); if( lastPageReached) break;}console.log("promise handler size: " + aHandlers.length);Promise.all(aHandlers).then(function(){ var articleIndex = 0; for (var [key, value] of mArticleResult) { console.log("Article[" + articleIndex++ + "]: " + key + " = " + value); } console.log("done");} );function getArticles(pageOptions, pageNumber) { return new Promise(function(resolve,reject){ var requestC = request.defaults({jar: true}); requestC(pageOptions,function(error,response,body){ if( error){ console.log("error: " + error); resolve(error); } var document = new JSDOM(body).window.document; var content = document.getElementsByTagName("li"); for( var i =0; i < content.length; i++){ var li = content[i]; var children = li.childNodes; for( var j = 0; j < children.length; j++){ var eachChild = children[j]; if( eachChild.nodeName == "DIV"){ var grandChild = eachChild.childNodes; for( var k = 0; k < grandChild.length; k++){ var grand = grandChild[k]; if( grand.nodeName == "A"){ var fragment = grand.getAttribute("href"); if( fragment.indexOf("/p") < 0) continue; console.log("title: " + grand.text); var wholeURL = PREFIX + fragment; console.log("url: " + wholeURL); if( mArticleResult.has(grand.text)){ lastPageReached = true; console.log("article size: " + mArticleResult.size); resolve(pageNumber); } mArticleResult.set(grand.text, wholeURL); } } } } }// end of outer loop resolve(pageNumber); }); });}原理就是使用nodejs的request module,向简书网站同时发起多个请求,每个请求读取一页的简书文章。 ...

May 9, 2019 · 3 min · jiezi

NodeJS踩坑实录

nodejs的常用apiurl 主要是配置一系列和路径相关的信息url.parse(urlString[, parseQueryString[, slashesDenoteHost]]) 将一个URL字符串解析为URL对象urlString: 解析的路径字符串parseQueryString: 返回是布尔类型,主要用来解析query的slashesDenoteHost: 返回是布尔类型,当你不确定你的请求协议时,辅助帮助你进行解析url.format(urlObj,parseObj,slashesObj) 将url对象转换为字符串与parse参数相反url.resolve(from, to) 将基础路径和后缀路径转换成目标路径from 解析时相对的基本URLto 要解析的超链接 URL值得注意的是基本路径要在路径最后添加’/’,否则合并会找到你最近的’/‘并替换const url = require(‘url’);url.resolve(’/one/two/three’, ‘four’); // ‘/one/two/four’url.resolve(‘http://example.com/', ‘/one’); // ‘http://example.com/one'url.resolve('http://example.com/one', ‘/two’); // ‘http://example.com/two'queryString 为查询字符串提供扩展querystring 模块提供了一些实用函数,用于解析与格式化 URL 查询字符串querystring.parse(str,con,seq)str 要解析的 URL 查询字符串con用于界定查询字符串中的键值对的子字符串。默认为 ‘&‘seq 用于界定查询字符串中的键与值的子字符串。默认为 ‘=‘querystring.stringify(obj,con,seq)obj 要序列化成 URL 查询字符串的对象con 用于界定查询字符串中的键值对的子字符串。默认为 ‘&‘seq 用于界定查询字符串中的键与值的子字符串。默认为 ‘=‘querystring.escape(str) 相当于encodeURI 将Asc编码转换成utf-8对给定的str进行 URL编码该方法是提供给 querystring.stringify()使用的,通常不直接使用querystring.unescape(str) 相当于decodeURI 将utf-8转换成ASc对给定的str进行解码该方法是提供给 querystring.parse()使用的,通常不直接使用events - 事件触发器大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用例子,一个简单的 EventEmitter 实例,绑定了一个监听器。 eventEmitter.on() 方法用于注册监听器,eventEmitter.emit() 方法用于触发事件。const Eventemitter = require(“events”)class Player extends Eventemitter {}const player = new Player()//使用 eventEmitter.on() 注册监听器时,监听器会在每次触发命名事件时被调用player.on(“change”,(track) => { console.log(node事件机制,${track})})//使用 eventEmitter.once() 可以注册最多可调用一次的监听器。 当事件被触发时,监听器会被注销,然后再调用//player.once(“change”,(track) => {// console.log(node事件机制,${track})//})player.emit(“change”,“react”)player.emit(“change”,“vue”)fs - 文件系统fs 模块提供了一些接口用于以一种类似标准 POSIX 函数的方式与文件系统进行交互所有的文件系统操作都有同步和异步两种形式异步形式的最后一个参数都是完成时的回调函数。 传给回调函数的参数取决于具体方法,但回调函数的第一个参数都会保留给异常。 如果操作成功完成,则第一个参数会是 null 或 undefinedfs.Stats 类fs.Stats 对象提供了一个文件的信息stats.isDirectory() 如果 fs.Stats 对象表示一个文件系统目录,则返回 truestats.isFile() 如果 fs.Stats 对象表示一个普通文件,则返回 truefs.mkdir(path[, options], callback)异步地创建目录。 完成回调只有一个可能的异常参数// 创建 /temp/a/apple 目录,不管 /temp 和 /temp/a 目录是否存在。fs.mkdir(’/temp/a/apple’, (err) => { if (err) throw err;});fs.writeFile(file, data[, options], callback)异步地写入数据到文件,如果文件已经存在,则覆盖文件。 data 可以是字符串或 bufferfs.writeFile(’temp.js’, ‘keep study’, (err) => { if (err) throw err; console.log(‘文件已保存!’);});fs.appendFile(path, data[, options], callback)异步地追加数据到文件,如果文件不存在则创建文件。 data 可以是字符串或 Bufferfs.appendFile(’temp.js’, ‘追加的数据’, (err) => { if (err) throw err; console.log(‘数据已追加到文件’);});fs.readFile(path[, options], callback)异步地读取一个文件的全部内容fs.readFile(’/etc/passwd’, (err, data) => { if (err) throw err; console.log(data);});回调有两个参数 (err, data),其中 data 是文件的内容。如果未指定字符编码,则返回原始的 buffer。如果 options 是一个字符串,则它指定了字符编码。例子:fs.readFile(’/etc/passwd’, ‘utf8’, callback);fs.readdir(path[, options], callback)读取目录的内容。 回调有两个参数 (err, files),其中 files 是目录中文件名的数组,不包含 ‘.’ 和 ‘..’。options 参数用于传入回调的文件名。 它可以是一个字符串,指定字符编码。 也可以是一个对象,其中 encoding 属性指定字符编码。 如果 encoding 设为 ‘buffer’,则返回的文件名会是 Buffer 对象。fs.rmdir(path, callback)删除目录fs.readFileSync(path[, options])同步读取文件fs.readdirSync(path[, options])同步读取目录fs.unlink(path, callback)解除关系(也即删除文件)readFileSync和unlink结合实现删除一个目录及其目录下的文件的例子:const fs = require(‘fs’);fs.readdirSync(“logs”).map((file) => { fs.unlink(logs/${file},() => { console.log(“删除成功”) })})fs.rmdir(“logs”, (err)=> { console.log(“确定要删除吗?”)})node框架之expressnode框架之koa2文档持续更新中~~~ ...

November 13, 2018 · 1 min · jiezi