├── .gitignore ├── README.md ├── package.json ├── src ├── cors-demo │ ├── demo1 │ │ ├── server │ │ │ └── app.js │ │ └── static │ │ │ └── html │ │ │ └── index.html │ ├── demo2 │ │ ├── server │ │ │ └── app.js │ │ └── static │ │ │ └── html │ │ │ └── index.html │ ├── demo3 │ │ ├── server │ │ │ └── app.js │ │ └── static │ │ │ └── html │ │ │ └── index.html │ └── demo4 │ │ ├── server │ │ └── app.js │ │ └── static │ │ └── html │ │ └── index.html ├── down-video │ ├── app.js │ ├── down.js │ └── getUrl.js ├── faker │ ├── app.js │ ├── data.js │ └── readme.md ├── generator-address │ └── index.js ├── iq-list │ ├── 001.png │ ├── 1con.md │ ├── 1pic.md │ ├── list.md │ └── node.js ├── node-demo │ ├── base64.txt │ ├── buffer.js │ └── demo1 │ │ ├── server │ │ └── app.js │ │ └── static │ │ ├── html │ │ ├── 1.html │ │ └── index.html │ │ └── img │ │ └── g.jpg ├── ssr-mock-data │ ├── child.js │ ├── db │ │ ├── 59e17a7ff265da430629cc4e.html │ │ ├── 59f954546fb9a04525776d7c.html │ │ ├── 5a09997cf265da432002bf6a.html │ │ ├── 5a0ab8e2f265da43111fbab2.html │ │ ├── 5a1212bc51882531ea64df07.html │ │ ├── 5a124b29f265da431d3c472e.html │ │ ├── 5a157c155188254a701eb3c1.html │ │ ├── 5a261d8f5188253ee45b4b21.html │ │ ├── 5a36661851882538e2259c0f.html │ │ ├── 5a6abad5518825733c144469.html │ │ ├── 5a7bfe595188257a7349b52a.html │ │ ├── 5bffcbc9f265da614b11b731.html │ │ ├── 5c70dbbe51882562046911bc.html │ │ ├── 5c70fc83518825428d7f9dfb.html │ │ ├── 5d9ea8fff265da5b81794756.html │ │ ├── 5da08714518825520e6bb810.html │ │ ├── 5da2f9d4f265da5b81794d48.html │ │ ├── 5da3bc3d6fb9a04e35597a76.html │ │ ├── 5da96626e51d4524ba0fd237.html │ │ ├── 5db6b0fa6fb9a020446c5278.html │ │ └── db.json │ ├── demo.js │ ├── package.json │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ ├── aa.js │ │ ├── upload_5cb411292d060f7c1beb5013d213aebf.xls │ │ ├── upload_72e5851d22c2658deff9dbdbc31fba6a.xls │ │ ├── upload_7a57454a6d3dc77b6fefd137112816ea.xls │ │ ├── upload_7e43363a259814b9c078ca664166a0fb.xls │ │ ├── upload_b2afbf318a199a9d56ea2e64658e8929.xls │ │ └── upload_fb5aa88dbb158d66d35a820ec113d538.xls └── upfiles-demo │ ├── README.md │ ├── api-upfile │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ ├── t.html │ │ └── upfile.html │ │ └── uploads │ │ ├── aa.js │ │ └── upload_e918b11c7fd084888c3ac089680589a8.jpg │ ├── demo1 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo10 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo11 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo12 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo13 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo14 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo15 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ ├── aa.js │ │ ├── upload_5cb411292d060f7c1beb5013d213aebf.xls │ │ ├── upload_72e5851d22c2658deff9dbdbc31fba6a.xls │ │ ├── upload_7a57454a6d3dc77b6fefd137112816ea.xls │ │ ├── upload_7e43363a259814b9c078ca664166a0fb.xls │ │ ├── upload_b2afbf318a199a9d56ea2e64658e8929.xls │ │ └── upload_fb5aa88dbb158d66d35a820ec113d538.xls │ ├── demo2 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ ├── aa.js │ │ └── upload_a9825b5922faed699503ee95b680b43d.png │ ├── demo3 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo4 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo5 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo6 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo7 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ ├── demo8 │ ├── server │ │ └── app.js │ └── static │ │ ├── html │ │ └── upfile.html │ │ └── uploads │ │ └── aa.js │ └── demo9 │ ├── server │ └── app.js │ └── static │ ├── html │ └── upfile.html │ └── uploads │ └── aa.js ├── videos ├── 1613922402615.mp4 ├── 1613922607631.mp4 ├── 1613922619676.mp4 ├── 1613922733493.mp4 ├── 1613922819298.mp4 ├── 1613922872381.mp4 ├── 1613923074995.mp4 └── 1613923332893.mp4 └── yarn-error.log /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .DS_Store 4 | npm-debug.log 5 | npm-debug.log.* 6 | .idea 7 | /dist 8 | build 9 | package-lock.json 10 | build/* 11 | server_compiled 12 | server_compiled/* 13 | .history/ 14 | data 15 | conf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 准备工作 2 | ``` 3 | $ npm install 4 | ``` 5 | ### 启动服务 6 | 7 | 找到对应的实例代码,然后启动 所在目录的 server/app.js 8 | 9 | 如 10 | 11 | ``` 12 | $ node ./src/cors-demo/demo1/server/app.js 13 | ``` 14 | 15 | ### PS 16 | 17 | 有些 ajax 的请求走的 ip,代码里是我的 ip,发送请求时候需要改为你自己的 ip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fe-learn-code", 3 | "version": "1.0.0", 4 | "description": "前端学习路上的代码实例", 5 | "main": "ls", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "223344386", 10 | "license": "ISC", 11 | "dependencies": { 12 | "fluent-ffmpeg": "^2.1.2", 13 | "koa": "^2.8.2", 14 | "koa-better-body": "^3.1.2", 15 | "koa-body": "^4.1.1", 16 | "koa-bodyparser": "^4.2.1", 17 | "koa-convert": "^1.2.0", 18 | "koa-json": "^2.0.2", 19 | "koa-static": "^5.0.0", 20 | "koa2": "^2.0.0-alpha.7", 21 | "request": "^2.88.2", 22 | "spark-md5": "^3.0.0" 23 | }, 24 | "devDependencies": { 25 | "chinese-address-generator": "^1.1.0", 26 | "faker": "^5.5.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/cors-demo/demo1/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost= `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody()); 17 | 18 | app.use(koaStatic( 19 | path.resolve(__dirname, '../static') 20 | )); 21 | 22 | 23 | 24 | //跨域处理 25 | app.use((ctx) => { 26 | 27 | 28 | //指定一个接口和返回数据 29 | var path =ctx.path; 30 | if(path==='/getdata'){ 31 | 32 | console.log('receive req'); 33 | 34 | //服务端通过 ctx.headers.origin 获取请求中的origin 35 | //ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 36 | 37 | ctx.body=JSON.stringify({ 38 | code:0, 39 | msg:'success', 40 | data:[] 41 | }); 42 | }else{ 43 | ctx.body='welcome'; 44 | } 45 | 46 | }) 47 | 48 | 49 | /** 50 | * Create HTTP server. 51 | */ 52 | var server = http.createServer(app.callback()); 53 | server.listen(port); 54 | console.log('cors demo1 server start ...... '); -------------------------------------------------------------------------------- /src/cors-demo/demo1/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 | 11 |

跨域问题复现和解决

12 |

使用 get 或者 post 发送数据, content-type:application/x-www-form-urlencoded

13 | 14 |

要发送的数据 a=1&b=2

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/cors-demo/demo2/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost= `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody()); 17 | 18 | app.use(koaStatic( 19 | path.resolve(__dirname, '../static') 20 | )); 21 | 22 | app.use((ctx,next)=>{ 23 | 24 | console.log('set cookie'); 25 | ctx.cookies.set('bigerfe-cookie', 'zz-jesse3', { 26 | maxAge: 1000000 27 | }); 28 | return next(); 29 | }) 30 | 31 | 32 | //跨域处理 33 | app.use((ctx) => { 34 | 35 | 36 | //指定一个接口和返回数据 37 | var path =ctx.path; 38 | if(path==='/getdata'){ 39 | 40 | console.log('receive req'); 41 | 42 | //服务端通过 ctx.headers.origin 获取请求中的origin 43 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 44 | ctx.set('Access-Control-Allow-Credentials',true); 45 | 46 | ctx.body=JSON.stringify({ 47 | code:0, 48 | msg:'success', 49 | data:[] 50 | }); 51 | }else{ 52 | ctx.body='welcome'; 53 | } 54 | 55 | }) 56 | 57 | 58 | /** 59 | * Create HTTP server. 60 | */ 61 | var server = http.createServer(app.callback()); 62 | server.listen(port); 63 | console.log('cors demo2 server start ...... '); -------------------------------------------------------------------------------- /src/cors-demo/demo2/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 | 11 |

跨域问题复现和解决

12 |

使用 get 或者 post 发送数据 并携带 cookie, content-type:application/x-www-form-urlencoded

13 | 14 |

要发送的数据 a=1&b=2

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/cors-demo/demo3/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost= `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody()); 17 | 18 | app.use(koaStatic( 19 | path.resolve(__dirname, '../static') 20 | )); 21 | 22 | 23 | //跨域处理 24 | app.use((ctx) => { 25 | 26 | 27 | //指定一个接口和返回数据 28 | var path =ctx.path; 29 | if(path==='/getdata'){ 30 | 31 | console.log('receive req'); 32 | 33 | //服务端通过 ctx.headers.origin 获取请求中的origin 34 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 35 | 36 | ctx.set('Access-Control-Allow-Headers', 'content-type'); 37 | 38 | ctx.body=JSON.stringify({ 39 | code:0, 40 | msg:'success', 41 | data:[] 42 | }); 43 | }else{ 44 | ctx.body='welcome'; 45 | } 46 | 47 | }) 48 | 49 | 50 | /** 51 | * Create HTTP server. 52 | */ 53 | var server = http.createServer(app.callback()); 54 | server.listen(port); 55 | console.log('cors demo3 server start ...... '); -------------------------------------------------------------------------------- /src/cors-demo/demo3/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 | 11 |

跨域问题复现和解决

12 |

使用 get 或者 post 发送json格式数据, content-type:application/json

13 | 14 |

要发送的数据 a=1&b=2

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/cors-demo/demo4/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost= `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody()); 17 | 18 | app.use(koaStatic( 19 | path.resolve(__dirname, '../static') 20 | )); 21 | 22 | 23 | //跨域处理 24 | app.use((ctx) => { 25 | //指定一个接口和返回数据 26 | var path =ctx.path; 27 | if(path==='/getdata'){ 28 | 29 | console.log('receive req'); 30 | 31 | if (canRequest(ctx.headers.origin)){ 32 | //服务端通过 ctx.headers.origin 获取请求中的origin 33 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 34 | } 35 | 36 | ctx.set('Access-Control-Allow-Methods', 'PUT'); 37 | 38 | ctx.set('Access-Control-Max-Age', 3600); 39 | 40 | ctx.body=JSON.stringify({ 41 | code:0, 42 | msg:'success', 43 | data:[] 44 | }); 45 | }else{ 46 | ctx.body='welcome'; 47 | } 48 | }); 49 | 50 | 51 | /** 52 | * Create HTTP server. 53 | */ 54 | var server = http.createServer(app.callback()); 55 | server.listen(port); 56 | console.log('cors demo4 server start ...... '); -------------------------------------------------------------------------------- /src/cors-demo/demo4/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 | 11 |

跨域问题复现和解决

12 |

使用PUT方法发送数据

13 | 14 |

要发送的数据 a=1&b=2

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/down-video/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | var request = require('request') 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost = `http://localhost:${port}/uploads/`; 15 | 16 | 17 | const destUrl = 'https://v.douyin.com/JoKdWDL/' 18 | 19 | function getRedirectUrl(destUrl) { 20 | 21 | request(destUrl, function (err, response, body) { 22 | 23 | /* 24 | response 响应信息的集合 25 | */ 26 | 27 | console.warn('dfdfdf') 28 | 29 | if (!err && response.statusCode == 200) { 30 | console.log(body) 31 | } 32 | }) 33 | } 34 | 35 | app.use(koaBody({ 36 | formidable: { 37 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 os 38 | uploadDir: path.resolve(__dirname, '../static/uploads') 39 | }, 40 | multipart: true // 支持文件上传 41 | })); 42 | 43 | //跨域处理 44 | app.use((ctx) => { 45 | console.log('path', ctx.path) 46 | 47 | //指定一个接口和返回数据 48 | var path = ctx.path; 49 | if (path === '/getdata') { 50 | getRedirectUrl(destUrl) 51 | console.log('receive req'); 52 | 53 | 54 | //服务端通过 ctx.headers.origin 获取请求中的origin 55 | //ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 56 | 57 | ctx.body = JSON.stringify({ 58 | code: 0, 59 | msg: 'success', 60 | data: [] 61 | }); 62 | } 63 | }) 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('down video server start ...... '); -------------------------------------------------------------------------------- /src/down-video/down.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const request = require('request') 4 | /** 5 | * 下载视频 6 | */ 7 | function downVideo(url) { 8 | 9 | var fileName = `${+new Date()}.mp4` 10 | var fullPath = path.resolve('./videos/' + fileName); 11 | 12 | console.log('开始下载视频:', fileName); 13 | 14 | //这个地方要详细说了 15 | request(encodeURI(url)).on('error', function (err) { 16 | console.warn(error) 17 | }).pipe(fs.createWriteStream(fullPath)).on('finish', () => { 18 | console.log('视频下载成功'); 19 | }) 20 | 21 | } 22 | 23 | const url = 'http://v26.douyinvod.com/1633e3a3a2d4afa5ba13427d06b1edf9/60332ca2/video/tos/cn/tos-cn-ve-15/04fcf0116d844e34974131acab1c0fe7/?a=1128&br=7060&bt=1765&cd=0%7C0%7C0&ch=0&cr=0&cs=0&cv=1&dr=0&ds=3&er=&l=202102220001380102121801332648776C&lr=&mime_type=video_mp4&pl=0&qs=0&rc=anJkcThvdjM6MzMzN2kzM0ApOWk3Njs0aDw0NzU0NzQ4O2cpaGRqbGRoaGRmcV5jcDJsLzVfYC0tNS0wc3MxM15gYDMvL18wYDZgNDQ0OmNwb2wrbStqdDo%3D&vl=&vr=' 24 | downVideo(url) 25 | 26 | module.exports = { 27 | downVideo, 28 | } -------------------------------------------------------------------------------- /src/down-video/getUrl.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const downFn = require('./down') 3 | 4 | let send = { 5 | 'Error:': '查询失败', 6 | 'code': 400, 7 | } 8 | 9 | 10 | function getApiUrl(url){ 11 | //前端传过来的地址 进行重定向拿到 item_ids 并且返回 12 | return new Promise(resolve => { 13 | request(url, (error, response) => { 14 | if (!error && response.statusCode == 200) { 15 | let href = response.request.href; 16 | let id = ''; 17 | id = href.match(/video\/(\S*)\/\?region/)[1]; 18 | resolve(`https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${id}`); 19 | } else { 20 | resolve(false) 21 | } 22 | }) 23 | }); 24 | } 25 | 26 | 27 | let Videowm = async (url) => { 28 | 29 | url = httpString(url); 30 | console.warn(url) 31 | //前端传过来的地址 进行重定向拿到 item_ids 并且返回 32 | let watermark = await new Promise(resolve => { 33 | request(url, (error, response, body) => { 34 | if (!error && response.statusCode == 200) { 35 | let href = response.request.href; 36 | console.log('href', href) 37 | let id = void 0; 38 | try { 39 | id = href.match(/video\/(\S*)\/\?region/)[1]; 40 | } catch (error) { 41 | res.json(send) 42 | return false; 43 | } 44 | resolve(`https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${id}`); 45 | } else { 46 | res.json(send) 47 | } 48 | }) 49 | }); 50 | 51 | let url1 = await new Promise(resolve => { 52 | //拿到完整地址返回指定数据 53 | request(watermark, async (error, response, body) => { 54 | if (!error && response.statusCode == 200) { 55 | 56 | let result = JSON.parse(body); 57 | let data = result.item_list[0]; 58 | //视频url解析 59 | let video = data['video']["play_addr"]["url_list"][0] 60 | resolve(video) 61 | }else{ 62 | resolve(false) 63 | } 64 | }) 65 | }) 66 | 67 | return videourl(url1).then(lastUrl=>{ 68 | return lastUrl 69 | }) 70 | } 71 | 72 | //解析视频 73 | const videourl = async (url) => { 74 | //截取字符串 wm 75 | url = url.replace(/wm/g, ''); 76 | return new Promise(resolve => { 77 | setTimeout(() => { 78 | request(url, (error, response, body) => { 79 | console.warn('response.request.href',response.request.href) 80 | console.log('121212',response.statusCode) 81 | resolve(response.request.href) 82 | }) 83 | }, 2000); 84 | }) 85 | } 86 | 87 | //解析字符串里面的url 88 | const httpString = (s) => { 89 | let reg = /(https?|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g; 90 | try { 91 | return s.match(reg)[0]; 92 | } catch (error) { 93 | return null; 94 | } 95 | } 96 | 97 | // module.exports = { 98 | // Videowm 99 | // } 100 | 101 | Videowm('https://v.douyin.com/JooFw6N/').then(url=>{ 102 | console.log('urlis',url) 103 | downFn.downVideo(url) 104 | }) 105 | 106 | -------------------------------------------------------------------------------- /src/faker/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | var getUserList = require('./data'); 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | app.use(koaBody()); 15 | 16 | //跨域处理 17 | app.use((ctx) => { 18 | 19 | //ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 20 | 21 | //指定一个接口和返回数据 22 | var path =ctx.path; 23 | if(path === '/userlist'){ 24 | ctx.body=JSON.stringify({ 25 | code:0, 26 | msg:'success', 27 | data:getUserList(), 28 | }); 29 | }else{ 30 | ctx.body='welcome'; 31 | } 32 | }); 33 | 34 | /** 35 | * Create HTTP server. 36 | */ 37 | var server = http.createServer(app.callback()); 38 | server.listen(port); 39 | console.log(`server start at port: ${port} ...... `); -------------------------------------------------------------------------------- /src/faker/data.js: -------------------------------------------------------------------------------- 1 | //github https://github.com/marak/Faker.js/ 2 | var faker = require('faker'); 3 | faker.locale = "zh_CN"; //设置语言 4 | 5 | function getUserItem(){ 6 | return { 7 | id: faker.datatype.number(), 8 | name: faker.name.findName(), 9 | email: faker.internet.email(), 10 | website: faker.internet.url(), 11 | address: faker.address.streetAddress() + faker.address.city() + faker.address.country(), 12 | bio: faker.lorem.sentences(), 13 | image: faker.image.avatar() 14 | } 15 | } 16 | 17 | console.log(getUserItem()); 18 | 19 | // 获取列表 20 | function getUserList(){ 21 | const arr = []; 22 | let i=0; 23 | while(i<20){ 24 | arr.push(getUserItem()) 25 | i+=1; 26 | } 27 | return arr; 28 | } 29 | 30 | module.exports = getUserList; -------------------------------------------------------------------------------- /src/faker/readme.md: -------------------------------------------------------------------------------- 1 | # 假数据模块使用 2 | -------------------------------------------------------------------------------- /src/generator-address/index.js: -------------------------------------------------------------------------------- 1 | // github https://github.com/moonrailgun/chinese-address-generator 2 | const generator = require('chinese-address-generator/generator4'); 3 | let i=1; 4 | while (i<100) { 5 | const address = generator.fabricateFullAddress(); // => {region: "上海", code: "310000"} 6 | console.log(address); 7 | i+=1; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/iq-list/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/iq-list/001.png -------------------------------------------------------------------------------- /src/iq-list/1con.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | 5 | # 1. var ,let ,const 区别 6 | 7 | # 2. 如何判断数据的类型 8 | 9 | # 3. call,apply,bind区别 10 | 11 | # 4. typeof 和 instanceof 区别 12 | 13 | # 5. 介绍下浅拷贝、深拷贝,如何实现 14 | 15 | # 6. Object.assign 的作用 16 | 17 | -------------------------------------------------------------------------------- /src/iq-list/1pic.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/iq-list/list.md: -------------------------------------------------------------------------------- 1 | # 1. var ,let ,const 区别 2 | 3 | # 2. 如何判断数据的类型 4 | 5 | # 3. call,apply,bind区别 6 | 7 | # 4. typeof 和 instanceof 区别 8 | 9 | # 5. 介绍下浅拷贝、深拷贝,如何实现 10 | 11 | # 6. Object.assign 的作用 12 | 13 | --- 第一期 14 | 15 | # 7. ==和===区别 16 | 17 | # 8. 如何理解闭包 18 | 19 | # 9. 介绍下原型和原型链 20 | 21 | # 10. 对箭头函数的理解 22 | 23 | 24 | 如何确定 this 指向 25 | 26 | set 和数组的区别 27 | 28 | map 和对象的区别 29 | 30 | 简述 new 一个对象的过程 31 | 32 | 介绍下事件循环,顺便会问和 node 的区别 33 | 34 | setTimeout和setInterval区别 35 | 36 | 介绍下节流和防抖 37 | 38 | Promise 原理 39 | 40 | Generator 原理 41 | 42 | async await 原理 43 | 44 | 为什么 0.1 + 0.2 != 0.3 45 | -------------------------------------------------------------------------------- /src/iq-list/node.js: -------------------------------------------------------------------------------- 1 | var ffmpeg = require('fluent-ffmpeg'); 2 | 3 | //new ffmpeg(); 4 | 5 | // ffmpeg('/Users/zhangjiapeng/666/mp4') 6 | // .output('aa.mp4') 7 | // .on('end', function () { 8 | // console.log('Finished processing'); 9 | // }) 10 | // .run(); 11 | 12 | ffmpeg('/Users/zhangjiapeng/666/mp4').output('aa.mp4').run(); -------------------------------------------------------------------------------- /src/node-demo/buffer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function g(e, t) { 5 | return t.length ? t.reduce(function (e, t) { 6 | return e[t] 7 | }, e) : e 8 | } 9 | 10 | function decompressFromBase64(t) { 11 | return null == t ? "" : "" == t ? null : _decompress(t.length, 32, function (e) { 12 | return o(n, t.charAt(e)) 13 | }) 14 | } 15 | 16 | function decompress(t) { 17 | return null == t ? "" : "" == t ? null : _decompress(t.length, 32768, function (e) { 18 | return t.charCodeAt(e) 19 | }) 20 | } 21 | 22 | function _decompress(e, t, n) { 23 | var r, i, o, a, s, c, u, l = [], 24 | d = 4, 25 | f = 4, 26 | h = 3, 27 | p = "", 28 | m = [], 29 | v = { 30 | val: n(0), 31 | position: t, 32 | index: 1 33 | }; 34 | for (r = 0; r < 3; r += 1) 35 | l[r] = r; 36 | for (o = 0, 37 | s = Math.pow(2, 2), 38 | c = 1; c != s;) 39 | a = v.val & v.position, 40 | v.position >>= 1, 41 | 0 == v.position && (v.position = t, 42 | v.val = n(v.index++)), 43 | o |= (0 < a ? 1 : 0) * c, 44 | c <<= 1; 45 | switch (o) { 46 | case 0: 47 | for (o = 0, 48 | s = Math.pow(2, 8), 49 | c = 1; c != s;) 50 | a = v.val & v.position, 51 | v.position >>= 1, 52 | 0 == v.position && (v.position = t, 53 | v.val = n(v.index++)), 54 | o |= (0 < a ? 1 : 0) * c, 55 | c <<= 1; 56 | u = g(o); 57 | break; 58 | case 1: 59 | for (o = 0, 60 | s = Math.pow(2, 16), 61 | c = 1; c != s;) 62 | a = v.val & v.position, 63 | v.position >>= 1, 64 | 0 == v.position && (v.position = t, 65 | v.val = n(v.index++)), 66 | o |= (0 < a ? 1 : 0) * c, 67 | c <<= 1; 68 | u = g(o); 69 | break; 70 | case 2: 71 | return "" 72 | } 73 | for (i = l[3] = u, 74 | m.push(u);;) { 75 | if (v.index > e) 76 | return ""; 77 | for (o = 0, 78 | s = Math.pow(2, h), 79 | c = 1; c != s;) 80 | a = v.val & v.position, 81 | v.position >>= 1, 82 | 0 == v.position && (v.position = t, 83 | v.val = n(v.index++)), 84 | o |= (0 < a ? 1 : 0) * c, 85 | c <<= 1; 86 | switch (u = o) { 87 | case 0: 88 | for (o = 0, 89 | s = Math.pow(2, 8), 90 | c = 1; c != s;) 91 | a = v.val & v.position, 92 | v.position >>= 1, 93 | 0 == v.position && (v.position = t, 94 | v.val = n(v.index++)), 95 | o |= (0 < a ? 1 : 0) * c, 96 | c <<= 1; 97 | l[f++] = g(o), 98 | u = f - 1, 99 | d--; 100 | break; 101 | case 1: 102 | for (o = 0, 103 | s = Math.pow(2, 16), 104 | c = 1; c != s;) 105 | a = v.val & v.position, 106 | v.position >>= 1, 107 | 0 == v.position && (v.position = t, 108 | v.val = n(v.index++)), 109 | o |= (0 < a ? 1 : 0) * c, 110 | c <<= 1; 111 | l[f++] = g(o), 112 | u = f - 1, 113 | d--; 114 | break; 115 | case 2: 116 | return m.join("") 117 | } 118 | if (0 == d && (d = Math.pow(2, h), 119 | h++), 120 | l[u]) 121 | p = l[u]; 122 | else { 123 | if (u !== f) 124 | return null; 125 | p = i + i.charAt(0) 126 | } 127 | m.push(p), 128 | l[f++] = i + p.charAt(0), 129 | i = p, 130 | 0 == --d && (d = Math.pow(2, h), 131 | h++) 132 | } 133 | } 134 | 135 | 136 | 137 | var data = fs.readFileSync(path.resolve(__dirname,'./base64.txt')).toString(); 138 | console.log(data); 139 | const d = decompressFromBase64(data); -------------------------------------------------------------------------------- /src/node-demo/demo1/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var Koa = require('koa2'); 9 | 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost= `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody({ 17 | formidable: { 18 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 os 19 | uploadDir: path.resolve(__dirname, '../static/uploads') 20 | }, 21 | multipart: true // 支持文件上传 22 | })); 23 | 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | 30 | 31 | //跨域处理 32 | app.use((ctx) => { 33 | console.log('path',ctx.path) 34 | 35 | //指定一个接口和返回数据 36 | var path =ctx.path; 37 | if(path==='/getdata'){ 38 | 39 | console.log('receive req'); 40 | 41 | //服务端通过 ctx.headers.origin 获取请求中的origin 42 | //ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 43 | 44 | ctx.body=JSON.stringify({ 45 | code:0, 46 | msg:'success', 47 | data:[] 48 | }); 49 | } 50 | }) 51 | 52 | 53 | /** 54 | * Create HTTP server. 55 | */ 56 | var server = http.createServer(app.callback()); 57 | server.listen(port); 58 | console.log('cors demo1 server start ...... '); -------------------------------------------------------------------------------- /src/node-demo/demo1/static/html/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 | -------------------------------------------------------------------------------- /src/node-demo/demo1/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/node-demo/demo1/static/img/g.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/node-demo/demo1/static/img/g.jpg -------------------------------------------------------------------------------- /src/ssr-mock-data/child.js: -------------------------------------------------------------------------------- 1 | const Nightmare = require('nightmare') 2 | const nightmare = Nightmare({ show: false }) 3 | let len = process.argv.length; 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | const item = { 8 | href:process.argv[len-2], 9 | id: process.argv[len - 1], 10 | } 11 | 12 | async function run() { 13 | return await nightmare 14 | .goto(item.href) 15 | .evaluate(() => { 16 | var data = document.querySelector('.article-content').innerHTML; 17 | return data; 18 | }) 19 | .end() 20 | .then(res => { 21 | fs.writeFile(path.resolve('./db/' + item.id + '.html'), res, (error) => { 22 | if (!error) { 23 | console.log(item.id, 'is ok '); 24 | } else { 25 | console.log(error); 26 | } 27 | }); 28 | return res; 29 | }).catch(error => { 30 | console.error('get :', error) 31 | }); 32 | 33 | } 34 | 35 | run(); -------------------------------------------------------------------------------- /src/ssr-mock-data/db/59e17a7ff265da430629cc4e.html: -------------------------------------------------------------------------------- 1 |

小册简介

2 |

关于作者

3 |

4 |

大家好,我是@Easy,写了十多年程序,架设过每天数亿访问量的云平台;帮上千名程序员找过工作(创业项目是人才拍卖服务),也在用户数超过一千五百万的技术门户做过VP。

5 |

由于工作和兴趣的原因,在「程序员的职业」这件事情上积累了不少经验。

6 |

关于这本书

7 |

2014年,我写过一本《程序员跳槽全攻略》(简称《攻略》)的电子书,传播量还不错(能统计到的分发量大概在小十万左右),程序员们的反响也很热烈。但《攻略》有两个遗憾:一是我当时主要做高端人才拍卖的业务,整个视野聚焦在「已经很有才的人,怎么找更好的工作上」,在新人成长上不够友好;另一方面,当时一边创业一边写书,精力比较有限,即使想把主题拓展得更宽一些,也有心无力。

8 |

现在正好掘金邀请我来写本小册,于是这里在原来的基础之上,对程序员职业的「规划」和「经营」进行一个更为全面的教学,力争成为程序员们「了解行业」和「规划职业」的必读工具书。

9 |

本书包含了《攻略》的核心内容,没看过《攻略》的同学直接读本书就好;本书又进行了较大比例的更新,看过《攻略》的同学也建议重看一遍,以了解更新的细节。 10 |

你会学到什么

11 |

职业书最忌鸡汤化,稍微不注意就「懂得了很多道理,却依然过不好一生。」

12 |

所以这次我们直接给出了五个小目标和十个具体问题。

13 |

14 |

五个小目标

15 |

先来说目标。

16 |

① 首先我们试图帮程序员们建立起对职业和职业规划的正确认识。

17 |

职业规划之所以重要,是因为它不能回滚。我们写程序时都知道,能回滚的错误都不用怕,最怕的是不能回滚的错误。

18 |

职业就是这种东西,你年轻时做得不好的事情,会清清楚楚的写在你的履历上,等你真正经历过了,回过头来看时,只能感慨万千,却不能重头再来了。

19 |

你可能会问,这么重要的事情,我为什么凭什么要相信你,你又凭什么知道你是对的呢?

20 |

我们也不知道,所以我们要交给大家的是方法论、是职业规划的工具,它可以帮你更清楚的看清大局、更高效的分析细节、更明白的理解最佳实践,但它并不会帮你做决定。做决定的,依然是你自己。

21 |

② 第二个小目标,我们会带大家简单了解下软件工程师这个行业。

22 |

还没工作的同学可能对这个行业一头雾水,虽然经常听说这个听说那个,但往往雾里看花,并不知道自己要做的这个选择背后,要付出什么,能得到什么。就像很多人都只看到了这个行业的高薪,却忘掉了它近乎疯狂的加班和知识更新频率。

23 |

已经工作的同学也有不少对其他的职位并不了解,比如像前端工程师和后端工程师就经常觉得对方的工作简单,当然事实上呢,谁的工作都不会简单。

24 |

于是,我们选择了「前端开发工程师」、「后端开发工程师」、「移动开发工程师」、「小组经理」、「总监」和「CTO」这些常见职位,邀请了正在这些岗位上工作的同学,和我们一起来做关于工作的访谈,包括培训状况、工作时间、加班情况、遇到的技术挑战和遇到的非技术挑战以及给新人的建议等内容。

25 | 26 |

③ 第三个小目标是,「学会如何设计职业目标,并按节点前进」。

27 |

在有了工具、又了解了行业之后,我们就可以开始具体实践了。这里我们创建了一个有代表性的角色,应届毕业生「薛小生」,以他为范例,进行完整的职业规划实践,大家可以在其中,看到非常多的细节,从而解决操作层面上的问题。

28 |

最后两个小目标呢,是为了更好的帮助大家选择技术方向和编程语言,我们会:

29 |

④ 聊聊自己理解的编程的本质和看到趋势、还有那些 ⑤ 程序员职业的最佳实践和超级大坑

30 |

十个具体问题

31 |

从这五个小目标,我们延伸出了十个问题,作为重点讨论的对象。分别如下:

32 |

33 | 34 |

你应该已经了解什么

35 |

小白并不是指技术上的小白,而是指职业规划上的小白 —— 即使技术很好的高手,也可以能在职业上一塌糊涂。

36 |

本小册被设计为对应两类读者:

37 |
    38 |
  1. 大学计算机系学生和应届毕业生
  2. 39 |
  3. 计算机相关技术岗位从业者
  4. 40 |
41 |

对于前一种读者,可以先跳过最后两章,在成为真正的程序员,有切身感受以后再来阅读;对于后一种读者,可以跳过「程序员们的日常」和「新手如何快速起步」两章,但请同样阅读「通过职业画布理解应届生和新手的市场竞争力」一章,这一章虽然以新人为例,里边的细节却是每次职业调整都要用到的。

42 |

需求的量化分析一章,我们用到了脚本抓取招聘网站数据,小册中以 PHP 代码为例进行了说明,熟悉 C 或者其他语言的同学应该可以看懂其描述的逻辑,但如果你看不懂,也可以直接跳过代码部分。

43 |

你需要准备什么

44 |

建议准备好笔和纸。在阅读过程中,请按照的自己的情况,画出「职业路线图」上的节点,并一边读一边填出下一份工作的「职业画布」。以后每次换工作,都可以这么来一遍。

45 | 46 |

购买须知

47 |
    48 |
  1. 本小册为图文形式内容服务,共计 14 节,上线时间为 2017 年 11 月 22 日;
  2. 49 |
  3. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  4. 50 |
  5. 购买掘金小册后可直接开始阅读全本小册;
  6. 51 |
  7. 购买掘金小册用户可享受文章永久阅读权限;
  8. 52 |
  9. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  10. 53 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者本网将依法追究责任
  12. 54 |
55 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/59f954546fb9a04525776d7c.html: -------------------------------------------------------------------------------- 1 |

小册简介

2 |

那些在我们心里存在了很久的问题

3 |

最近几年,随着比特币的兴起并不断走高的价格,越来越多的投资者和机构开始对数字货币这一新兴概念产生了兴趣。由比特币创建并使用的区块链技术也获得了从创业公司到金融巨头的广泛关注和参与。

4 |

自08年诞生的第一种数字货币——比特币,从一万个币换一个披萨到一个币单价$6000,比特币8年暴涨百万倍,其背后的区块链技术更是被各大金融巨头竞相研究。

5 |

2008年至2017年11月比特币价格增长曲线

6 |

郎咸平教授称比特币是彻头彻尾的金融骗局,各种山寨虚拟币诈骗也层出不穷。比特币究竟是虚拟资产还是庞氏骗局?人民日报发文称比特币成了“数字黄金”,央行更是早就设立了数字货币研究所,并表示正在推进中国自己的数字货币系统。

7 |

到底什么是数字货币?没有中心节点的数字货币却号称不可伪造,数字货币和区块链究竟是如何运行的?又会给未来的互联网金融以及其他行业带来哪些变化?和每个人息息相关的金融服务将来会运行在区块链之上吗?

8 |
9 |

马云说,对于新兴事物,绝大多数人第一看不见,第二看不起,第三看不懂,第四来不及。

10 |
11 |

如果你对比特币等数字货币处于“看不懂”阶段,不想等到“来不及”阶段,作为国内原创的数字货币与区块链的中文教程,将为你深入浅出地介绍数字货币和区块链技术的运行原理,让你对挖矿、钱包、签名、密钥这些技术不再陌生。

12 |

名人推荐

13 |

14 |

关于作者

15 |

16 |

廖雪峰,拥有十多年软件开发经验,精通Java/Python等多种编程语言,对区块链技术有深入研究。欢迎访问廖雪峰的官方网站:www.liaoxuefeng.com

17 |

适合人群

18 | 22 |

请注意:本教程仅从技术角度介绍数字货币和区块链原理,不构成任何投资比特币或者其他数字货币的意见和建议! 23 |

你会学到什么?

24 |

你将学习数字货币和区块链的运行原理:

25 | 37 |

等技术原理

38 |

你应该已经了解什么?

39 |

本教程不涉及具体程序开发,你只需熟悉计算机网络即可。

40 |

购买须知

41 |
    42 |
  1. 本小册为图文形式内容服务,共计 5 节,上线时间为 2017 年 11 月 22 日;
  2. 43 |
  3. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  4. 44 |
  5. 购买掘金小册后可直接开始阅读全本小册;
  6. 45 |
  7. 购买掘金小册用户可享受文章永久阅读权限;
  8. 46 |
  9. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  10. 47 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者本网将依法追究责任
  12. 48 |
49 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a09997cf265da432002bf6a.html: -------------------------------------------------------------------------------- 1 |

小册简介

2 |

3 | RxJava 可以称得上是 Java 世界里的明星项目,在 github 上,Java 语言分类下的项目,star 数排名第一项目的就是 RxJava,它确实是Java世界的革命者,解决了 Java 在许多方面的编程困境;当然,也有人说,RxJava 如此的流行,是因为抱上了 Android 这条大腿,这给 RxJava带来了巨大的关注度(我不确定到底是谁抱了谁的大腿),哈哈。

4 |

5 |

抛开大腿论调,RxJava确实可以帮助你写出更加高效而且简洁的代码,在传统的编程方式中很难实现的逻辑,通过 RxJava 可以很方便的处理。于此同时,也可以帮助你对自己的项目结构有更加深入的了解,可以说这两者是相辅相成的。

6 |

遗憾的是,在中文互联网中,大部分的博客都聚焦于怎么入门,如何使用那些操作符等等这些相对基础的东西,这对于不了解RxJava的开发者来讲,是一个非常友好的现象。至少,我本人最初也是分享一些的关于RxJava相对基础的文章。不过随着学习的深入,我还是觉得关于RxJava需要一个相对高阶的内容,去关注那些相对深入和底层的概念,以及RxJava相对高阶的问题。

7 |

这本小册的名字叫RxJava高阶指南,那么必定和普通的RxJava入门教程是有所区别的,这本小册会着力于探讨两大类问题:

8 | 12 |

最近,我突发奇想,把自己这一两年关于RxJava相关的文章和笔记汇集在一起,然后根据词汇出现的频率做了一个弹幕图片(单词出现频率越大,单词字体越大):

13 |

14 |

里面的很多高频的单词,就是我们接下来需要深入探讨的概念。这说明,我们时常提到一些概念,一些单词,比如异步编程模型,响应式编程,基于事件等等,但是一旦要你把它说清楚,又会觉得很困难。这是这本小册会帮助你解决这个问题。

15 |

研发团队都应该学习使用RxJava,因为这会提高开发的效率,降低在处理复杂逻辑时的失误率,而团队里更应该有一个对RxJava最了解的人来坐镇,当团队出现比较棘手的问题时,能出手解决;我希望这本小册能够帮助你成为那个人。

16 |
17 |

这本小册将会以Java为主要的代码实现语言,但其实很多内容也基本适用于所有的Rx其他语言实现版本。

18 |
19 |

关于作者

20 |

大家好,我是拉丁吴,糗事百科Android工程师,没有名号,多多指教。

21 |

适合人群

22 |
    23 |
  1. Android工程师
  2. 24 |
  3. Java工程师
  4. 25 |
  5. 对响应式编程感兴趣的开发者
  6. 26 |
27 |

你会学到什么?

28 | 38 |

你应该已经了解什么?

39 |

你应该具备基本的Java编程知识,以及对RxJava有基本的了解

40 |

如果不太了解RxJava基础怎么办?

41 |

其实我在掘金上一些写了一些关于RxJava的基础入门文章:

42 | 47 |

从阅读量和点赞数来看,这是掘金上所有关于Rxjava的文章中最受欢迎的小系列,其中第一篇还成为了去年掘金年度最受欢迎的文章之一,如果你看过这些文章,我相信你就不会担心接下来的小册的内容会晦涩难懂,我会尽力把复杂的概念拆解给大家,帮助大家理解,在小册的易读性上,大家可以完全放心。

48 |

购买须知

49 |
    50 |
  1. 本小册为图文形式内容服务,共计 10 节,上线时间为 2017 年 11 月 22 日;
  2. 51 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 52 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 53 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 54 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 55 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 56 |
57 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a0ab8e2f265da43111fbab2.html: -------------------------------------------------------------------------------- 1 |

小册简介

2 |

你可能会看到这样的效果: 3 |

知乎登录背景

4 |

又或者这样的: 5 |

QQPC介绍页面

6 |

还可能是这样的: 7 |

http://cherryblog.site/

8 |

笔者博客地址:cherryblog.site ٩(๑>◡<๑)۶

9 |

你是不是被上面几个例子中的背景所吸引,很好奇这些背景是怎么实现的?是否也想让自己的个人网站有同样炫酷且与众不同的背景呢?

10 |

本小册将会帮你解答这个问题:如何使用 Canvas 制作出炫酷的网页背景特效。

11 |

你可能好奇这种效果是怎么做到的呢?其实答案很简单,就是 HTML5 的新标签 —— Canvas。

12 |

Canvas 自 HTML5 发布以来就受到了广泛的关注,但却很少在项目中使用,所以大部分前端攻城狮都只是知道,很少实践。

13 |

本小册将带你进入 Canvas 的世界,为你展示 30+ 个 Canvas 项目,你会惊叹于 Canvas 所制作出的神奇效果。

14 |

同时会带大家从零开始学习 Canvas:从零开始,教你绘制出基本图形。通过组合这些基本图形,将产生不可思议的化学反应~

15 |

在入门了 Canvas 之后,将带大家分析那些可以作为背景的 Canvas 炫酷特效,总结出这些炫酷特效都有哪些特点。日后这些就是你的思想武器。掌握了这些规律,你就相当于站在了巨人的肩膀上,再制作炫酷的网页背景特效将会事半功倍。

16 |

在了解了这些特点之后,我们将通过一个具体的案例来一起实现一下这些特效。

17 |

最终, Canvas 基础知识 + 特效特点 + 你丰富的想象力 = 属于你自己的炫酷网页背景特效。

18 |

划重点:本小册的所有特效,都会将源码分享给大家~ (。♥‿♥。)

19 |

你会学到什么?

20 | 26 |

你应该已经了解什么?

27 | 31 |

你需要准备什么?

32 | 37 |

购买须知

38 |
    39 |
  1. 本小册为图文形式内容服务,共计 9 节,上线时间为 2017 年 11 月 22 日;
  2. 40 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 41 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 42 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 43 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 44 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 45 |
46 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a1212bc51882531ea64df07.html: -------------------------------------------------------------------------------- 1 |

内容简介

2 |

3 |

互联网大潮和前端社区的蓬勃发展让现代前端项目的复杂性比 5 年前翻了好多倍,前端工作流中也出现了越来越多工程化的环节,比如代码风格检查、自动化测试、自动化构建、自动化部署、服务监控、依赖管理等。

4 |

大多数前端工程师的工作流可能都离不开 gulp、grunt、webpack 这样的重量级构建工具,而是否能熟练运用这些工具将重复任务自动化也是工程师素质的重要体现,我本人也是这些自动化工具的忠实粉丝,因为它们确实能帮我解决问题。但几番折腾之后,你可能已经像我一样感受到明显的痛点:比如对插件依赖严重(开发者的自由度受限),插件和底层工具文档脱节,调试变的更复杂等。

5 |

相比而言,直接使用 npm 内置的 script 机制已经被无数开发者证明是更好的选择,它能减轻甚至消除上面的痛点:你可以直接使用海量的 npm 包来完成你的任务、不需要在插件文档和基础工具文档间来回切换,最重要的点,不使用 grunt 之类的构建工具能让你的技术栈相对更简单,而我在做技术选择是遵循的基本原则是简单化,简单才有可能容易让别人上手。

6 |

可能有同学会反问,Talk is cheap, show me the data,下面这张图(出自这里)是最好的证明:

7 |

8 |

更精确的数据是:截止 2017年11月,grunt 插件 6309 个,gulp 插件 3367 个,webpack 插件数量 2174 个,而 npm 包多达 594438 个,并且还在飞速增长

9 |

那 npm script 为什么没有没有在构建工具中成为主流呢?可能大多数人觉得使用 npm script 需要很强的命令行功底、或者它不够强大、或者它不能跨平台。可以很负责任的说,社区发展到现在,上面的担心都是多余的。

10 |

这也是这本小册的切入点,我在这本小册中会用 step-by-step 的方式讲解如何使用 npm script 打造轻量级但完整的前端工作流。即使你是命令行小白,也能轻松跟上,小册会以实际前端项目为底板逐步介绍更高阶的话题。学完这本小册,你将熟知使用 npm script 打造前端工作流要用的各种小工具和技巧。

11 |

小册的内容划分为 4 篇:

12 | 18 |

此外,为了方便读者上手实践,我还为每个小节录制的视频教程,想了解我短视频教程风格和质量的同学可以看我专栏的历史文章:styled-componentsasync/await

19 |

说句题外话,我的 zsh 命令行历史中 npm 已经是仅次于 git 调用次数的命令了。

20 |

适合什么群体?

21 | 26 |

你会学到什么?

27 | 33 |

你要准备什么?

34 | 39 |

视频目录如何?

40 |

41 |

关于作者

42 |

王仕军,爱折腾、爱分享的前端老司机,6 年以上前端开发经验,4 年大型互联网公司工作经验;掘金专栏作者;熟知(是的,到现在我还不敢说精通) JavascriptNode.js,对开发效率和软件质量有极致追求。目标是 Be a Power User of Everything

43 |

读者福利

44 | 48 |

购买须知

49 |
    50 |
  1. 本小册为图文形式内容服务,共计 15 节,上线时间为 2017 年 11 月 22 日;
  2. 51 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 52 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 53 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 54 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 55 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 56 |
57 |

更新日志

58 |
    59 |
  1. 2017-11-20 完成小册简介、为什么选 npm script、hello npm script 3 部分内容;
  2. 60 |
  3. 2017-11-24 修订小册第 1.1 节(试读部分),大量新增内容,如给新老项目配置 eslint 的方法;
  4. 61 |
  5. 2017-11-25 增加小册更新进度表,增加小册更新日志部分;
  6. 62 |
  7. 2017-11-25 完成小册第 1.2 节:运行多个 npm script 的各种姿势;
  8. 63 |
  9. 2017-11-27 完成小册第 1.3 节:给 npm script 传递参数、添加注释以及控制输出;
  10. 64 |
  11. 2017-11-29 完成小册第 2.1 节:使用 pre、post 钩子把自动化任务串起来;
  12. 65 |
  13. 2017-12-01 完成小册第 2.2 节:使用自定义变量改进测试覆盖率归档过程;
  14. 66 |
  15. 2017-12-03 完成小册第 2.3 节:实现 npm 命令自动补全;
  16. 67 |
  17. 2017-12-05 完成小册第 3.1 节:实现 npm script 的跨平台兼容;
  18. 68 |
  19. 2017-12-07 完成小册第 3.2 节:用 scripty 隔离 npm script 的复杂性;
  20. 69 |
  21. 2017-12-10 完成小册第 3.3 节:用 Node.js 脚本替代复杂的 npm script;
  22. 70 |
  23. 2017-12-12 完成小册第 4.1 节:在文件变化时执行 npm script;
  24. 71 |
  25. 2017-12-14 完成小册第 4.2 节:结合 livereload 实现自动刷新;
  26. 72 |
  27. 2017-12-14 完成小册第 4.3 节:在 git hooks 中执行 npm script;
  28. 73 |
  29. 2017-12-18 完成小吃第 4.4 节:用 npm script 实现构建流水线;
  30. 74 |
  31. 2017-12-18 完成小册第 4.5 节:用 npm script 进行服务运维,至此小册文字版完稿;
  32. 75 |
  33. 2018-01-02 完成小册视频版录制。
  34. 76 |
77 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a124b29f265da431d3c472e.html: -------------------------------------------------------------------------------- 1 |

作者介绍

2 |

3 |

我是扔物线,Android 开发者,开源贡献者,在 GitHub 上有 4.9k followers 和 7.8k stars ,个人的 Android 开源库 MaterialEditText 被全世界多个项目引用,其中包括在全球拥有 5 亿用户的新闻阅读软件 Flipboard 。曾两次在 Google Developer Group Beijing 线下分享会中担任 Android 部分的讲师。个人技术文章《给 Android 开发者的 RxJava 详解》发布后,在国内多个公司和团队内部被转发分享和作为团队技术会议的主要资料来源,以及逆向传播到了美国一些如 Google 、 Uber 等公司的部分华人团队。

4 |

现在我正全职在做一个我个人的免费的 Android 高级进阶分享计划 HenCoder,旨在帮助国内的高级 Android 工程师(例如小团队的 Android Leader)突破技术瓶颈,继续高速提升。

5 |

小册简介

6 |
7 |

「Git 好难啊!」不会用 Git 和会用 Git 的人都这么说。

8 |
9 |

10 |

随着这几年 GitHub 的流行,Git 已经是一个程序员逃不过的技术项,但很多人却纷纷倒在了学习它的路上。而且,出于工作原因而不得不用 Git 的人,有不少在工作中对 Git 也是能不用就不用,生怕哪个命令用错就把公司的代码库毁掉了🙈。而那些对 Git 掌握得比较好的少数人,就像团队中的神一样,在同事遇到 Git 相关的问题的时候用各种风骚操作来拯救队友于水火。

11 |

12 |

学不会、学不好 Git 的人,其实多数并不是不愿意学。很多人都会尝试去网上找 Git 教程、去社区请教高手、在公司咨询同事,但转了一大圈下来,依然没有搞懂,甚至有可能越来越糊涂。

13 |
14 |

- 你刚才输入的这个 Git 指令是什么意思?
15 | - 意思是 XXX。
16 | - 可你上次跟我说它的意思是 YYY 啊?
17 | - 嗯对,不同的场景不同的用法,上次是 YYY。
18 | - ……好吧。另外你上次帮我解决这个问题用的是另一个指令 zzz 啊?
19 | - 嗯对,那个也能解决,但这次用这个指令更适合,因为 @#¥@%*&。
20 | - ……
21 | - 懂了吗?不懂的话还可以问我,没事的。
22 | - ……

23 |
24 |

Git 学习到底难在哪?

25 |

26 |

Git 的学习曲线很不友好:想上手很容易,只要学会 commit、push、pull 等几个指令,就能够初步地使用它;但如果想要更进一步,让自己能够在团队项目中和朋友或同事自由合作,却又很难。

27 |

那么 Git 到底难在哪呢?

28 |

其实关键在于一点:概念

29 |

Git 的概念,是由一套完整的思维逻辑所构成的。 你不能从多个角度分步理解它,而是要把它作为一个整体一下子吃掉;而同时这个「整体」由于过于复杂,又实在有点难以一口吞。颇有点悖论的意味。

30 |

31 |

很多人在使用 Git 一段时间后,会觉得 Git 有点复杂和混乱:

32 |
33 |

- 为什么要 commit 后再 push 这么啰嗦,而不能直接提交到中央仓库?
34 | - reset 这个指令为什么这么神奇,好多看起来并不相似的操作却都要用到它?它到底是干嘛的?
35 | - revert 和 rebase 都可以撤销历史提交?它们的区别在哪?什么,你说 reset 也行?

36 |
37 |

类似的问题其实还有很多。这些问题看起来每个都很难,但只要你把 Git 的概念了解了,这些问题(以及那些许许多多我没有列出来的问题)就全都迎刃而解了。

38 |

学懂了概念,就能学懂 Git,就这么简单。可是市面上的很多 Git 教程都只停留在了 Git 的使用上,而对它的概念却总是一笔带过或干脆提都不提。这里的原因,我猜可能是因为它的概念太难讲清楚了,也可能是因为这些作者其实也对 Git 的许多概念并不够了解吧(这句是胡说八道,Git 教程的作者们请放下手中的枪)。

39 |

你为什么应该选择这本小册?

40 |

读了这本小册,你可以彻底理解 Git,从而彻底会用 Git。Git 的确很难,但别担心,读了这本小册你就从根本上掌握它了(虽然熟练使用还会需要一些时间来练习)。

41 |

我写技术文章,比较喜欢挑难的写:难学会的,难讲清的。我写过 RxJava 的详解,写过 Android 自定义 View 的原理,目前来看反馈都很不错。这些「学的人学不会,讲的人讲不明白」的东西,写起来很痛苦,但写完之后的成就感也挺大,我会去跟老婆吹牛:「这东西好多人都讲不明白,我给讲明白了,你看这些人看了以后留言多激动」(这话只敢在家说,出去说怕被打)。而且,写着写着,我也越来越明白怎么把复杂的技术讲简单、讲透彻。

42 |

所以简单地总结:Git 难学,是因为它的概念难以整体理解。而这本小册,就是从概念的角度出发,帮你先从本质上了解 Git 的工作模型,在此基础上去了解它的具体用法,以此来达到四两拨千斤的学习效果。而且这样的学习具有更高的持久性,在看完这本小册之后,你以后也很难再忘掉 Git 的用法了。

43 |

44 |

你会学到什么?

45 | 52 |
53 |

例如:「如何修改历史提交中的错误」「误删 branch 怎么办」「merge 和 rebase 的区别」「reset 的几种实用用法」这类东西又多又难记,但其实你根本不用去记它们。在你了解了 Git 的本质之后,不仅这些日经问题你能轻松解答,而且一些罕见的、复杂的问题,你也应付得来。

54 |
55 |

你应该已经具备什么?

56 | 60 |

你需要准备什么?

61 | 64 |

你需要做什么?

65 | 69 |

然后,你就可以在工作中自如地使用 Git 了。而且很快,你就能成为同事眼里的那个神奇的「救火队员」(如果你愿意的话)。

70 |

读完这本小册你会发现,Git 真的不难。

71 |

购买须知

72 |
    73 |
  1. 本小册为图文形式内容服务,共计 22 节,上线时间为 2017 年 11 月 30 日;
  2. 74 |
  3. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  4. 75 |
  5. 购买掘金小册后可直接开始阅读全本小册;
  6. 76 |
  7. 购买掘金小册用户可享受文章永久阅读权限;
  8. 77 |
  9. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  10. 78 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者本网将依法追究责任
  12. 79 |
80 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a157c155188254a701eb3c1.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

为什么要学爬虫?

3 |

爬虫是一个非常具有实践性的编程技能,它并不是程序员的专属技能,任何具有一定编程基础的人都可以学习爬虫,写个爬虫分析一下股票走势,写个爬虫YouTube下载视频,上链家爬个房源数据分析房价趋势,爬知乎、爬豆瓣、爬新浪微博、爬影评,爬虫有太多可以做的事情,人工智能时代,对数据的依赖越来越重要。

4 |
5 |

马云说:数据是新一轮技术革命最重要的生产资料。

6 |
7 |

数据主要的来源就是通过爬虫获取,通过爬虫获取数据可以进行市场调研和数据分析,可以作为机器学习和数据挖掘的原始数据,我们通过微信公众号爬虫得到的数据对新媒体内容提供运营策略。

8 |

9 |

通过爬虫发现原来我4年前就在公众号写了文章,最近一年写了一百多篇,这些数据在微信平台是没法统计的,只有通过爬虫自己来统计分析。

10 |

11 |

对小白来说,爬虫可能是一个很复杂的事情,现在我们带着一个具体的目标(以爬虫微信公众号文章为例),在目标的驱动下,跟着这本小册一步一步学会爬虫,同时,那些所谓的前置知识也在这个过程中学会了。在这本小册中,我将以手把手的方式教会你如何进行网络爬虫。

12 |

为什么要学Python

13 |

Python 作为一门连小学生都可以学会的语言,非常适合没有编程基础的同学。它可以让你更快的理解编程的思想,能让你体会到通过编程来解决问题带来快乐,它没有复杂的语法,最为接近伪代码的语言,没有繁琐的编译过程,也不需要你手动管理内存,类库非常丰富,解决各种问题都有很多现成的工具,无需自己造轮子。

14 |
15 |

Python之父说:人生苦短,我用Python。

16 |
17 |

18 |

你会学到什么?

19 | 27 |

你需要准备什么?

28 |

任何对网络爬虫感兴趣者,或者是对微信公众号数据感兴趣的人都可以参与到这本小册中来,你需要准备的东西包括:

29 | 35 |

温馨提醒 36 | 最后还是要声明一下,爬虫与反爬虫就像矛与盾,它们之间的较量是一场没有硝烟的战争,所以需要提醒广大爬友,爬取微信公众号文章数据过程中可能会受到微信服务器反爬虫机制的抵抗,虽然我没有遇到过明显地账号被限制的情况,但是我并不能保证你的微信号不会出现异常,在爬虫过程中,一定要控制好节奏,别惹怒了微信爸爸,为了保险起见,用小号进行测试爬虫是最安全的。 37 |

作者介绍

38 |

39 | 刘志军,Python 开发者,多年大型互联网公司工作经验,知乎 Python 话题活跃回答者,CSDN 公开课 讲师,在微信公众号「Python之禅」有4万+读者。

40 |

购买须知

41 |
    42 |
  1. 本小册为图文形式内容服务,共计 11 节,上线时间为 2017 年 12 月 22 日;
  2. 43 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 44 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 45 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 46 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 47 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 48 |
49 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a261d8f5188253ee45b4b21.html: -------------------------------------------------------------------------------- 1 |

重制说明

2 |

转眼里这本小册发行已接近一年,Laravel 也从 5.5 版本迭代到了 5.7 版本,一直萌生重写这本小册的想法,现在已经是实施的时候了。现在都流行重制,帝国二、魔兽三的重制版都在近期发售了,所以我也借来这个概念,编写一个这本小册的重制版。

3 |

在重制版本中,我将从以下方面对小册的内容进行更新:

4 |
    5 |
  1. 所有 Laravel 的示例更新至 5.7 版本;
  2. 6 |
  3. 增加更多设计模式和其在 Laravel 中的体现;
  4. 7 |
  5. 丰富文字和图片内容,更详尽的展现每种设计模式。
  6. 8 |
9 |

除了内容,大家比较关心的应该是重制版的收费了。很高兴告诉大家,由于小册仍然是这一本,我只是在此之上增加、调整内容,所以已经购买过这本小册的读者,可以直接阅读重制版,并不需要额外付费。

10 |

另外,在重制版编写的过程中,我会直接把编写好的章节直接更新到小册中,所以新老篇章是共存的。在这段时间中,读者阅读小册可能会产生前后断章的感觉,望请见谅。

11 |

有明
12 | 2018.11.12

13 |

小册简介

14 |

Laravel 是个致力于为开发者创造愉悦、舒适开发体验的 PHP Web 框架。 15 | Laravel 最大的优势,是做好了足够的准备,能够让使用者摆脱开发过程中对基础设施的关心,使用简洁、优雅的代码,专注于需要实现的功能。

16 |

正如 Laravel 对自己的介绍:

17 |
18 |

The PHP Framework For Web Artisans

19 |
20 |

Laravel 希望使用它的开发者,将代码视为艺术品,全身心投入到艺术创作中去。

21 |

Laravel 5.5

22 |

从 Google 趋势对近五年来热门的几个 PHP Web 框架分析来看,Laravel 的关注度莫不能说是令其它前辈都难以望其项背的。

23 |

热门 PHP 框架近五年的 Google 搜索趋势

24 |

与其他 PHP 框架不同,Laravel 是个站在巨人肩膀上的框架。 25 | 它汲取了前辈框架,甚至其他语言框架的优秀思想,让自己拥有充实、丰满的功能。 26 | 而借助于 Composer 和自身的容器体系,让其拥有了能够快速融入其他扩展的能力。

27 |

Swoole 的作者韩天峰是这么评价 Laravel 的:

28 |

韩天峰的评价

29 |

与 Laravel 所推崇的以艺术之心设计代码的理念相同,Laravel 框架本身,也是由精美绝伦的代码组成。 30 | 其中的架构思想、设计模式、逻辑结构,乃至代码样式,都可称作是教科书式的典范。 31 | 在这本小册中,我们就从 Laravel 框架代码中所使用到的主流设计模式入手,对它们进行归类并分别讲解。 32 | 不仅希望通过这本小册,让大家了解 Laravel 中的设计模式和设计思想,进而更好的掌握 Laravel 的使用。 33 | 更希望大家能够以 Laravel 为范本,升华自己对程序设计的理解,将这些思想应用到更广泛的设计和开发中去。

34 |

35 |

为什么这本小册你值得拥有?

36 |

我们知道,在国内,PHP 长期处于专业素养匮乏的状态,生态环境相对来说非常糟糕,而 PHP 也一直处于程序员鄙视链的一端。 37 | 然而,我们知道,国内的百度、腾讯、360,国外的 Facebook、Yahoo、维基百科 等等,都在使用 PHP,甚至以 PHP 作为主力语言。 38 | 这就说明,PHP 并非真的一塌糊涂,PHP 是世界上最好的语言也绝非一句空话。

39 |

相对于其他开发语言来说,国内的 PHP 领域一直缺乏较为完整的生态体系,也没有相对权威、全面的文档等资源支持,难以大范围的培养优秀的 PHP 开发者。 40 | 并且,互联网上关于 PHP 的相关资源参差不齐,有的过于老旧,仍是面向过程的开发结构,非常容易使初学者陷入泥潭和误区。 41 | 然而放眼世界来说,PHP 领域早就不是以往草根的形象,在 PHP 领域早已出现了很多优秀的框架、设计、规范等等。

42 |

设计模式

43 |

那么要成为一个合格的 PHP 开发者,除了良好的语言功底之外,一些编程领域的理论也是不可或缺,必须掌握的。 44 | 设计模式作为其中一员,也是最为常见,使用范围非常广泛的理论体系之一,自然是少不了的一门学问。 45 | 这本小册结合了当下最流行的 PHP 框架,通过深入剖析代码,结合理论讲解的形式,希望给广大开发者,特别是对这些领域知识掌握比较薄弱的 PHP 开发者,提供参考。

46 |

另外,这本小册与其他关于设计模式的书籍、教程有很大的不同。 47 | 在其他关于设计模式的书籍、教程里,通常只是通过片段式的代码,配合复杂、抽象的概念进行说明。 48 | 这种形式不但很难让开发者理解设计模式的理论概念,也属于纸上谈兵,没有让开发者掌握实战使用时选择和实现的道理。 49 | 而在这本小册里,结合的是 Laravel 框架成熟且已经广泛应用和验证的代码作为范例,进行深入浅出的剖析和讲解。 50 | 不但向大家展示 Laravel 框架代码设计的美学,也用真实的应用场景来承载设计模式的理论。 51 | 让概念和实践结合,使理论不在枯燥乏味。

52 |

53 |

你会学到什么?

54 | 60 |

作者介绍

61 |

62 |

有明,长期关注新兴技术结合微服务化实现,对技术理论体系有所研究。熟知全栈开发、运维相关技术。多个开源框架开发、维护者,Docker 小型软件镜像库 Cogset 发起者。SF 认证讲师。著有 《没什么难的:Docker》。

63 |

购买须知

64 |
    65 |
  1. 本小册为图文形式内容服务,共计 13 节,上线时间为 2017 年 12 月 20 日;
  2. 66 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 67 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 68 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 69 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 70 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 71 |
72 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a36661851882538e2259c0f.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

之前写《Vue.js 源码解析》的时候笔者就一直在思考,如何写出让新手同学能够读懂的文章呢?当时采用通篇的源码加上注释的方式讲解,笔者发现这样做不但导致文章体量大代码多,而且对没有阅读过源码或者没有阅读大型项目源码经历的同学来说并不友好。因为源码中有很多细节的东西,这些东西对于理解整个项目的内部运行机制并不那么重要,应该是先理解内部运行机制,然后再去深剖这些细节。

3 |
4 |

那么怎么样让新手更容易理解这些内容呢?

5 |

于是笔者就诞生了一个想法:把 Vue.js 的核心源码抽离出来,写成一个一个代码量更小更精细的 Demo ,形成一个简易版的 Vue.js 轮子,尝试用更少量的代码讲解核心部分内容,这样能更好地让人理解,毕竟大段的源码在没有上下文的情况下会让人觉得晦涩难懂。

6 |
7 |

所以这本小册就这样诞生啦,期望能以一种对新手更加友好的方式来讲解 Vue.js 内部运行机制。

8 |

9 |

讲了那么多,我们还是要介绍一下 Vue.js 这一款优秀的 MVVM 框架。 Vue.js 是一款专注于视图层、用于构建用户交互界面的响应式渐进框架。除了大大提高了开发效率并降低了维护成本以外,它还拥有着优雅的 API 设计、快速上手的特性,这使它已经成为了目前主流前端框架之一。

10 |

但是你们有没有思考过:

11 | 16 |

很多同学并没有对其原理有一个更深一层的理解,导致在遇到一些难以琢磨的问题的时候会感到无从下手。

17 |

本小册希望通过一种对新手更加友好直观的方式讲解 Vue.js 内部运行机制。把 Vue.js 拆分成多个小模块,讲解模块间的依赖以及调用关系。然后将源码核心部分抽离压缩,各个模块以小 Demo 的形式展现出来,用最少的代码讲解内部实现。掌握了这些模块的核心原理之后,再去阅读 Vue.js 源码或者是解决 Vue.js 的疑难杂症时,相信会更加得心应手。

18 |

程序界的「二八定律」,百分之八十的问题可以运用百分之二十的知识来解决,而剩下的百分之二十的问题需要运用百分之八十的知识来解决。准备好那百分之八十的知识,才会在遇到有挑战的问题时更加游刃有余,机会永远留给准备好的人。

19 |

20 |

本小册希望用一种对新手更加友好的方式来讲解 Vue.js 内部运行机制,带领大家漫游 Vue.js 的世界,旨在帮助每一名想要进一步学习 Vue.js 的开发者。

21 |

作者介绍

22 |

23 |

染陌,前端工程师,掘金专栏作者。 24 | 前 C++ 后端工程师,技术涉猎广泛。GitHub 千星项目《Vue.js 源码解析》作者,对 Vue.js 有着较为深入的研究。

25 |

公众号:前端技术优选

26 |

27 |

GitHub:github.com/answershuto

28 |

名人推荐

29 |

30 |

31 |

你会学到什么?

32 | 42 |

43 |
44 |

了解基本实现有利于想去阅读 Vue.js 源码的同学更快更有效地阅读源码,不会再觉得大量的源码难以入手

45 |
46 |

适宜人群

47 | 51 |

你需要准备什么?

52 | 57 |

购买须知

58 |
    59 |
  1. 本小册为图文形式内容服务,共计 9 节,上线时间为 2018 年 1 月 17 日;
  2. 60 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 61 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 62 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 63 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 64 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 65 |
66 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a6abad5518825733c144469.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

缤纷多彩的前端世界

3 |

随着互联网大潮的兴起,web 项目复杂度上升,前端社区蓬勃发展,前端构建已经是开发工作中一个绕不开的话题。上图罗列了前端的很多东西,除了一些类库,其他基本都和构建有一定的关系。

4 |

在前端旧时代,JavaScript 本身就缺乏模块化相关规范的支持,而现今也并非所有浏览器都支持新的 JavaScript Module,所以在前端构建中,代码模块打包几乎就是最重要的一部分。

5 |

Popular degree of module bundler

6 |

上图简单地比较了当前前端社区比较流行的四个模块打包工具 —— webpackbrowserifyrollupparcel-bundler 2018 年 1 月 npm 包下载量和 GitHub stars 数量,很明显,webpack 可以说是最为人所知,使用量最大的一个。

7 |

Contributions

8 |

从 GitHub 的 webpack 代码仓库的 master 贡献图来看,webpack 从 2012 年开始,主代码仓库都相当活跃,尤其是 2017 年,更是有了巨大的变化。

9 |

期间,webpack 经历了几个主要版本变更,从开始火热的 1.x 版本,经过了 2.x,3.x,走到了现在还在成长中的 4.x 版本,webpack 在不断地完善中。整个 webpack 社区一直是相当活跃的,周边相关的插件等基本覆盖了前端日常开发工作所需,随时代发展,也添加了不少新特性,如 tree-shaking 等。

10 |

至今,webpack 已经成为家喻户晓的前端打包工具,是当前可以使用的前端代码模块打包工具中最具代表性的一个。

11 |

webpack as a bundler

12 |

使用过 webpack 的都了解,webpack 本身具备了诸多优点:从单一入口出发,打包所有前端资源,使用 loader 处理多种代码语言的转换,使用 plugin 扩展原有的模块打包流程,使用 HMR 提升开发体验,利用代码压缩和代码分割来提升前端加载性能等。

13 |

我们可以发现,在大多数项目中,webpack 已经可以成为构建工作的主心骨,应该具备的功能都已经具备,对 webpack 的了解几乎成了前端开发人员必不可少的技能之一。

14 |

但是一直以来,webpack 文档说明为人所诟病,由于 webpack 本身功能就具备一定的复杂性和自由度,文档总是难以面面俱到(3.x 版本已经改善了相当多,4.x 的文档还在努力准备中),部分细节没有深入,也比较少去讲述 webpack 面向具体构建需求时的使用。同时,webpack 源码的可读性一般,开发者遇见问题时难以开发调试,普遍停留在 webpack 的基础使用上,不敢大刀阔斧地用来定制舒服的开发流程。

15 |

我们可以在社区中找到很多关于 webpack 的文章,有不少相当实用的,可以帮助新手快速入门 webpack,也有不少相当深入的,可以从某一层面剖析如何更好地使用 webpack,但总归是零零散散,缺乏一个相对系统化的 webpack 学习指南。

16 |

17 |

我希望将我使用 webpack 的经验总结一下,来完成这么一份系统化的学习指南,来帮助更多开发者熟悉了解 webpack,更好地利用 webpack,为自己,为团队,随心所欲地定制前端开发环境。于是,便诞生了这样一本小册,希望能够对你的前端之路有所帮助。

18 |

作者介绍

19 |

20 |

teabyii,《Node.js硬实战:115个核心技巧》译者之一,前支付宝前端工程师,现唯品会高级开发工程师,曾负责多个前端系统的基础构建服务,热爱 JavaScript,喜爱折腾开发工具,致力于提高前端开发效率,正努力走在成为优秀的开发工程师的路上。

21 |

名人推荐

22 |

23 |

24 |

你会学到什么?

25 | 34 |

希望你在学完整个小册后,可以做到:

35 | 42 |

适宜人群

43 | 47 |

温馨提示

48 | 52 |

购买须知

53 |
    54 |
  1. 本小册为图文形式内容服务,共计 16 节,上线时间为 2018 年 3 月 28 日;
  2. 55 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 56 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 57 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 58 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 59 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 60 |
61 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5a7bfe595188257a7349b52a.html: -------------------------------------------------------------------------------- 1 |

作者介绍

2 |

凹凸实验室

3 |

京东凹凸实验室 (Aotu.io,英文简称 O2) ,创立于 2015 年 10 月,为掘金最早一批联合编辑,拥有数千关注者。O2 对内负责京东 PC 端首页、多个频道页、小程序版本京东购物、微信手 Q 京东购物、M 端京东、各类营销活动场景 HTML5 活动等多个业务模块的前端开发工作,对外作为京东的多终端技术品牌一直活跃于掘金GitHub 等知名知识共享平台。

4 |

O2 官网:Aotu.io

5 |

小册介绍

6 |

这本小册是由京东凹凸实验室的多名资深前端开发工程师(Koppt、JC、EC、大婷、小婷、陈老湿、AV、LV,排名不分先后,部分同学曾在腾讯 ISUXTGideas 团队就职)结合自身工作实践,梳理整合了凹凸实验室近年积累沉淀的和主题相关的心得体会(部分在凹凸实验室博客上分散发表过),联合编写,献给那些想应聘京东或腾讯等大厂「H5 开发工程师」或「UI 开发工程师」的童鞋们看的。当然,对于那些致力于成为「全栈开发」或提升自己综合开发能力的「前端开发」人员,本小册的内容也尤为适合。欢迎新老读者继续支持我们的原创内容。

7 |

「H5 开发工程师」和「UI 开发工程师」的岗位定义及职责要求其实十分相近,本小册将统一使用「H5 开发」来代表它们,并尝试为大家解答这样一个问题:

8 |
9 |

我拿出什么样的作品(或能力),才能真正满足「H5 开发」相关岗位的要求,使得我去面试的时候,至少技术面(专业能力)是完全过关的?

10 |
11 |

所以,本小册的核心内容将会是:

12 | 16 |

作者自荐

17 |

小册不是教大家从零学会网页开发,要求读者至少具备以下能力:

18 | 23 |

名人推荐

24 |

名人1

25 |

26 |

你会学到什么?

27 |

了解怎样成为大厂需要的「H5 开发工程师」或「UI 开发工程师」。

28 | 38 |

适宜人群

39 | 44 |

购买须知

45 |
    46 |
  1. 本小册为图文形式内容服务,共计 11 节,上线时间为 2018 年 3 月 15 日;
  2. 47 |
  3. 购买用户可享有小册永久的阅读权限;
  4. 48 |
  5. 购买用户可进入小册微信群,与作者互动;
  6. 49 |
  7. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  8. 50 |
  9. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  10. 51 |
  11. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  12. 52 |
53 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5c70dbbe51882562046911bc.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

TCP 协议是我一直很想写的一个主题,因为 TCP 学起来实在是太痛苦了。刚参加工作时,TCP 协议一直是一个心头痛,知道皮毛,但是始终无法深入。在阅读了大量相关的书籍、做了很多网络编程方面的工作以后觉得掌握的过程太过曲折,这本小册并不是想重复阐述一些书上都能找到的 TCP 的理论,更多的是想跟大家分享一些学习和探究的方法,授之以渔,让大家少走一些弯路,知道从哪些方面下手来学习和分析问题。

3 |

写这本小册所花的精力比上一本 JVM 字节码多了很多,越写发现水越深,想说服自己实在是太难了。有时候找一个问题的原因看 RFC 看到想吐,用 keynote 画了 100 多张图,所以看这本小册会发现图特别多。

4 |

作者介绍

5 |

我是挖坑的张师傅,vim 爱好者。从事 Java 开发六年多,做过安卓、写过 Node,现在深耕后台开发,在 CVTE 担任高级技术经理,带一个 20 多人的开发团队做教育相关的产品。

6 |

维护了一个微信公众号「张师傅的博客」,主要写偏底层原理的分布式理论、网络协议、架构设计、Go 语言汇编相关的东西,随缘。

7 |

2010 年大三的时候,阴差阳错学习了当时还是 1.5 版本的安卓开发。恰逢 Google 在大陆举办了首届 Android 应用开发中国大学生挑战赛。当时穷学生买不起安卓真机,在模拟器上运行写了一个界面比较炫酷的音乐播放器就去参赛了,结果拿了个三等奖,也是湖北赛区唯一获奖的作品。奖品是一部 HTC G7 手机和几千块的奖金,拿奖以后就应邀去参观了 Google 上海的办公室,安排我们在上海浪了三天。

8 |

毕业以后就真的去做了安卓开发,做了大半年的安卓 framework 移植和手写笔记软件,期间大量接触 linuxvimgit 等工具。

9 |

后面的一年多就是我最高速成长的一段时期了,内部转岗去做了后台开发。因为喜欢保持简单,自己花了一些时间造了很多轮子,比如 REST 服务端框架、数据库连接池、SQL 执行框架、类 Dubbo 的 RPC 调用框架、基于 RocksDB 的延时消息队列、Gossip 协议框架等。

10 |

那段时间遇到了非常多网络相关的问题,于是对 TCP 协议做了深入的学习,现在条件反射一上来就可以 tcpdump 抓包开始分析问题了。当组员遇到问题,三下五除二帮忙找到根本原因的感觉真不错。

11 |

为什么网络协议如此重要?

12 |

之前腾讯过来的带我们部门的后端大牛 ernest 就说过一句话我觉得很经典:「我们做的事情说到底无非是给用户交付一个网络包,你有什么理由不学好呢?」。

13 |

识别一个码农能力的好坏很大程度上是取决于他解决问题的能力。

14 |

我们面对的很多系统都是黑盒系统(MySQL、Nginx 等),几乎每隔一段时间都会遇到网络相关的问题,Redis 连接超时、请求第三方平台连接被重置、系统的吞吐量上不去等。我们没有精力去把每个组件的源码都读通读透,如果不懂网络协议,尤其是 TCP 协议,将无从下手分析。遇到第三方服务有问题时,没有一些抓包现场的分析证据,对方是不会接锅的,只有无休止的扯皮。

15 |

如果你想成为技术牛人,网络协议绝对是一道很高的门槛。 16 | 随着开发年限的增加,越发觉得计算机底层的原理才是最有价值的。技术浪潮一波接着一波,只有掌握了操作系统、计算机网络、分布式理论等基础知识,才能在浪潮之巅游刃有余。

17 |

纸上得来终觉浅,绝知此事要躬行

18 |

TCP 协议的学习非常枯燥,尤其大部分资料都是讲原理没有什么实操的东西。如果平时工作只是写写业务代码很少有机会去深入理解 TCP 协议,也不知道学了 TCP 协议以后可以用在什么地方。所以我想写这本小册,大部分理论相关的内容我都尽量会 packetdrill、scapy、防火墙等工具构造实验可以抓包查看实际的效果,真正可以看到超时重传、零窗口探测、Nagle 算法等实际的效果。

19 |

这本小册并不是为了单纯介绍一些协议的理论知识,这些《TCP/IP 详解》这本书已经足够好了,更多的是想授之以渔,跟大家一起分享自己一些思路和方法,如何去研究 TCP 中的一些复杂的问题,相信掌握了这些方法和工具,一定可以助力你后续的技术提升。

20 |

你会学到什么

21 | 42 |

我列了一个这本小册的思维导图

43 |

44 |

适宜人群

45 | 52 |

阅读这本小册需要哪些必备条件

53 | 57 |

购买须知

58 |
    59 |
  1. 本小册为图文形式内容服务,共计 34 节;
  2. 60 |
  3. 全部文章预计 6 月 30 日更新完成;
  4. 61 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 62 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 63 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 64 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 65 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 66 |
67 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5c70fc83518825428d7f9dfb.html: -------------------------------------------------------------------------------- 1 |

作者介绍

2 |

3 |

修言,《前端性能优化原理与实践》小册作者。阿里巴巴集团前端工程师。历任创业团队高级工程师、滴滴出行前端工程师。

4 |

始终战斗在前端工程化、性能优化的第一线,拥有丰富的研发经验、面试经验和性能死磕经验。

5 |

小册介绍

6 |

烹饪有菜谱,游戏有攻略,每个领域都存在一些能够让我们又好又快地达成目标的“套路”。在程序世界,编程的“套路”就是设计模式

7 |

设计模式是“拿来主义”在软件领域的贯彻实践。和很多人的主观臆断相反,设计模式不是一堆空空如也、晦涩鸡肋的理论,它是一套现成的工具 —— 就好像你想要做饭的时候,会拿起厨具直接烹饪,而不会自己去铸一口锅、磨一把菜刀一样。

8 |

随着前端应用复杂度的日新月异,如今的前端应用已经妥妥地成为了软件思想的一种载体,而前端工程师,也被要求在掌握多重专业技能之余,具备最基本的软件理论知识。同时,工程师对设计模式的掌握程度,一定程度上反映着他用健壮的代码去解决具体的问题的能力。因此,设计模式如今已经成为前端面试中无法回避、同时具有较高候选人区分度的一个核心考点

9 |

基于面试、工作的双重需要,相信很多同学不止一次地向设计模式发起过挑战、并草草收场——觉得设计模式难学,是一件非常正常的事情。设计模式的“难”,在于其令人望而生畏的抽象性和知识点的分散性。这带来了本册要着重解决的问题——帮助大家摆脱枯燥乏味的技术恐惧感,最大程度上降低前端设计模式的学习成本。

10 |

本书经过近一年时间的打磨,在编写过程中力求内容的可读性、趣味性和时效性。

11 |

书中的每一个设计模式,都有它自己的一个“故事”,有它自己的场景。经过我们近20个小节的学习,相信大家会有一个非常惊喜的发现:其实设计模式并不高大上,它是一个非常接地气、非常实际、非常好理解的东西——因为它本身就是一帮非常苦逼的程序员在自己的职业生涯里实打实地踩坑踩出来的。本书的重点不在于对固有理论的反复阐述,而是把读者放到一个正确的场景里、去体会每一个设计模式的好。甚至贯穿设计模式始终的设计原则理论,也会被我们化解到具体的、易于理解的场景片段里去。本书具体的知识结构用思维导图展示如下:

12 |

13 |

“橘生淮南则为橘,橘生淮北则为枳”——一些在服务端应用场景下看似合理、好用又酷炫的操作,生搬硬套到前端的场景里可能就会弄巧成拙。本书的目的并不是做传统设计模式书籍的“译本”,而是面向前端工程师,讲有利于前端的技术。因此在正式的实战章节里,我们权衡每种模式对前端的价值、对 23 种设计模式做了取舍,保留下来的这些设计模式,具备这两个共性:

14 | 18 |

此外,设计模式中有几个特别重要、特别好使、特别受面试官关注的的,我们在讲解的过程中会有针对性地穿插一些高频面试真题(注意面试题不一定会单开小节,有的面试题就穿插于原理讲解之中~)。具体是哪几个,可能要等大家读到了那一节才知道了哈哈(所以不要随便跳读:))。

19 |

你会学到什么?

20 |

通过对本书的学习,我们至少可以达到三个目的

21 | 28 |

适宜人群

29 | 32 |

购买须知

33 |
    34 |
  1. 本小册为图文形式内容服务,共计 18 节;
  2. 35 |
  3. 全部文章预计 9 月 25 日更新完成;
  4. 36 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 37 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 38 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 39 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 40 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 41 |
42 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5d9ea8fff265da5b81794756.html: -------------------------------------------------------------------------------- 1 |

作者介绍

2 |

周兴博,互联网行业十年从业者,出于对全栈工程师头衔的渴望,从事过多种技术岗位,信息安全、基础网络服务、服务端架构、机器学习、NLP等,积累了丰富的经验,对各种技术有着深刻理解,熟练使用Python,Golang,Java等开发语言,在亿级日活的互联网公司设计并开发过搜索系统、推荐系统。

3 |

小册介绍

4 |

随着人工智能技术的发展,越来越多的互联网公司开始重视机器学习在其业务中的价值。就拿近些年大火的字节跳动来说,其旗下的今日头条、抖音、西瓜视频,都是依靠着其强大的智能推荐系统,将海量的内容以个性化的方式分发给用户,最终赢得了大量的忠实用户,这背后自然是离不开NLP的功劳。

5 |

NLP是Natural Language Processing的缩写,中文意思是自然语言处理,是人工智能的一个子领域。由于深度学习的发展,机器在图像识别、语音识别方面已经取得了巨大的进步,达到了惊人的准确率。而让机器能够理解人类语言的这个问题上,还有很大的发展空间,所以掌握NLP的基础知识就非常有必要。

6 |

7 |

在中文的语言环境里,中文分词是NLP最基础,也是最核心的问题,几乎影响着所有NLP任务最后结果的质量。想必做过全文搜索、关键词挖掘的同学,都切身的感受过中文分词那满满的恶意,大量的专有名词切分不出来,或者切分出来很多的歧义词。这时候只能是无奈的手动一个一个往分词词典里添加或者调整词频,但这样不仅耗时耗力,而且治标不治本,无法跟上业务的发展速度。

8 |

本小册的核心是希望通过深入讲解中文分词的原理及实践经验,来帮助大家彻底掌握中文分词技术,以便能够灵活应对各种分词难题。同时中文分词背后所涉及到的算法和模型,都是机器学习最重要的基础,有助于大家从事其它机器学习的相关工作。

9 |

你会学到什么?

10 |

中文分词经历了20多年的发展,从最开始的蛮荒阶段,到现在的趋于成熟,过程中克服了种种艰难险阻,产生了大量思想和方法,我会取其精华,并深入浅出地讲述给大家,大家会学到:

11 |
    12 |
  1. 少量的中文语言学,帮助理解中文分词所要面对的难题
  2. 13 |
  3. Python的编程知识,用于算法的实践和开源项目的源码讲解
  4. 14 |
  5. 统计学和信息论,中文分词的算法里使用了很多这两方面的知识
  6. 15 |
  7. 机器学习知识,主要涉及HMM、CRF、Word2vec等模型
  8. 16 |
  9. 业务案例分析,结合实际业务场景,介绍各种解决方案
  10. 17 |
18 |

适宜人群

19 |
    20 |
  1. 渴望从事NLP相关工作的初学者
  2. 21 |
  3. 希望提高NLP基础理论知识和实践技巧的初中级开发者
  4. 22 |
  5. 对机器学习、NLP等技术感兴趣的爱好者
  6. 23 |
24 |

购买须知

25 |
    26 |
  1. 本小册为图文形式内容服务,共计 20 节;
  2. 27 |
  3. 全部文章预计 11 月 10 日更新完成;
  4. 28 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 29 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 30 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 31 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 32 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 33 |
34 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5da08714518825520e6bb810.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

如今越来越多的大型项目和团队选择了 TypeScript 作为主力开发语言,GitHub 发布 2018 编程语言 TOP10 中,TypeScript 在开源贡献者使用最多的语言中排名跃居第七。

3 |

2019-10-21-16-12-53

4 |

而仅仅在一年之前,TypeScript 还未能进入前十,可见其近些年的发展势头之猛。

5 |

而最新开源的 Vue3 Pre-Alpha 版代码就是采用 TypeScript 重写而成。

6 |

2019-10-08-22-14-01

7 |

除此之外,在前端开发群体中最受欢迎的编辑器之一 VS Code 就是基于 TypeScript 开发,三大框架之一的 Angular、著名后端框架 NestJS、机器学习框架 tfjs 都是基于 TypeScript 开发。

8 |

我们可以想象 Vue3.0 正式发布之后整个大型项目的生态会继续向 TypeScript 倾斜,可以说学习 TypeScript 已经是不可逆的趋势,但是与此同时我们发现在学习 TypeScript 过程中会踩到很多坑。

9 |

笔者当初学习 TypeScript 时就饱受折磨:

10 | 15 |

由于以上的种种问题,促成了本册子的诞生,笔者用44节超过5万字循序渐进地把 TypeScript 学习入门到进阶做了一个梳理,并结合实战、甚至拿出一节解析了一个有一定难度的面试题,在本册子里笔者力求把当初学习遇到的困难点都写出来,帮助大家少走弯路,更快地成为一个合格的 TypeScript 开发者。

16 |

在我心中 TypeScript 的开发者是分四个层级的:

17 |
    18 |
  1. 业务使用者: 这个层级的开发者可以在业务代码中熟练利用 TypeScript 编码,但是无法进行类型编程,也无法写出一些底层库,仅仅停留在使用阶段
  2. 19 |
  3. 类型编程者: 这个层级的开发者可以对类型进行编程,可以开发出一些实用的工具类型,对于难以定义的类型也能驾轻就熟,在语言的使用层面不会再碰到太多棘手的问题了
  4. 20 |
  5. TS 定制者: 这个层级的开发者对 TypeScript 的类型系统比较熟悉,对 TypeScript 的语言设计也有一定的认知,可以开发 TypeScript Transformer Plugin 来定制化开发 TypeScript
  6. 21 |
  7. TS 设计者: 这个层面的开发者可以参与到 TypeScript 这门语言的设计中去,基本上能达到 PL 领域的从业人员的水准
  8. 22 |
23 |

本册子力力求能让读者读完后能达到第二个层级以上的水平,再往后的话我跟大家共勉...

24 |

我将小册相关的知识点制作成了脑图如下:

25 |

2019-10-21-14-54-30

26 |

作者介绍

27 |

寻找海蓝,前端工程师,《前端面试与进阶指南》开源书(2k star)作者,微信公众号「程序员面试官」作者。

28 |

TypeScript重度使用者,用TypeScript构建过多个中大型项目,有丰富的一线实战经验。

29 |

你会学到什么?

30 | 37 |

适宜人群

38 | 42 |

购买须知

43 |
    44 |
  1. 本小册为图文形式内容服务,共计 44 节;
  2. 45 |
  3. 全部文章预计 10 月 28 日更新完成;
  4. 46 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 47 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 48 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 49 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 50 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 51 |
52 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5da3bc3d6fb9a04e35597a76.html: -------------------------------------------------------------------------------- 1 |

作者介绍

2 |

LinkedBear:前浪潮集团后端工程师,乐忠于读源码和技术分享。

3 |

小册介绍

4 |

嘿伙计,你用过 Spring 和 SpringBoot 吗?是不是感觉Spring真是个强大的框架, SpringBoot 又让 Spring 更加牛批了呢?我想这个大家也都这么认为吧!SpringBoot 在当下的 Java 后端开发中已经相当流行,非常多的公司和开发团队都选用 SpringBoot 作为快速构建项目的打底框架,究其原因你我都清楚,它方便简单,而且注解和编程式配置都让我们觉得更加简单、容易理解和维护。可是老伙计,你会用 Spring 和 SpringBoot,你是否曾想过这样一些问题呢:

5 | 12 |

正如你的这些问题所想, SpringBoot 用的人多,但懂其原理的人说实话不多,能深入源码探寻最底层的人更是少之又少。(诶伙计别跑啊,等我说完。。。)

13 |

为了让更多的 “Springer” 能深入的了解 SpringBootSpringFramework 中的一些精髓,我在尝试着将框架中的核心原理系统化的整理,并且尽量的降低阅读和理解的难度,最后编制了这本小册。小册将围绕 SpringFrameworkSpringBoot 的几个核心模块(IOC、AOP、事务、Web、嵌入式容器等)来展开剖析,力求达到足够的深度和尽可能低的理解难度。你也知道,源码的阅读和理解通常都是比较复杂且费力的,我也这么觉得,所以我在试着想一些办法,来尽可能从你熟悉的领域出发,一步步让你走入框架底层,了解它的底层原理和工作机制。相信我老伙计,这本小册不一定把 SpringFrameworkSpringBoot 的所有源码都带你啃一遍,但它的几大核心小册已经都包含了,而且都会尽可能的带你啃的足够深,让你从底层根本了解原理,进而重新认识你面前的这个熟悉的Spring。

14 |

你会学到什么?

15 | 25 |

适宜人群

26 |

小册的内容偏原理和源码,需要有一定的 SpringFrameworkSpringBoot 的基础知识才可以愉快地阅读下去。

27 |

所以这本小册适合:

28 | 34 |

购买须知

35 |
    36 |
  1. 本小册为图文形式内容服务,共计 35 节;
  2. 37 |
  3. 全部文章已在 11 月 11 日更新完成;
  4. 38 |
  5. 购买小册前一定要先阅读“开始前的约定”;
  6. 39 |
  7. 购买用户可享有小册永久的阅读权限;
  8. 40 |
  9. 购买用户可进入小册微信群,与作者互动;
  10. 41 |
  11. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  12. 42 |
  13. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  14. 43 |
  15. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  16. 44 |
45 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5da96626e51d4524ba0fd237.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

本小册为纯实战类型的教程系列,以React全家桶(包含hooks)以及immutable数据流为基础仿网易云音乐UI打造一款高质量的WebApp。

3 |

首先说一说为什么要开始做这个前端的小册教程:

4 |

1、React Hooks如今可谓前端界"当红小生", 因其API简洁性、逻辑复用性等特性逐渐被开发者所应用,vue3.0也是采用类似的Function Based的模式,因此学习React Hooks也是未来的大趋势,通过一个具体的项目来实践、应用hooks特性,我觉得比干啃文档要强太多,并且在实践的过程中会遇到一些坑,通过坑驱动来学习,可以加深我们对于hooks原理的理解。

5 |

2、把Redux和不可变数据结合也是react性能优化的一个重要的手段,通过immutable.js来进行实践一把,能够加深自己对于React性能优化相关知识的理解。

6 |

3、完成这个小册中的项目绝对不仅仅是功能上的实现,我保证整个项目的质量能够对得起”精美“二字,从头到尾不断优化和迭代才得以完成,因此也非常乐意分享给更多在前端路上的同学,也有自信能让大家收获到非常多的知识点和实战经验,帮助大家进阶。

7 |

接下来介绍一下这个项目的技术栈:

8 | 32 |

写作思路:小册的主干部分以不同的模块为线索,一共有推荐模块、歌手列表模块、排行榜模块、歌单详情模块、歌手主页模块、播放器模块和搜索模块构成,中间会穿插一些基础组件的搭建,整个性能及体验的优化过程也会在每个模块开发基本完成之后来进行,因此是一个循序渐进、不断深入的过程。

33 |

主题特点:以实战为线索,逐步深入React开发各个环节,掌握前端常用性能体验优化思路,打造完整前端工作流,提升工程化编码能力和思维能力。

34 |

整体架构:

35 |

36 |

你会学到什么?

37 |

读完小册你会有什么收获:

38 |
    39 |
  1. 熟练使用React Hooks进行业务开发,理解哪些场景产生闭包陷阱,如何避免掉坑。
  2. 40 |
  3. 手写近6000行代码,封装13个基础UI组件、12个业务组件,让你知道关于 React + Redux 业内公认的最佳实践到底是什么样子。
  4. 41 |
  5. 封装常用的移动端组件,实现常见的需求,如封装滚动组件实现图片懒加载实现上拉/下拉刷新的功能、实现防抖功能、实现组件代码分割(CodeSpliting)等。
  6. 42 |
  7. 拥有实现前端复杂交互的实际项目经验,提升自己的内功,比如开发播放器内核就是其中一个很大的挑战。
  8. 43 |
  9. 掌握CSS中的诸多技巧,提升自己的CSS能力,无论布局还是动画,都有相当多的实践和探索,未使用任何UI框架,样式代码独立实现。
  10. 44 |
  11. 彻底理解redux原理,并能够独立开发redux的中间件。
  12. 45 |
46 |

作者介绍

47 |

「前端三元同学」公众号作者,掘金优秀作者,博客关注量过万, 深耕前端领域,并且长期坚持输出优质博客文章,热爱开源,热爱分享。

48 |

适宜人群

49 | 56 |

另外,一些前端知识的基础是需要的,比如HTML、CSS、原生JavaScript的基础。总之,这个小册并不是零基础教程,重点的原理性内容会有所强调,但绝不会有照搬官方文档的重复性内容,保证全程高能实战。当然这里我也没有办法保证项目涉及的技术栈你都有实践过,但这个小册的意义恰恰在于给你一个实际的场景,在一个舒适的环境中完成一个复杂的项目,并在这个过程中知道自己还有哪些不足、理解不透彻的地方,让你更加明确以后努力以及深耕的具体方向,我觉得这是比单纯地做一个项目更加宝贵的东西。

57 |

购买须知

58 |
    59 |
  1. 本小册为图文形式内容服务,共计 34 节;
  2. 60 |
  3. 全部文章预计 11 月 1 日更新完成;
  4. 61 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 62 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 63 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 64 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 65 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 66 |
67 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/5db6b0fa6fb9a020446c5278.html: -------------------------------------------------------------------------------- 1 |

小册介绍

2 |

Swift语言作为苹果的亲生儿子这些年的发展是趋势是逐步向上的,在2019的WWDC上,苹果又为其发布了最新的UI框架SwiftUI。

3 |

很多朋友可能就可能就会存在以下疑问:

4 | 10 |

本课程主要为大家讲解苹果为开发者带来的全新的SwiftUI的开发框架,全新的声明式语法,跨平台的开发能力以及实施的预览功能是这个框架的主要特性。在本课程中,先为大家介绍SwiftUI中的各种特性以及使用方法,然后为大家演示如何从零开始使用SwiftUI开发一个应用程序。

11 |

在整个过程中穿插了大量的demo示例,整体图文经过精心编排,保证了阅读的质量和体验

12 |

13 |

你会学到什么?

14 |

通过整个内容的学习,将会收获到以下几点干货:

15 |
    16 |
  1. 17 |

    入门整个SwiftUI框架,了解布局、状态绑定、数据流、动画等特性。

    18 |
  2. 19 |
  3. 20 |

    手把手开发一个demo应用

    21 |

    包括绘制各个View,暗色模式,美化界面,动画效果,数据存储和网络请求等几部分内容

    22 |
  4. 23 |
  5. 24 |

    开发时提升效率的小技巧

    25 |
  6. 26 |
27 |

适宜人群

28 |

对iOS开发感兴趣的入门初学者;有其他语言基础的开发者;想要学习swiftUI开发项目的开发者

29 |

购买须知

30 |
    31 |
  1. 本小册为图文形式内容服务,共计 16 节;
  2. 32 |
  3. 全部文章预计 11 月 25 日更新完成;
  4. 33 |
  5. 购买用户可享有小册永久的阅读权限;
  6. 34 |
  7. 购买用户可进入小册微信群,与作者互动;
  8. 35 |
  9. 掘金小册为虚拟内容服务,一经购买成功概不退款;
  10. 36 |
  11. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  12. 37 |
  13. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  14. 38 |
39 | -------------------------------------------------------------------------------- /src/ssr-mock-data/db/db.json: -------------------------------------------------------------------------------- 1 | [{"des":"职业上的错误是不能回滚的,将陪伴你一生,帮助每一位程序员规划自己的职业生涯","href":"https://juejin.im/book/59e17a7ff265da430629cc4e","id":"59e17a7ff265da430629cc4e","pic":"https://user-gold-cdn.xitu.io/2017/11/20/15fd7933f4f4ed03?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"程序员职业小白书 —— 如何规划和经营你的职业"},{"des":"用实验和图解的方式带你深入理解 TCP 协议,让 TCP 协议不再是拦路虎","href":"https://juejin.im/book/5c70dbbe51882562046911bc","id":"5c70dbbe51882562046911bc","pic":"https://user-gold-cdn.xitu.io/2019/6/12/16b49d5cb7263893?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"深入理解 TCP 协议:从原理到实战"},{"des":"从熟悉的场景逐步深入源码底层,理解SpringBoot的设计和原理。","href":"https://juejin.im/book/5da3bc3d6fb9a04e35597a76","id":"5da3bc3d6fb9a04e35597a76","pic":"https://user-gold-cdn.xitu.io/2019/11/11/16e59b3002fb97e0?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"SpringBoot 源码解读与原理分析"},{"des":"授人以鱼不如授人以渔,从根儿上理解 MySQL,让 MySQL 不再是一个黑盒。","href":"https://juejin.im/book/5bffcbc9f265da614b11b731","id":"5bffcbc9f265da614b11b731","pic":"https://user-gold-cdn.xitu.io/2019/2/27/1692d9b999203c38?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"MySQL 是怎样运行的:从根儿上理解 MySQL"},{"des":"以实战为线索,逐步深入React开发各个环节,掌握前端常用性能体验优化思路,打造完整前端工作流,提升工程化编码能力和思维能力。","href":"https://juejin.im/book/5da96626e51d4524ba0fd237","id":"5da96626e51d4524ba0fd237","pic":"https://user-gold-cdn.xitu.io/2019/12/13/16efe7ec78b36ff7?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"React Hooks 与 Immutable 数据流实战"},{"des":"通俗易懂的编程“套路“学。带你深入看似高深实则接地气的设计模式原理,在实际场景中内化设计模式的”道“与”术“。学会驾驭代码,而非被其奴役。","href":"https://juejin.im/book/5c70fc83518825428d7f9dfb","id":"5c70fc83518825428d7f9dfb","pic":"https://user-gold-cdn.xitu.io/2019/9/16/16d382e623923d91?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"JavaScript 设计模式核⼼原理与应⽤实践"},{"des":"SwiftUI是苹果在2019年WWDC发布的全新UI框架,拥有声明式语法,跨平台开发以及实时预览的特性。本小册主要内容以SwiftUI的入门开发为主。适宜零基础入门iOS开发,内含一个的项目的开发教程,向大家讲解swiftUI的页面布局,状态绑定,数据流等内容。","href":"https://juejin.im/book/5db6b0fa6fb9a020446c5278","id":"5db6b0fa6fb9a020446c5278","pic":"https://user-gold-cdn.xitu.io/2019/11/8/16e4a5eaadd0ee9c?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"SwiftUI实战,带你入门苹果最新的UI开发框架"},{"des":"Spring Boot 技术栈真实应用案例","href":"https://juejin.im/book/5da2f9d4f265da5b81794d48","id":"5da2f9d4f265da5b81794d48","pic":"https://user-gold-cdn.xitu.io/2019/11/11/16e5857b0c541381?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"预售"},{"des":"Vue3 源码及开发必备基础,从基础知识到类型工具设计,从理论到实战,手把手让你从零基础成为进阶使用者。","href":"https://juejin.im/book/5da08714518825520e6bb810","id":"5da08714518825520e6bb810","pic":"https://user-gold-cdn.xitu.io/2019/11/8/16e4ab5d6aff406a?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"深入浅出TypeScript:从基础知识到类型编程"},{"des":"在中文的语言环境里,中文分词是NLP基础中的基础,核心中的核心,直接影响着各种NLP任务最后的结果质量。","href":"https://juejin.im/book/5d9ea8fff265da5b81794756","id":"5d9ea8fff265da5b81794756","pic":"https://user-gold-cdn.xitu.io/2019/11/8/16e4a65bafa2c3e5?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"深入理解NLP的中文分词:从原理到实践"},{"des":"深入了解区块链、挖矿、钱包、签名等技术原理,对未来的数字货币世界做好准备","href":"https://juejin.im/book/59f954546fb9a04525776d7c","id":"59f954546fb9a04525776d7c","pic":"https://user-gold-cdn.xitu.io/2017/11/20/15fd79417e955d9a?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"数字货币与区块链原理"},{"des":"研究响应式编程,探讨 ReactiveX 的底层概念和 RxJava 的高阶问题","href":"https://juejin.im/book/5a09997cf265da432002bf6a","id":"5a09997cf265da432002bf6a","pic":"https://user-gold-cdn.xitu.io/2017/12/13/1604f3080efffc24?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"响应式编程 —— RxJava 高阶指南"},{"des":"从零开始学习 Canvas 相关知识,分析其特效,最终制作出炫酷的网页背景","href":"https://juejin.im/book/5a0ab8e2f265da43111fbab2","id":"5a0ab8e2f265da43111fbab2","pic":"https://user-gold-cdn.xitu.io/2017/11/20/15fd79563b28dd6e?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"如何使用 Canvas 制作出炫酷的网页背景特效"},{"des":"抛弃笨重的构建工具,拥抱轻巧而不失强大的 npm\n script,随小册赠送视频版教程。","href":"https://juejin.im/book/5a1212bc51882531ea64df07","id":"5a1212bc51882531ea64df07","pic":"https://user-gold-cdn.xitu.io/2017/11/20/15fd699517c3c6a4?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"用 npm script 打造超溜的前端工作流"},{"des":"让你不仅用上、更用明白的 Git 实用指南","href":"https://juejin.im/book/5a124b29f265da431d3c472e","id":"5a124b29f265da431d3c472e","pic":"https://user-gold-cdn.xitu.io/2017/11/27/15ffbb05174a57f8?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"Git 原理详解及实用指南"},{"des":"爬虫基本原理、使用、抓包分析、存储数据、分析数据、数据可视化","href":"https://juejin.im/book/5a157c155188254a701eb3c1","id":"5a157c155188254a701eb3c1","pic":"https://user-gold-cdn.xitu.io/2017/12/18/160691fc2995dbf9?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"基于 Python 实现微信公众号爬虫"},{"des":"深入浅出地带你领略 Laravel 框架的设计模式美学,一本你不可或缺的设计模式范本","href":"https://juejin.im/book/5a261d8f5188253ee45b4b21","id":"5a261d8f5188253ee45b4b21","pic":"https://user-gold-cdn.xitu.io/2017/12/19/1606e20aea39c319?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"详解 Laravel 源码中优秀的设计模式"},{"des":"把原理抽象为小 Demo,以一种对新手友好的方式带领读者漫游 Vue.js 的世界","href":"https://juejin.im/book/5a36661851882538e2259c0f","id":"5a36661851882538e2259c0f","pic":"https://user-gold-cdn.xitu.io/2018/1/16/160fdc404b36a1a0?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"剖析 Vue.js 内部运行机制"},{"des":"基于 4.x 版本,从细节和深度上弄懂 webpack,随心所欲定制前端开发环境","href":"https://juejin.im/book/5a6abad5518825733c144469","id":"5a6abad5518825733c144469","pic":"https://user-gold-cdn.xitu.io/2018/3/2/161e5a0aebdab5ed?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"使用 webpack 定制前端开发环境"},{"des":"京东、腾讯等大厂 H5 开发或 UI 开发工程师的真实实战技巧","href":"https://juejin.im/book/5a7bfe595188257a7349b52a","id":"5a7bfe595188257a7349b52a","pic":"https://user-gold-cdn.xitu.io/2018/5/9/16342f9666cf9b8f?imageView2/1/w/200/h/280/q/95/format/webp/interlace/1","title":"大厂 H5 开发实战手册"}] -------------------------------------------------------------------------------- /src/ssr-mock-data/demo.js: -------------------------------------------------------------------------------- 1 | const Nightmare = require('nightmare') 2 | const nightmare = Nightmare({ show: false }) 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const spwan = require('child_process').spawnSync; 6 | 7 | async function run() { 8 | 9 | let dt = await nightmare 10 | .goto('https://juejin.im/books') 11 | .evaluate(() => { 12 | 13 | var list = document.querySelectorAll('.list-wrap .item'); 14 | var obj={},last=[]; 15 | 16 | for(let item of list){ 17 | 18 | last.push({ 19 | title:item.querySelector('.title span').textContent, 20 | des:item.querySelector('.desc').textContent, 21 | pic: item.querySelector('.poster div').getAttribute('data-src'), 22 | href: 'https://juejin.im'+item.getAttribute('href'), 23 | id: item.getAttribute('href').split('/')[2] 24 | }) 25 | } 26 | 27 | return last; 28 | 29 | }) 30 | .end() 31 | .then((res)=>{ 32 | fs.writeFile(path.resolve('./db/db.json'), JSON.stringify(res),(error)=>{ 33 | if(!error)console.log('write ok'); 34 | }); 35 | 36 | return res; 37 | }) 38 | .catch(error => { 39 | console.error('Search failed:', error) 40 | }); 41 | 42 | 43 | 44 | //采集页面 45 | for (var i = 0; i < dt.length; i++) { 46 | var item = dt[i]; 47 | spwan('node', ['./child.js', item.href, item.id], { stdio: 'inherit' }); 48 | } 49 | } 50 | 51 | 52 | run(); -------------------------------------------------------------------------------- /src/ssr-mock-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr-mock-data", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "nightmare": "^3.0.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ssr-mock-data/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var pathSys = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | 16 | app.use(koaBody({ 17 | })); 18 | 19 | const list = require('../db/db.json'); 20 | 21 | app.use(async function (ctx) { 22 | const path = ctx.path; 23 | console.log(path); 24 | const data={ 25 | code:0, 26 | data:{} 27 | } 28 | if(path==='/list'){ 29 | data.data = list; 30 | } else if (path.indexOf('/detail/')>-1){ 31 | let id = path.split('/')[2]; 32 | if (fs.existsSync(pathSys.resolve('./db/'+id+'.html'))){ 33 | data.html = fs.readFileSync(pathSys.resolve(`./db/${id}.html`)).toString(); 34 | } 35 | } 36 | ctx.body = data; 37 | 38 | }); 39 | 40 | /** 41 | * Create HTTP server. 42 | */ 43 | var server = http.createServer(app.callback()); 44 | server.listen(port); 45 | console.log(' 8100 server start ...... '); -------------------------------------------------------------------------------- /src/ssr-mock-data/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | formdata解析 8 | 9 | 10 |

手动解析 formdata

11 |
12 | 13 | 选择文件(可多选): 14 |

15 | 16 | 标题:


17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/aa.js -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_5cb411292d060f7c1beb5013d213aebf.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_5cb411292d060f7c1beb5013d213aebf.xls -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_72e5851d22c2658deff9dbdbc31fba6a.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_72e5851d22c2658deff9dbdbc31fba6a.xls -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_7a57454a6d3dc77b6fefd137112816ea.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_7a57454a6d3dc77b6fefd137112816ea.xls -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_7e43363a259814b9c078ca664166a0fb.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_7e43363a259814b9c078ca664166a0fb.xls -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_b2afbf318a199a9d56ea2e64658e8929.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_b2afbf318a199a9d56ea2e64658e8929.xls -------------------------------------------------------------------------------- /src/ssr-mock-data/static/uploads/upload_fb5aa88dbb158d66d35a820ec113d538.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/ssr-mock-data/static/uploads/upload_fb5aa88dbb158d66d35a820ec113d538.xls -------------------------------------------------------------------------------- /src/upfiles-demo/README.md: -------------------------------------------------------------------------------- 1 | # 前端各种场景文件上传代码实现 demo 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/upfiles-demo/api-upfile/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost = `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody({ 17 | formidable: { 18 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 os 19 | uploadDir: path.resolve(__dirname, '../static/uploads') 20 | }, 21 | multipart: true // 支持文件上传 22 | })); 23 | 24 | //开启静态文件访问 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | 30 | 31 | //二次处理文件,修改名称 32 | app.use((ctx) => { 33 | if (ctx.path === '/upfile') { 34 | var file = ctx.request.files ? ctx.request.files.f1 : null; //得到文件对象 35 | if (file) { 36 | 37 | var path = file.path.replace(/\\/g, '/'); 38 | var fname = file.name; //原文件名称 39 | var nextPath = ''; 40 | if (file.size > 0 && path) { 41 | //得到扩展名 42 | var extArr = fname.split('.'); 43 | var ext = extArr[extArr.length - 1]; 44 | nextPath = path + '.' + ext; 45 | //重命名文件 46 | fs.renameSync(path, nextPath); 47 | } 48 | //以 json 形式输出上传文件地址 49 | ctx.body = getRenderData({ 50 | data: `${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/') + 1)}` 51 | }); 52 | }else { 53 | ctx.body = getRenderData({ 54 | code:1, 55 | msg:'file is null' 56 | }); 57 | } 58 | 59 | } 60 | 61 | }); 62 | 63 | /** 64 | * 65 | * @param {设置返回结果} opt 66 | */ 67 | function getRenderData(opt) { 68 | return Object.assign({ 69 | code:0, 70 | msg:'', 71 | data:null 72 | },opt); 73 | } 74 | 75 | /** 76 | * http server 77 | */ 78 | var server = http.createServer(app.callback()); 79 | server.listen(port); 80 | console.log('upload file server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/api-upfile/static/html/t.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | cccc 40 | 41 |
42 | 43 |
44 | content....
45 | cccc 46 | 47 |
48 | 49 | -------------------------------------------------------------------------------- /src/upfiles-demo/api-upfile/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

最原始的上传文件操作 - 单文件上传

11 |

使用 form 表单直接请求,不用 js,页面会刷新

12 | 13 | 14 |
15 | 16 | 17 |
input 必须设置 name 属性,否则数据无法发送
18 |
19 | 标题:


20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/upfiles-demo/api-upfile/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/api-upfile/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/api-upfile/static/uploads/upload_e918b11c7fd084888c3ac089680589a8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/api-upfile/static/uploads/upload_e918b11c7fd084888c3ac089680589a8.jpg -------------------------------------------------------------------------------- /src/upfiles-demo/demo1/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | var app = new Koa(); 12 | var port = process.env.PORT || '8100'; 13 | 14 | var uploadHost = `http://localhost:${port}/uploads/`; 15 | 16 | app.use(koaBody({ 17 | formidable: { 18 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 os 19 | uploadDir: path.resolve(__dirname, '../static/uploads') 20 | }, 21 | multipart: true // 支持文件上传 22 | })); 23 | 24 | //开启静态文件访问 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | var file = ctx.request.files ? ctx.request.files.f1 : null; //得道文件对象 32 | if (file) { 33 | 34 | var path = file.path.replace(/\\/g, '/'); 35 | var fname = file.name; //原文件名称 36 | var nextPath = ''; 37 | if (file.size > 0 && path) { 38 | //得到扩展名 39 | var extArr = fname.split('.'); 40 | var ext = extArr[extArr.length - 1]; 41 | nextPath = path + '.' + ext; 42 | //重命名文件 43 | fs.renameSync(path, nextPath); 44 | } 45 | //以 json 形式输出上传文件地址 46 | ctx.body = `{ 47 | "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/') + 1)}" 48 | }`; 49 | } else { 50 | ctx.body = 'null'; 51 | } 52 | 53 | 54 | }); 55 | 56 | /** 57 | * http server 58 | */ 59 | var server = http.createServer(app.callback()); 60 | server.listen(port); 61 | console.log('demo1 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo1/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

最原始的上传文件操作 - 单文件上传

11 |

使用 form 表单直接请求,不用 js,页面会刷新

12 | 13 | 14 |
15 | 16 | 17 |
input 必须设置 name 属性,否则数据无法发送
18 |
19 | 标题:


20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo1/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo1/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo10/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo10 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo10/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 图片上传前的一些判断 8 | 15 | 16 | 17 |

图片上传前的一些判断

18 |

19 | 1. 判断文件类型 如:必须为 jpg 图片
2.判断文件大小 size 如:不能>100kb
3. 判断图片尺寸 如:不能 >100*90 20 |

21 |
22 | 23 | 选择文件(可多选): 24 |

25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo10/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo10/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo11/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //允许跨域 30 | app.use(async (ctx, next) => { 31 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 32 | ctx.set("Access-Control-Max-Age", 864000); 33 | // 设置所允许的HTTP请求方法 34 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); 35 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. 36 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); 37 | 38 | await next(); 39 | }) 40 | 41 | //二次处理文件,修改名称 42 | app.use((ctx) => { 43 | console.log(ctx.request.files); 44 | 45 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 46 | var result=[]; 47 | console.log(files); 48 | 49 | if(!Array.isArray(files)){//单文件上传容错 50 | files=[files]; 51 | } 52 | 53 | files && files.forEach(item=>{ 54 | var path = item.path.replace(/\\/g, '/'); 55 | var fname = item.name;//原文件名称 56 | var nextPath = path + fname; 57 | if (item.size > 0 && path) { 58 | //得到扩展名 59 | var extArr = fname.split('.'); 60 | var ext = extArr[extArr.length - 1]; 61 | var nextPath = path + '.' + ext; 62 | //重命名文件 63 | fs.renameSync(path, nextPath); 64 | 65 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 66 | } 67 | }); 68 | 69 | 70 | ctx.body = `{ 71 | "fileUrl":${JSON.stringify(result)} 72 | }`; 73 | }); 74 | 75 | 76 | 77 | 78 | /** 79 | * Create HTTP server. 80 | */ 81 | var server = http.createServer(app.callback()); 82 | server.listen(port); 83 | console.log('demo11 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo11/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 多文件上传,多进度条 8 | 73 | 74 | 75 |

多文件上传 之预览 + 多进度条 progress

76 |
77 | 选择文件(可多选): 78 |
添加文件 79 | 80 |
81 |
82 | 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo11/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo11/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo12/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //允许跨域 30 | app.use(async (ctx, next) => { 31 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 32 | ctx.set("Access-Control-Max-Age", 864000); 33 | // 设置所允许的HTTP请求方法 34 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); 35 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. 36 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); 37 | 38 | await next(); 39 | }) 40 | 41 | //二次处理文件,修改名称 42 | app.use((ctx) => { 43 | console.log(ctx.request.files); 44 | var body = ctx.request.body; 45 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 46 | var result=[]; 47 | var fileToken = ctx.request.body.token;// 文件标识 48 | var fileIndex=ctx.request.body.index;//文件顺序 49 | 50 | console.log('files'); 51 | console.log(files); 52 | 53 | if(files && !Array.isArray(files)){//单文件上传容错 54 | files=[files]; 55 | } 56 | 57 | files && files.forEach(item=>{ 58 | var path = item.path.replace(/\\/g, '/'); 59 | var fname = item.name;//原文件名称 60 | var nextPath = path.slice(0, path.lastIndexOf('/') + 1) + fileIndex + '-' + fileToken; 61 | if (item.size > 0 && path) { 62 | //得到扩展名 63 | var extArr = fname.split('.'); 64 | var ext = extArr[extArr.length - 1]; 65 | //var nextPath = path + '.' + ext; 66 | //重命名文件 67 | fs.renameSync(path, nextPath); 68 | 69 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 70 | } 71 | }); 72 | 73 | ctx.body = `{ 74 | "fileUrl":${JSON.stringify(result)} 75 | }`; 76 | 77 | if(body.type==='merge'){ 78 | //合并文件 79 | var filename = body.filename, 80 | chunkCount = body.chunkCount, 81 | folder = path.resolve(__dirname, '../static/uploads')+'/'; 82 | 83 | var writeStream = fs.createWriteStream(`${folder}${filename}`); 84 | 85 | var cindex=0; 86 | //合并文件 87 | function fnMergeFile(){ 88 | var fname = `${folder}${cindex}-${fileToken}`; 89 | var readStream = fs.createReadStream(fname); 90 | readStream.pipe(writeStream, { end: false }); 91 | readStream.on("end", function () { 92 | fs.unlink(fname, function (err) { 93 | if (err) { 94 | throw err; 95 | } 96 | }); 97 | if (cindex+1 < chunkCount){ 98 | cindex += 1; 99 | fnMergeFile(); 100 | } 101 | }); 102 | } 103 | 104 | fnMergeFile(); 105 | 106 | ctx.body='merge ok 200'; 107 | } 108 | 109 | }); 110 | 111 | 112 | 113 | 114 | /** 115 | * Create HTTP server. 116 | */ 117 | var server = http.createServer(app.callback()); 118 | server.listen(port); 119 | console.log('demo12 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo12/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 大文件上传 8 | 30 | 31 | 32 |

大文文件分片上传 之 xhr formdata

33 |
34 | 35 | 选择文件: 36 |

37 | 38 | 39 |
40 | 41 |
42 | 43 | 44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo12/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo12/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo13/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //允许跨域 30 | app.use(async (ctx, next) => { 31 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 32 | ctx.set("Access-Control-Max-Age", 864000); 33 | // 设置所允许的HTTP请求方法 34 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); 35 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. 36 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); 37 | 38 | await next(); 39 | }) 40 | 41 | //二次处理文件,修改名称 42 | app.use((ctx) => { 43 | console.log(ctx.request.files); 44 | var body = ctx.request.body; 45 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 46 | var result=[]; 47 | var fileToken = ctx.request.body.token;// 文件标识 48 | var fileIndex=ctx.request.body.index;//文件顺序 49 | 50 | console.log('files'); 51 | console.log(files); 52 | 53 | if(files && !Array.isArray(files)){//单文件上传容错 54 | files=[files]; 55 | } 56 | 57 | files && files.forEach(item=>{ 58 | var path = item.path.replace(/\\/g, '/'); 59 | var fname = item.name;//原文件名称 60 | var nextPath = path.slice(0, path.lastIndexOf('/') + 1) + fileIndex + '-' + fileToken; 61 | if (item.size > 0 && path) { 62 | //得到扩展名 63 | var extArr = fname.split('.'); 64 | var ext = extArr[extArr.length - 1]; 65 | //var nextPath = path + '.' + ext; 66 | //重命名文件 67 | fs.renameSync(path, nextPath); 68 | 69 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 70 | } 71 | }); 72 | 73 | ctx.body = `{ 74 | "fileUrl":${JSON.stringify(result)} 75 | }`; 76 | 77 | if(body.type==='merge'){ 78 | //合并文件 79 | var filename = body.filename, 80 | chunkCount = body.chunkCount, 81 | folder = path.resolve(__dirname, '../static/uploads')+'/'; 82 | 83 | var writeStream = fs.createWriteStream(`${folder}${filename}`); 84 | 85 | var cindex=0; 86 | //合并文件 87 | function fnMergeFile(){ 88 | var fname = `${folder}${cindex}-${fileToken}`; 89 | var readStream = fs.createReadStream(fname); 90 | 91 | readStream.pipe(writeStream, { end: false }); 92 | readStream.on("end", function () { 93 | fs.unlink(fname, function (err) { 94 | if (err) { 95 | throw err; 96 | } 97 | }); 98 | if (cindex+1 < chunkCount){ 99 | cindex += 1; 100 | fnMergeFile(); 101 | } 102 | }); 103 | } 104 | 105 | try { 106 | fnMergeFile(); 107 | } catch (error) { 108 | 109 | } 110 | 111 | 112 | ctx.body='merge ok 200'; 113 | } 114 | 115 | }); 116 | 117 | 118 | 119 | 120 | /** 121 | * Create HTTP server. 122 | */ 123 | var server = http.createServer(app.callback()); 124 | server.listen(port); 125 | console.log('demo13 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo13/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 大文件上传 8 | 30 | 31 | 32 |

大文文件分片上传 之 断点续传

33 |

34 | 也是在分片上传的基础上,在上传的过程中如果网络中断或服务器中断的情况下,我们需要把已上传的片段信息 hash保存到本地,然后当网络恢复的时候,我们继续上传,那么继续上传的时候, 35 | 我们得到保存的信息,通过 hash 值判断当前的分段是否上传过,跳过已上传的部分,只传未上传的部分。 36 | 使用这种方式有个比较耗费资源的部分,就是需要对比分片的 hash 值,但是相对于我们重新上传消耗的时间来讲,这些可以忽略了。 37 | 38 | 下面我们进行模拟断点续传,用每个 chunk 的索引(顺序值)代替 hash 值,构建一个简单对象,保存是否上传到了服务器。 39 | 40 |

41 |
42 | 43 | 选择文件: 44 |

45 | 46 | 47 |
48 | 49 |
50 | 51 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo13/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo13/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo14/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //允许跨域 30 | app.use(async (ctx, next) => { 31 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 32 | ctx.set("Access-Control-Max-Age", 864000); 33 | // 设置所允许的HTTP请求方法 34 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); 35 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. 36 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); 37 | 38 | await next(); 39 | }) 40 | 41 | //二次处理文件,修改名称 42 | app.use((ctx) => { 43 | console.log(ctx.request.files); 44 | var body = ctx.request.body; 45 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 46 | var result=[]; 47 | var fileToken = ctx.request.body.token;// 文件标识 48 | var fileIndex=ctx.request.body.index;//文件顺序 49 | 50 | console.log('files'); 51 | console.log(files); 52 | 53 | if(files && !Array.isArray(files)){//单文件上传容错 54 | files=[files]; 55 | } 56 | 57 | files && files.forEach(item=>{ 58 | var path = item.path.replace(/\\/g, '/'); 59 | var fname = item.name;//原文件名称 60 | var nextPath = path.slice(0, path.lastIndexOf('/') + 1) + fileIndex + '-' + fileToken; 61 | if (item.size > 0 && path) { 62 | //得到扩展名 63 | var extArr = fname.split('.'); 64 | var ext = extArr[extArr.length - 1]; 65 | //var nextPath = path + '.' + ext; 66 | //重命名文件 67 | fs.renameSync(path, nextPath); 68 | 69 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 70 | } 71 | }); 72 | 73 | ctx.body = `{ 74 | "fileUrl":${JSON.stringify(result)} 75 | }`; 76 | 77 | if(body.type==='merge'){ 78 | //合并文件 79 | var filename = body.filename, 80 | chunkCount = body.chunkCount, 81 | folder = path.resolve(__dirname, '../static/uploads')+'/'; 82 | 83 | var writeStream = fs.createWriteStream(`${folder}${filename}`); 84 | 85 | var cindex=0; 86 | //合并文件 87 | function fnMergeFile(){ 88 | var fname = `${folder}${cindex}-${fileToken}`; 89 | var readStream = fs.createReadStream(fname); 90 | 91 | readStream.pipe(writeStream, { end: false }); 92 | readStream.on("end", function () { 93 | fs.unlink(fname, function (err) { 94 | if (err) { 95 | throw err; 96 | } 97 | }); 98 | if (cindex+1 < chunkCount){ 99 | cindex += 1; 100 | fnMergeFile(); 101 | } 102 | }); 103 | } 104 | 105 | try { 106 | fnMergeFile(); 107 | } catch (error) { 108 | 109 | } 110 | 111 | 112 | ctx.body='merge ok 200'; 113 | } 114 | 115 | }); 116 | 117 | 118 | 119 | 120 | /** 121 | * Create HTTP server. 122 | */ 123 | var server = http.createServer(app.callback()); 124 | server.listen(port); 125 | console.log('demo14 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo14/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 大文件上传-优化版 8 | 30 | 31 | 32 |

大文文件分片上传 之 断点续传

33 |

34 | 1. 为文件生成一个 hash 值,标识这个文件
35 | 2. js 将文件进行分段 按照2m 分
36 | 3. 上传文件前,在服务器获取到当前文件的已上分段信息。
37 | 4. 上传过程中进行和本地对比分段信息,只传未上传的分段。
38 | 5. 服务端保存分段信息和文件顺序。
39 | 6. 分段上传完成,客户端发送合并文件的请求 40 | 41 |

42 |
43 | 44 | 选择文件: 45 |

46 | 47 |
48 | 49 |
50 | 51 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo14/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo14/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files.f1;//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo15 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | formdata解析 8 | 9 | 10 |

手动解析 formdata

11 |
12 | 13 | 选择文件(可多选): 14 |

15 | 16 | 标题:


17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_5cb411292d060f7c1beb5013d213aebf.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_5cb411292d060f7c1beb5013d213aebf.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_72e5851d22c2658deff9dbdbc31fba6a.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_72e5851d22c2658deff9dbdbc31fba6a.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_7a57454a6d3dc77b6fefd137112816ea.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_7a57454a6d3dc77b6fefd137112816ea.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_7e43363a259814b9c078ca664166a0fb.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_7e43363a259814b9c078ca664166a0fb.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_b2afbf318a199a9d56ea2e64658e8929.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_b2afbf318a199a9d56ea2e64658e8929.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo15/static/uploads/upload_fb5aa88dbb158d66d35a820ec113d538.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo15/static/uploads/upload_fb5aa88dbb158d66d35a820ec113d538.xls -------------------------------------------------------------------------------- /src/upfiles-demo/demo2/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | var files = ctx.request.files.f1;//得到上传文件的数组 33 | var result=[]; 34 | if(!Array.isArray(files)){ 35 | files=[files]; 36 | } 37 | files && files.forEach(item=>{ 38 | var path = item.path.replace(/\\/g, '/'); 39 | var fname = item.name;//原文件名称 40 | var nextPath = path + fname; 41 | if (item.size > 0 && path) { 42 | //得到扩展名 43 | var extArr = fname.split('.'); 44 | var ext = extArr[extArr.length - 1]; 45 | var nextPath = path + '.' + ext; 46 | //重命名文件 47 | fs.renameSync(path, nextPath); 48 | 49 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 50 | } 51 | }); 52 | 53 | 54 | ctx.body = `{ 55 | "fileUrl":${JSON.stringify(result)} 56 | }`; 57 | }) 58 | 59 | 60 | 61 | 62 | /** 63 | * Create HTTP server. 64 | */ 65 | var server = http.createServer(app.callback()); 66 | server.listen(port); 67 | console.log('demo2 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo2/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

最原始的上传文件操作 - 多文件上传

11 |

使用 form 表单直接请求,不用 js

12 |
13 |
14 | 15 | 选择文件(可多选): 16 |
input 必须设置 name 属性,否则数据无法发送
17 |
18 | 标题:


19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo2/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo2/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo2/static/uploads/upload_a9825b5922faed699503ee95b680b43d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo2/static/uploads/upload_a9825b5922faed699503ee95b680b43d.png -------------------------------------------------------------------------------- /src/upfiles-demo/demo3/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | var files = ctx.request.files.f1;//得到上传文件的数组 32 | var result=[]; 33 | console.log(files); 34 | 35 | if(!Array.isArray(files)){//单文件上传容错 36 | files=[files]; 37 | } 38 | 39 | files && files.forEach(item=>{ 40 | var path = item.path.replace(/\\/g, '/'); 41 | var fname = item.name;//原文件名称 42 | var nextPath = path + fname; 43 | if (item.size > 0 && path) { 44 | //得到扩展名 45 | var extArr = fname.split('.'); 46 | var ext = extArr[extArr.length - 1]; 47 | var nextPath = path + '.' + ext; 48 | //重命名文件 49 | fs.renameSync(path, nextPath); 50 | 51 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 52 | } 53 | }); 54 | 55 | 56 | ctx.body = `{ 57 | "fileUrl":${JSON.stringify(result)} 58 | }`; 59 | }) 60 | 61 | 62 | 63 | 64 | /** 65 | * Create HTTP server. 66 | */ 67 | var server = http.createServer(app.callback()); 68 | server.listen(port); 69 | console.log('demo3 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo3/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

最原始的上传文件操作 - 多文件上传 之 iframe 局部刷新

11 |
12 | 13 |
14 | 15 | 选择文件(可多选): 16 |
input 必须设置 name 属性,否则数据无法发送
17 |
18 | 标题:


19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo3/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo3/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo4/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files.f1;//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo4 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo4/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

多文件上传 之 xhr formdata

11 |
12 | 选择文件(可多选): 13 |

14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo4/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo4/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo5/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files.f1;//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo5 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo5/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 9 | 10 |

多文件上传 之 fetch formdata

11 |
12 | 13 | 选择文件(可多选): 14 |

15 | 16 | 标题:


17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo5/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo5/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo6/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //允许跨域 30 | app.use(async (ctx, next) => { 31 | ctx.set('Access-Control-Allow-Origin', ctx.headers.origin); 32 | ctx.set("Access-Control-Max-Age", 864000); 33 | // 设置所允许的HTTP请求方法 34 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); 35 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. 36 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type"); 37 | 38 | await next(); 39 | }) 40 | 41 | //二次处理文件,修改名称 42 | app.use((ctx) => { 43 | console.log(ctx.request.files); 44 | 45 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 46 | var result=[]; 47 | console.log(files); 48 | 49 | if(!Array.isArray(files)){//单文件上传容错 50 | files=[files]; 51 | } 52 | 53 | files && files.forEach(item=>{ 54 | var path = item.path.replace(/\\/g, '/'); 55 | var fname = item.name;//原文件名称 56 | var nextPath = path + fname; 57 | if (item.size > 0 && path) { 58 | //得到扩展名 59 | var extArr = fname.split('.'); 60 | var ext = extArr[extArr.length - 1]; 61 | var nextPath = path + '.' + ext; 62 | //重命名文件 63 | fs.renameSync(path, nextPath); 64 | 65 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 66 | } 67 | }); 68 | 69 | 70 | ctx.body = `{ 71 | "fileUrl":${JSON.stringify(result)} 72 | }`; 73 | }); 74 | 75 | 76 | 77 | 78 | /** 79 | * Create HTTP server. 80 | */ 81 | var server = http.createServer(app.callback()); 82 | server.listen(port); 83 | console.log('demo6 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo6/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 30 | 31 | 32 |

多文件上传 之 xhr formdata 上传进度条 progress

33 |
34 | 35 | 选择文件(可多选): 36 |

37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo6/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo6/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo7/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files.f1;//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo7 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo7/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 拖拽上传文件 8 | 19 | 20 | 21 |

多文件上传 之 拖拽上传 xhr formdata

22 |
23 | 拖动文件到这里,开始上传 24 |
25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo7/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo7/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo8/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files.f1;//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo8 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo8/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 粘贴上传文件 8 | 19 | 20 | 21 |

多文件上传 之 粘贴上传 xhr formdata

22 |
23 | 可以直接粘贴图片到这里直接上传 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo8/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo8/static/uploads/aa.js -------------------------------------------------------------------------------- /src/upfiles-demo/demo9/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务入口 3 | */ 4 | var http = require('http'); 5 | var koaStatic = require('koa-static'); 6 | var path = require('path'); 7 | var koaBody = require('koa-body'); 8 | var fs = require('fs'); 9 | var Koa = require('koa2'); 10 | 11 | 12 | var app = new Koa(); 13 | var port = process.env.PORT || '8100'; 14 | 15 | var uploadHost= `http://localhost:${port}/uploads/`; 16 | 17 | app.use(koaBody({ 18 | formidable: { 19 | //设置文件的默认保存目录,不设置则保存在系统临时目录下 20 | uploadDir: path.resolve(__dirname, '../static/uploads') 21 | }, 22 | multipart: true // 支持文件上传 23 | })); 24 | 25 | app.use(koaStatic( 26 | path.resolve(__dirname, '../static') 27 | )); 28 | 29 | //二次处理文件,修改名称 30 | app.use((ctx) => { 31 | console.log(ctx.request.files); 32 | 33 | var files = ctx.request.files ? ctx.request.files.f1:[];//得到上传文件的数组 34 | var result=[]; 35 | console.log(files); 36 | 37 | if(!Array.isArray(files)){//单文件上传容错 38 | files=[files]; 39 | } 40 | 41 | files && files.forEach(item=>{ 42 | var path = item.path.replace(/\\/g, '/'); 43 | var fname = item.name;//原文件名称 44 | var nextPath = path + fname; 45 | if (item.size > 0 && path) { 46 | //得到扩展名 47 | var extArr = fname.split('.'); 48 | var ext = extArr[extArr.length - 1]; 49 | var nextPath = path + '.' + ext; 50 | //重命名文件 51 | fs.renameSync(path, nextPath); 52 | 53 | result.push(uploadHost+ nextPath.slice(nextPath.lastIndexOf('/') + 1)); 54 | } 55 | }); 56 | 57 | 58 | ctx.body = `{ 59 | "fileUrl":${JSON.stringify(result)} 60 | }`; 61 | }) 62 | 63 | 64 | 65 | 66 | /** 67 | * Create HTTP server. 68 | */ 69 | var server = http.createServer(app.callback()); 70 | server.listen(port); 71 | console.log('demo9 server start ...... '); -------------------------------------------------------------------------------- /src/upfiles-demo/demo9/static/html/upfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 基础-上传文件 8 | 14 | 15 | 16 |

多文件上传 之 本地图片预览

17 |

注意:window.URL.createObjectURL或者FileReader 兼容 ie10+

18 |
19 | 20 | 选择文件(可多选): 21 |

22 | 23 | 标题:


24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/upfiles-demo/demo9/static/uploads/aa.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/src/upfiles-demo/demo9/static/uploads/aa.js -------------------------------------------------------------------------------- /videos/1613922402615.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922402615.mp4 -------------------------------------------------------------------------------- /videos/1613922607631.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922607631.mp4 -------------------------------------------------------------------------------- /videos/1613922619676.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922619676.mp4 -------------------------------------------------------------------------------- /videos/1613922733493.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922733493.mp4 -------------------------------------------------------------------------------- /videos/1613922819298.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922819298.mp4 -------------------------------------------------------------------------------- /videos/1613922872381.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613922872381.mp4 -------------------------------------------------------------------------------- /videos/1613923074995.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613923074995.mp4 -------------------------------------------------------------------------------- /videos/1613923332893.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bigerfe/fe-learn-code/16ae63c3b2115cd5587af6731f43b3f025dccd2a/videos/1613923332893.mp4 -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /Users/zz_jesse/.nvm/versions/node/v12.16.3/bin/node /Users/zz_jesse/.nvm/versions/node/v12.16.3/bin/yarn 3 | 4 | PATH: 5 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/zz_jesse/.nvm/versions/node/v12.16.3/bin 6 | 7 | Yarn version: 8 | 1.22.4 9 | 10 | Node version: 11 | 12.16.3 12 | 13 | Platform: 14 | darwin x64 15 | 16 | Trace: 17 | Error: connect ECONNREFUSED 0.0.0.0:443 18 | at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16) 19 | 20 | npm manifest: 21 | { 22 | "name": "fe-learn-code", 23 | "version": "1.0.0", 24 | "description": "前端学习路上的代码实例", 25 | "main": "ls", 26 | "scripts": { 27 | "test": "echo \"Error: no test specified\" && exit 1" 28 | }, 29 | "author": "223344386", 30 | "license": "ISC", 31 | "dependencies": { 32 | "fluent-ffmpeg": "^2.1.2", 33 | "koa": "^2.8.2", 34 | "koa-better-body": "^3.1.2", 35 | "koa-body": "^4.1.1", 36 | "koa-bodyparser": "^4.2.1", 37 | "koa-convert": "^1.2.0", 38 | "koa-json": "^2.0.2", 39 | "koa-static": "^5.0.0", 40 | "koa2": "^2.0.0-alpha.7", 41 | "spark-md5": "^3.0.0" 42 | } 43 | } 44 | 45 | yarn manifest: 46 | No manifest 47 | 48 | Lockfile: 49 | No lockfile 50 | --------------------------------------------------------------------------------