关于express:我是如何让我的-GPT-长记性-的轻松实现有-记忆-的-GPT

我的项目地址:github.com/ltyzzzxxx/g… 最新进度:目前已实现 GPT 在线角色 DIY 性能,还在解决细节中... 欢送大家Star、提出 Issue & PR,一起高兴地用 GPT Terminal 游玩吧~ 前言明天持续来教大家如何玩转 OpenAI 接口! 自从 ChatGPT 横空出世之后,市面上就涌现了大量的类 GPT 利用(网站、公众号、小程序、App等等),它们和 ChatGPT 提供的性能简直并驾齐驱。这一切都是源于 OpenAI 为开发者们提供了 SDK 与 API 服务,使得大家可能欢乐地调用接口~ 然而,如果你不懂得如何应用它提供的服务,那么做进去的 GPT 利用与市面上的相比,可能有许多缺点。明天,我就先带大家功克第一个缺点:如何让你做的 GPT 利用长长 “忘性” !我会从实践与实战的角度,带大家制作出有 "记忆" 性能的 GPT! 接口分析咱们在发送音讯时,都是申请 OpenAI 提供的 createChatCompletion SDK 或 去调用 https://api.openai.com/v1/chat/completions API 从而获取 GPT 响应。想必大家如果看过我之前写的文章,肯定对这种形式不生疏。然而,如果你只是单纯地将以后用户的发问作为申请参数传递给接口中,GPT 只会给你返回以后问题的响应,它本身没有记录上下文的能力。因为,咱们对于 GPT 的每次申请与响应,都是独自的,并不会被 GPT 所存储。 然而,在实在的聊天场景,与你聊天的人肯定会晓得对话的上下文。要想使得 GPT 更加智能,必须得具备这一特点。难道 OpenAI 团队不晓得这一点吗?其实,解决方案还是老配方,答案还是藏在 createChatCompletion接口参数中! 大家应该还记得我在上上一篇文章中,我通过以下流程实现了角色定制: 在 Markdown 中依照模板格局,事后定义好角色信息以及具体问答 Case将其从 Markdown 格局转为了 JSON 对象数组申请 createChatCompletion 接口,参数 messages 即为转化好的对象数组这时候聪慧的大家预计曾经想到了,这些事后定义好的角色信息和问答 Case,就是 GPT 能够参考的上下文啊!这样看来,要想让 GPT “长忘性”,也能够通过这一思路实现! ...

June 23, 2023 · 2 min · jiezi

关于express:还在只用RedisTemplate访问Redis吗

开始筹备开始之前咱们须要有Redis装置,咱们采纳本机Docker运行Redis, 次要命令如下css复制代码docker pull redisdocker run --name my_redis -d -p 6379:6379 redisdocker exec -it my_redis bashredis-cli 后面两个命令是启动redis docker, 后两个是连贯到docker, 在应用redis-cli 去查看redis外面的内容,次要查看咱们存在redis外面的数据。RedisTemplate咱们先从RedisTemplate开始,这个是最好了解的一种形式,我之前在工作中也应用过这种形式,先看代码示例 咱们先定义一个POJO类less复制代码@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class Book implements Serializable {    private Long id;    private String name;    private String author;} 一个很简略的BOOK类,三个字段: id,name和author. 再来一个RedisTemplate的Beanarduino复制代码    @Bean    public RedisTemplate<String, Book> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<String, Book> template = new RedisTemplate<>();        template.setConnectionFactory(redisConnectionFactory);        return template;   } ...

May 24, 2023 · 4 min · jiezi

关于express:2023大厂算法面试真题手刷笔记含社区7大语言最佳答案No34在排序数组中查找元素的第一个和最后一个位置

题目详情给你一个依照非递加顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始地位和完结地位。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现工夫复杂度为 O(log n) 的算法解决此问题。 示例 1: **输出:** nums = [5,7,7,8,8,10], target = 8**输入:** [3,4]示例 2: **输出:** nums = [5,7,7,8,8,10], target = 6**输入:** [-1,-1]示例 3: **输出:** nums = [], target = 0**输入:** [-1,-1]提醒: 0 <= nums.length <= 105-109 <= nums[i] <= 109nums 是一个非递加数组-109 <= target <= 109解题思路办法1 线性查找首先,咱们从右边对nums进行线性扫描,当咱们找到一个指标实例时就会中断。如果咱们从不中断,那么指标就不存在,所以咱们能够提前返回“-1,-1”的“错误代码”。思考到咱们的确找到了一个无效的左索引,咱们能够进行第二次线性扫描,但这次从右开始。在这种状况下,遇到的指标的第一个实例将是最左边的实例(并且因为存在最右边的实例,因而也保障存在最左边的实例)。而后咱们简略地返回一个蕴含两个定位索引的列表。 class Solution { public int[] searchRange(int[] nums, int target) { int[] targetRange = {-1, -1}; // find the index of the leftmost appearance of `target`. for (int i = 0; i < nums.length; i++) { if (nums[i] == target) { targetRange[0] = i; break; } } // if the last loop did not find any index, then there is no valid range // and we return [-1, -1]. if (targetRange[0] == -1) { return targetRange; } // find the index of the rightmost appearance of `target` (by reverse // iteration). it is guaranteed to appear. for (int j = nums.length-1; j >= 0; j--) { if (nums[j] == target) { targetRange[1] = j; break; } } return targetRange; }}办法二 二分查找除了用于查找左右索引自身的子例程之外,整体算法与线性扫描办法的工作形式十分类似。,在这里,咱们应用批改后的二分查找来搜寻已排序的数组,并进行一些小的调整。,首先,因为咱们正在定位蕴含指标的最右边(或最左边)索引(而不是在咱们找到指标时返回true),所以算法一找到匹配就不会终止。,相同,咱们持续搜寻直到lo == hi并且它们蕴含能够找到指标的索引。 ...

May 18, 2023 · 7 min · jiezi

关于express:Leetcode算法刷题笔记含7大语言社区最佳答案No1两数之和

题目详情给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个整数,并返回它们的数组下标。 你能够假如每种输出只会对应一个答案。然而,数组中同一个元素在答案里不能反复呈现。 你能够按任意程序返回答案。 示例 1: **输出:** nums = [2,7,11,15], target = 9**输入:** [0,1]**解释:** 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。示例 2: **输出:** nums = [3,2,4], target = 6**输入:** [1,2]示例 3: **输出:** nums = [3,3], target = 6**输入:** [0,1]提醒: 2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109只会存在一个无效答案进阶: 你能够想出一个工夫复杂度小于 O(n2) 的算法吗? 解题思路办法一:暴力法暴力法很简略。遍历每个元素 x,并查找是否存在一个值与 target - x 相等的指标元素。 ...

May 11, 2023 · 2 min · jiezi

关于express:test

taeawtwetwet

April 27, 2023 · 1 min · jiezi

关于express:几个不错的idea插件让我码速又快了

前言idea能够说是java开发者应用的最多的开发工具了,一写好的idea插件不仅仅能赏心悦目,更能帮忙咱们晋升效率,有更多的工夫去摸鱼。上面就举荐一些我常常用的idea插件。Gradianto插件反对 idea的相干皮肤,是我用过比拟好用,ui格调很好的一款插件 jclasslib Bytecode viewer插件class文件反编译插件 ->jclasslib is a bytecode viewer for Java class files。 可是反编译class文件,当然这针对于老版本的idea,新版本的idea曾经主动集成该性能了。Grep Console 插件能够将控制台打印的日志分色彩显示,能够如下进行设置。 GenerateAllSetter 插件一键生成一个对象的所有set办法 。插件能够疾速生成 Java 类中的所有属性的 setter 办法,让你能够更快地为对象的各个属性增加 setter。帮忙你放慢 Java 开发工作流程,自动化生成 setter 办法的创立,进步开发效率GsonFormat 插件应用 alt+s 快捷键调起。应用 GsonFormat 插件能够大大简化在开发过程中手动创立 Java 类的工作量。生成的 Java 类将蕴含与输出的 JSON 字符串中的键和值对应的属性和办法。这使得在解决 JSON 数据时更加不便和快捷JRebel and XRebel 插件热部署插件(这个我都用多介绍了吧)。Tomcat启动相干配置(jrebel启动tomcat时须要加载的我的项目配置): CATALINA_BASE=E:\下载文件\java产品\tomcat\apache-tomcat-7.0.76-windows-x64\apache-tomcat-7.0.76热部署jar包中的jsp , js 等代码时,须要在pom文件中配置相应的打包代码。而后再rebel.xml部署jsp等页面的本地文件地址,即可实时失效。leetcode editor 插件一个连贯leetcode官网的插件。当咱们摸鱼的时候,咱们就能够每天都在idea中练习一下,还没人发现你在摸鱼,真香。maven helper 插件一个帮组你显示maven依赖树的插件。点击pom文件而后再编辑视图中左下角抉择“Dependency Aanlyzer”即可查看。 咱们能够通过他疾速查看我的项目的maven依赖关系,发现依赖抵触等。其反对树形显示依赖和列表显示,非常不便。 SequenceDiagram 插件查看办法调用时序图。应用 SequenceDiagram 插件能够不便地可视化代码中的办法调用关系,以及办法之间的时序关系,有助于开发人员更好地了解代码执行过程,有助于调试和优化代码。 Statistic 插件统计代码量多少的一个插件。Statistic 插件能够不便地理解代码的构造和应用状况,有助于开发人员更好地治理和保护代码。统计后果将显示代码中各种元素的应用状况,例如类的数量、办法的数量、变量的数量等。你还能够依照各种条件排序和过滤统计后果,以便更好地理解代码的构造和应用状况。 Translation 插件这是一款国人开发的插件,每当你查看源码的doc文档时,看到满屏的英文您累嘛?说实话对我这种英文渣来说,这是在是一件苦楚的事件,然而该插件拯救了我。感激作者。以上就是我举荐的几款idea插卡,各位看官按需获取。 ...

April 18, 2023 · 1 min · jiezi

关于express:FastDFS收藏起来现在开始用Minio吧

一、Minio介绍MinIO是寰球当先的对象存储先锋,目前在全世界有数百万的用户。 高性能 ,在规范硬件上,读/写速度上高达183GB/秒和171GB/秒,领有更高的吞吐量和更低的提早可扩展性 ,为对象存储带来了简略的缩放模型,通过增加更多集群能够扩大空间简略 ,极简主义是MinIO的指导性设计准则,即可在几分钟内装置和配置与Amazon S3兼容 ,亚马逊云的 S3 API(接口协议)是在寰球范畴内达到共识的对象存储的协定,是全世界内大家都认可的规范数据安全 ,应用纠删码来爱护数据免受硬件故障和无声数据损坏 纠删码纠删码是一种复原失落和损坏数据的数学算法, Minio默认采纳 Reed-Solomon code将数据拆分成N/2个数据块和N/2个奇偶校验块。这就意味着如果是16块盘,一个对象会被分成8个数据块、8个奇偶校验块,你能够失落任意8块盘(不论其是寄存的数据块还是校验块),你仍能够从剩下的盘中的数据进行复原。docs.minio.org.cn/docs/master…Minio和FastDFS的比照 装置难度文档性能容器化反对SDK反对 二、Minio装置 为了疾速搞定Minio的部署工作。咱们通过Docker-Compose来一键疾速部署操作1.装置DockerCompose 装置DockerCompose的前提是先装置一个Docker环境,如果还没装置的参考波哥的博客地址:blog.csdn.net/qq_38526573… Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您能够应用 YML 文件来配置应用程序须要的所有服务。而后,应用一个命令,就能够从 YML 文件配置中创立并启动所有服务。 一键启动所有的服务 DockerCompose的应用步骤 创立对应的DockerFile文件创立yml文件,在yml文件中编排咱们的服务通过 docker-compose up命令 一键运行咱们的容器 官网地址:docs.docker.com/compose下载地址:curl -L https://get.daocloud.io/docker/compose/releases/download/1.25...uname -s-uname -m > /usr/local/bin/docker-compose复制代码批改文件夹权限chmod +x /usr/local/bin/docker-compose复制代码建设软连贯ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose复制代码查看是否装置胜利docker-compose --version复制代码2.装置Minio集群官网举荐 docker-compose.yaml:稍加批改,内容如下:version: '3.7' 所有容器通用的设置和配置x-minio-common: &minio-common image: minio/minio command: server --console-address ":9001" http://minio{1...4}/data expose: - "9000"# environment: # MINIO_ROOT_USER: minioadmin# MINIO_ROOT_PASSWORD: minioadminhealthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]interval: 30stimeout: 20sretries: 3启动4个docker容器运行minio服务器实例应用nginx反向代理9000端口,负载平衡, 你能够通过9001、9002、9003、9004端口拜访它们的web consoleservices: minio1: <<: *minio-commonhostname: minio1ports: - "9001:9001"volumes: - ./data/data1:/dataminio2: ...

April 18, 2023 · 2 min · jiezi

关于express:Java项目是不是分布式真有那么重要吗

大略不晓得从什么时候,「微服务」「分布式」这两个词又再次频繁呈现在我的眼帘里。「微服务」「分布式」在我刚毕业的时候还是比拟关注的,那时候还入门了一把SpringCloud,写了一篇很长的文章,还是很顶的,有不少的大号都给我转载了,在知乎又取得了很多的赞。那时候感觉懂「分布式」「微服务」是要害,什么SSM/SSH这不是谁都会吗,靠SSH/SSM我怎么有竞争力找工作啊。起初工作当前,对这块技术栈就没怎么深刻去看过了,毕竟我不是在公司里搞RPC框架组件的,把工夫都专一于本人的业务零碎里去了。工作了之后,有的共事跳槽去了阿里/字节,我看他们简历也没写本人懂「微服务」「分布式」,也没见他们在简历上有Dubbo和SpringCloud这种技术栈,但这也没影响他们跳去字节和阿里这种公司。同理,我在去年跳槽的时候,我的简历也没有这块内容。面试下来,也仅仅只有一个面试官随口提了下我懂不懂SpringCloud的原理。我跟他说我对这块理解不深,只晓得大抵的过程,他也没尴尬我,间接就跳过了。而我当初工作的内容也没有大量波及到Dubbo/SpringCloud这种技术栈的组件去应用,所以跟大家比起来,我这块技术栈还是很单薄。可能等我下次跳槽的时候,这块货色我还是写不上简历去。回到正题上吧,最近「微服务」「分布式」这两个词又再次频繁呈现在我的眼帘里,最次要的可能是我做了个开源我的项目「Austin」,有挺多人问我这个我的项目是不是分布式的。开源我的项目音讯推送平台austin仓库地址: 音讯推送平台推送下发【邮件】【短信】【微信服务号】【微信小程序】【企业微信】【钉钉】等音讯类型。 gitee.com/zhongfuchen…github.com/ZhongFuChen… 能够明确地通知大家,它并不是「分布式」「微服务」的我的项目。目前到此为止,它外围就只有一个发送的接口,而且只能通过HTTP的形式去调用。那他能做成一个「分布式」我的项目吗?答案也是能够的,只有把「服务治理」相干的组件引入就能够问题了。当初是我的项目是离开module模块的,austin-web(治理后盾)/austin-cron(定时工作)/austin-api和austin-api-impl(接入层)/austin-handler(下发逻辑解决层)这几个都能够独自抽出来部署。 (实际上在线上环境里,也是这么干的)独自部署了当前,再通过「服务治理」的组件进行治理,那零碎就是「分布式」的架构了。听着听不难,对不对?实际上也的确不难。既然如此,为什么我始终都没去变动我的零碎呢?最外围的点在于:我认为以我这类零碎来说,性能的完整性比「分布式」这种架构模式更加重要。又因为我的工作历程导致我始终在生产环境下就没有很多条件去深刻接触这些「服务治理」的组件,我对它们是不相熟的。而且我集体对此类框架又没有很浓重的趣味,我喜爱把重点放在存储的组件上(更违心把工夫花在Redis/MySQL/HBase/Elasticsearch这些)最近,我看股东群有好多都是在备战校招的,也见证了整个校招环境的确是越来越卷了,在这我给个小tips吧。其实吧,我感觉作为应届生在面试的时候是不太须要过于在意「分布式」。以我做面试官的角度而言,在正式工作之前,能有啥场景给你深刻去做「分布式」零碎。除非你简历真的写了挺多的分布式内容,不然我是不会把「分布式」作为面试校招生的重点(如果你都真的懂了,那的确是能够拉开差距的,前提是你的基础知识体现都不错)。如果你没写,那我真的就不会去问这块内容。简历上写的技术栈最好是本人比拟相熟的,只是用过但不懂原理的能够去掉,简历上的技术栈并不是越多越好祝福备战的小伙伴都能早日上岸!

March 28, 2023 · 1 min · jiezi

关于express:任务编排CompletableFuture从入门到精通

前言最近遇到了一个业务场景,波及到多数据源之间的申请的流程编排,正好看到了一篇某团介绍CompletableFuture原理和应用的技术文章,次要还是波及应用层面。网上很多文章波及原理的局部讲的不是特地具体且比拟形象。因为波及到多线程的工具必须要了解原理,不然一旦遇到问题排查起来就只能凭玄学,正好借此梳理一下CompletableFuture的工作原理背景咱们把Runnable了解为最根本的线程工作,只具备在线程下执行一段逻辑的能力。为了获取执行的返回值,发明了Callable和与其配合应用的Future。为了将工作之间进行逻辑编排,就诞生了CompletableFuture。对于如何了解工作的逻辑编排,举一个简略的例子: 关上电脑-更新零碎这两个操作是有先后顺序的,然而泡茶和这两个操作没有先后顺序,是能够并行的,而开始办公必须要期待其余操作完结之后能力进行,这就造成了工作编排的执行链。在IO密集型零碎中,相似的场景有很多。因为不同数据集的查问依赖主键不同,A数据集的查问主键是B数据集的一个字段这种状况很常见,通常还须要并发查问多个数据集的数据,所以对于多线程的执行编排是有需要的。一种解决办法是CountDownLatch,让线程执行到某个中央后进行期待,直到依赖的工作执行完结。对于一些简略的执行链是能够满足的,然而当编排逻辑简单起来,CountDownLatch会导致代码难以保护和调试。所以诞生了CompletableFuture用来形容和保护工作之间的依赖关系以进行工作编排。在理论利用中,有以下两类场景是适宜应用工作编排的: 多数据源申请的流程编排 非阻塞化网关等NIO场景 应用形式创立与执行同步办法和FutureTask相似,CompletableFuture也通过get()办法获取执行后果。然而不同的是,CompletableFuture自身能够不承载可执行的工作(相比FutureTask则必须承载一个可执行的工作Callable),通过一个用于标记执行胜利并设置返回值的函数,在应用上也更为灵便,如下: CompletableFuture<String> demo = new CompletableFuture<>();demo.complete("success");System.out.println(demo.get());复制代码 执行后果:success 和Future相似,get()函数也是同步阻塞的,调用get函数后线程会阻塞直到调用complete办法标记工作曾经执行胜利。除了手动触发工作的实现,也能够让创建对象的同时就标记工作实现: CompletableFuture<String> demo = CompletableFuture.completedFuture("success");System.out.println(demo.get());复制代码 执行后果:success 异步办法相比于同步办法,异步执行更为常见。比方上面这个例子: CompletableFuture<String> demo = CompletableFuture.supplyAsync(() -> { System.out.println("do something by thread" + Thread.currentThread().getName()); return "success"; }); System.out.println(demo.get());复制代码 执行后果:do something by threadForkJoinPool.commonPool-worker-9success supplyAsync办法接管一个Supplier对象,逻辑函数交给线程池中的线程异步执行 默认会应用ForkJoinPool的公共线程池来执行代码(不举荐),当然也能够指定线程池,如下: ExecutorService executor = Executors.newFixedThreadPool(4);CompletableFuture<String> demo = CompletableFuture.supplyAsync(() -> { System.out.println("do something by thread" + Thread.currentThread().getName()); return "success";}, executor);System.out.println(demo.get());复制代码 执行后果:do something by threadpool-1-thread-1success 如果不须要执行后果,也能够用runAsync办法: CompletableFuture.runAsync(() -> { System.out.println("do something by thread" + Thread.currentThread().getName());});复制代码 ...

March 28, 2023 · 4 min · jiezi

关于express:GPT4免费无限制使用教程

你还在为开明Chat GPT账号苦恼吗你还在为不能拜访的问题苦恼吗你还在为拜访次数及速度苦恼吗明天举荐的这个工具对于这些问题都不是问题,基于GPT-4(官网是这样介绍的,然而有人通过对话让它答复模型,它的答复却不是,运行中也有肯定的错误率,理论大家须要自行判断),分分钟即可体验,不须要注册账号,没有Q的问题,没有拜访次数的限度。专为开发者设计,针对你的代码逐行剖析,优化代码,书写代码。以下是援用官网的一句话: Write, edit, and chat about your code with GPT-4 in a new type of editor 应用非常简单,进入官网的首页点击下载,装置软件,软件不大很快就能装置实现。装置实现后的工具页面,和vscode开发工具很像。 上面介绍一下次要的四个性能,点击右上角的机器人按钮能够呈现这个弹窗。 也能够通过选中代码进行聊天和改写。 生成代码输出你要生成代码形容,回车即可,比方我要写一个五子棋小游戏,如下图所示其响应速度十分快。 每次的答复长度是有限度的,对于没有书写实现或者答复实现的状况输出持续即可。对于比拟长的代码可能生成的代码运行会有问题,针对有问题的中央能够持续提醒它优化改过。 文字聊天产品的次要指标是程序软件方面,然而问其余的问题我试过也是可行的,就和一般的chatGPT没有什么区别。 选中代码聊天对选中的代码进行发问,解析其含意,是否有优化的空间,改写为其余语音等等。总之就是针对你选中的代码你能够问任何问题。 改写代码对选中的代码进行改写编辑,比方减少正文,改进优化,批改代码语言或者正文语言等。批改完了会有提醒是否承受本次的批改,就像合并代码一样。 最初最初附上官网的地址:www.cursor.so 。整个工具非常简洁,运行速度非常高效。是程序开发者的利器,很合乎公众程序开发者的习惯。这个界面的操作和一般的开发工具一样,不晓得你是在写代码呢,还是在写代码呢~[偷笑]看完本文如果感觉有用,记得点个赞反对,珍藏起来说不定哪天就用上啦~

March 22, 2023 · 1 min · jiezi

关于express:大屏开发你需要知道哪些

大屏 大屏是什么呢?再我前几年刚接触这个词得时候很新鲜,全名叫态势感知大屏,大屏得特点是炫酷、难看,给用户满满得科技感。 听一位前辈说当年再招标会上,再都用exel、word做界面图表文档得时候,有一家公司把可视化态势感知大屏展现进去了,间接秒杀其余厂家。 那么当咱们开发一款大屏点的时候须要留神什么呢? 适配 再适配得技术概念上分为真适配、伪适配。 那么什么叫做真适配、伪适配呢? 伪适配 伪适配就是利用csstransform: scale(1); 达到一个界面适配。 长处: 适配比拟快,就应用失常px开发就好了,监听下分辨率做一个scale缩放。 毛病: png、canvas 等要转成svg,不然就会含糊、不清晰。 真适配 真适配就是利用vw、vh、rem、%,达到一个界面得真适配。 vw 100vw 等于以后窗口屏幕得宽度;vh 100vh 等于以后窗口屏幕得高度;rem 次要依据根元素body得font-size:12px, 1rem 等于12px, 而后跟用窗口得大小赋值给body对应得fontSize; 长处: 再开发阶段须要间接应用对应得尺寸单位,或者利用postcss-px-to-viewport、postcss-pxtorem等postcss插件达到一个px得转换。(集体倡议再开发阶段间接应用适配单位,插件还是有或多或少得问题)。 毛病: 要针对性进行每个元素进行单位适配,略微有些老本(能够利用vscode插件间接实现转化); vscode插件:【px2vw】 针对css、less、sass 能够实现单位转化,然而他不反对.vue文件中style的转换。vscode插件:【px2xx】这个插件能满足咱们开发单位转换。 真适配又分为 高度适配、宽度适配。 宽度适配就是依据用户得屏幕窗口宽度发生变化做到一个界面适配,比方全副都应用vw。高度适配是依据用户屏幕窗口发生变化达到一个界面适配,比方应用vh; 其实集体认为没必要做高度适配、都依据宽度vw达到一个界面适配就好了,次要是因为做高度适配得话应用vh,再小点得屏幕上 很容易就产生文本重叠、界面不美观、因为文字大小再浏览器最小是12px嘛。大屏界面布局 其实个别大屏布局会又一个header(主题目、工夫展现)、side (副标题:屏幕的两侧可能会分为4块4个维度去展现以后屏的一些信息)、main(大屏主视图)、footer(底部)。 咱们再搭建容器应用的都是定位那么肯定要分清定位权重。上面是一个常见布局权重散布: header 应该是position: absolute;top: 0; height: 60px(须要实现对应设计搞单位转换): z-index:2; 权重是2; side 应该是position: absolute; top: 60px(header的高度); leftSide 应该是position: absolute; top: 0; left: 0; z-index: 2; 权重2 leftTopSide 应该是 position: absolute; top: 10px(间距); z-index: 3; 权重3leftBottomSide 应该是 position: absolute; bottom: 10px(间距); z-index: 3;权重3 ...

March 22, 2023 · 2 min · jiezi

关于express:赶走烦人的ifelse使用状态模式推动业务生命周期的流转

1.业务背景本文借助海内互金业务的借款流程开展。业务外围是借款的生命周期,相当于是电商中的订单一样。一笔借款的整个生命周期蕴含了提交,审批,确认,放款,还款。一笔借款的状态对应已上的操作,同样就很多了。如图是一笔借款的生命周期: 对于这么多状态,业务代码上有很多判断分支,什么状况下能够做什么操作,这是强校验的。业务初期疾速上线,if else能够疾速地将业务串联起来,在各个业务解决的节点判断并执行业务逻辑。 if (LoanStatusConstant.CREATED.equals(oldStatus) ||     LoanStatusConstant.NEED_MORE_INFO.equals(oldStatus)) {     log.info("---> Loan to Submitted"); }  if (LoanStatusConstant.APPROVED.equals(loan.getStatus())) {     log.info("---> Loan confirmed"); }  if (!LoanStatusConstant.APPROVED.equals(loanStatus)) {     log.info("---> Loan approved,to Fund"); } //.......复制代码2.业务代码的“坏滋味”随着经营推广的力度加大,用户一拥而上,风控环境更加严苛,整个产品线也陆续退出了更多的流程去迭代产品,让危险和收益能趋于均衡。这时候在开发代码上的体现就是代码库急剧收缩,业务扩张天然会扩招,新共事也会在已有的代码上打补丁,在这些补丁式的需要下,已经的if else会指数级的凌乱,一个简略的需要都可能挑战现有的状态分支。这种“坏滋味”不加以干涉,整个我的项目的生命力间接走向晚年。 //审核 public void approve(@RequestBody LoanSubmitDTO submitDTO) {     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));     if (Objects.isNull(loan)){         throw new BaseBizException("loan Not exists");    }     if (!SUBMITTED.getCode().equals(loan.getStatus())){         throw new BaseBizException("loan status incorrect");    }      loan.setStatus(APPROVED.getCode());     loanMapper.updateById(loan); } //确认 public LoanDTO submit(LoanSubmitDTO submitDTO) {     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));     if (Objects.isNull(loan)){         throw new BaseBizException("loan Not exists");    }     if (!CREATED.getCode().equals(loan.getStatus())){         throw new BaseBizException("loan status incorrect");    }      loan.setStatus(SUBMITTED.getCode());     loanMapper.updateById(loan);     riskService.callRisk(submitDTO);      return BeanConvertUtil.map(loan,LoanDTO.class); }复制代码随着我的项目一直收缩,为了对贷款状态进行校验,if else会充斥业务层的各个中央,此时,一旦产品上对业务流程进行调整,状态也会随着批改。比方新增了风控确认机制,用户能够补充信息再提交,对于满足肯定条件的贷款能够让用户补充肯定的风控信息再提交,那么此时对于借款提交流程的前置状态就要发生变化了: //提交 public LoanDTO submit(LoanSubmitDTO submitDTO) {     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));     if (Objects.isNull(loan)){         throw new BaseBizException("loan Not exists");    }     //判断条件发生变化。     if (!CREATED.getCode().equals(loan.getStatus())&&!NEED_MORE_INFO.getCode().equals(loan.getStatus())){         throw new BaseBizException("loan status incorrect");    }      loan.setStatus(SUBMITTED.getCode());     loanMapper.updateById(loan);     riskService.callRisk(submitDTO);      return BeanConvertUtil.map(loan,LoanDTO.class); }复制代码我的项目迭代过程中每一次上线前测试同学都会进行严格地测试。以上这种变动可能会批改多个中央的代码,测试同学就不得不进行大面积的回归测试,上线危险会大大增加;而咱们开发同学这种新逻辑上线就硬改原有代码的行为,违反了开闭准则,随着业务的迭代,我的项目代码的可读性会越来越差(if-else越来越多,测试用例不清晰),可能很多人感觉就改了个判断语句没什么大不了的,但实际上很多生产事变都是因为这种频繁的小改变导致的。如何去躲避已上这种 “坏滋味” 呢?3.OCP准则(凋谢关闭准则)3.1 定义咱们先来看看什么是【凋谢关闭准则】: ...

March 20, 2023 · 9 min · jiezi

关于express:SpringBoot项目jarwar包启动解析

一、jar包和war包的区别1.1 war包 war包是Java Web应用程序的一种打包形式合乎Servlet规范,它是Web Archive的缩写,次要用于存储Web应用程序相干的文件,包含Java类文件、JSP、HTML、CSS、JavaScript、图片等资源文件。war包须要部署到web服务器中(Tomcat、Apache、IIS) 1.2 jar包 jar包是类的归档文件,次要用于存储Java类文件和相干资源文件。它通常被用于封装Java应用程序或Java类库,不便程序的部署和公布jar包能够被JVM间接加载和运行。 1.3 次要区别: jar包次要用于存储Java类文件和相干资源文件,而war包次要用于存储Web应用程序相干的文件。jar包能够被JVM间接加载和运行,而war包须要被Web服务器加载和运行。jar包通常用于封装Java应用程序或Java类库,而war包用于封装Java Web应用程序。 二、SpringBoot应用war包启动war包启动:须要先启动内部的Web服务器,实现Servlet3.0标准中疏导利用启动类,而后将war包放入Web服务器下,Web服务器通过回调疏导利用启动类办法启动利用。2.1 Servlet3.0标准中疏导利用启动的阐明 在Servlet容器(Tomcat、Jetty等)启动利用时,会扫描利用jar包中 ServletContainerInitializer 的实现类。框架必须在jar包的 META-INF/services 的文件夹中提供一个名为 javax.servlet.ServletContainerInitializer 的文件,文件内容要写明 ServletContainerInitializer 的实现类的全限定名。这个 ServletContainerInitializer 是一个接口,实现它的类必须实现一个办法:onStartUp能够在这个 ServletContainerInitializer 的实现类上标注 @HandlesTypes 注解,在利用启动的时候自行加载一些附加的类,这些类会以字节码的汇合模式传入 onStartup 办法的第一个参数中。 public interface ServletContainerInitializer { void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;}复制代码2.2 SpringBootServletInitializer的作用和原理Spirng中SpringServletContainerInitializer实现了Servlet的标准 @HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer { @Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { // SpringServletContainerInitializer会加载所有的WebApplicationInitializer类型的一般实现类 List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // 如果不是接口,不是抽象类 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { // 创立该类的实例 initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); // 启动Web利用onStartup办法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }}}复制代码@HandlesTypes应用BCEL的ClassParser在字节码层面读取了/WEB-INF/classes和jar中class文件的超类名和实现的接口名,判断是否与记录的注解类名雷同,若雷同再通过org.apache.catalina.util.Introspection类加载为Class对象保存起来,最初传入onStartup办法参数中SpringServletContainerInitializer类上标注了@HandlesTypes(WebApplicationInitializer.class),所以会导入WebApplicationInitializer实现类SpringBoot中SpringBootServletInitializer是WebApplicationInitializer的抽象类,实现了onStartup办法@Overridepublic void onStartup(ServletContext servletContext) throws ServletException { ...

March 20, 2023 · 4 min · jiezi

关于express:使用expressws启动WebSocket服务实现双向通讯功能

成果动态图咱们应用WebSocket写一个这样的小demo: 演示网址:http://ashuai.work:8888/#/WebSocket (点击跳转观看成果)前端代码仓库地址:https://github.com/shuirongshuifu/elementSrcCodeStudy后端代码仓库地址:https://github.com/shuirongshuifu/express-ws-serverWebSocket相干常识温习什么是WebSocket,解决了什么问题WebSocket是html5推出的新个性,新性能,是一种新的网络通讯协定WebSocket解决了http只能单向申请的缺点(http只能由客户端向服务端发申请)应用WebSocket协定能够做到,客户端和服务端互相发送音讯http和WebSocket二者长相区别:http://ashuai.work:6789/url和ws://ashuai.work:6789/url(就最后面的协定变了)WebSocket利用场景对于实时性要求比拟高的性能需要,如: 聊天室性能实时股票行情性能体育竞赛直播性能最新天气最新消息(订阅公布,小红点未读音讯)多人协同编辑文档(及时更新)监控性能(监控地位、监控相干数据搭配可视化图表)在线客服根本WebSocket的八股文概念没啥意思,大家可移步到MDN官网文档瞅瞅这里咱们通过一个例子去学习,高深莫测哎 举个WebSocket理论例子例子需要可创立WebSocket服务,并敞开之实现客户端能够向服务端发送申请实现服务端能够向客户端推送音讯存储后端推送的每一条音讯类编程,便于前端多个中央复用......后端用express-ws开启WebSocket服务这里咱们应用npm上的一个比拟优良的包:express-ws,地址如下:https://www.npmjs.com/package/express-ws/v/5.0.2 当然,前提是咱们须要搭建一个express环境,这里咱们不赘述,间接上代码: 代码中,曾经有相干正文啦...app.js文件中应用express-ws包注入.ws接口办法 const express = require('express') // 引入express插件包并生成一个实例appconst app = express()// 引入express-ws的WebSocket性能,并混入app,相当于为 app实例增加 .ws 办法const expressWs = require('express-ws')(app) const Router = require('./router') // 引入分模块治理的路由app.use(Router) // 路由分模块app.listen(10000, (req,res) => { // 在10000端口上启动后端服务 console.log('后端服务端口地址为: http://localhost:10000');})router.js中应用route.ws创立WebSocket接口服务 /** * route.ws('/url',(ws, req)=>{ }) * 建设WebSocket服务,并指定对应接口url,及相应回调 * ws为实例化的对象,req即为申请 * * ws.send办法用来向客户端发送信息 * ws.on办法用于监听事件(如监听message事件,或监听close事件) * */route.ws('/mySocketUrl', (ws, req) => { // console.log('连贯胜利', ws) ws.send('来自服务端推送的音讯') ws.on('message', function (msg) { ws.send(`收到客户端的音讯为:${msg},再返回去`) }) // 应用定时器不停的向客户端推动音讯 let timer = setInterval(() => { ws.send(`服务端定时推送音讯: ${getNowTime()}`) }, 1000) ws.on('close', function (e) { // console.log('连贯敞开') clearInterval(timer) timer = null })})有的道友说,这样看着也不不便,有没有残缺的后端代码啊,我想运行起来,更加直观,不便,那必须有哎 ...

March 18, 2023 · 2 min · jiezi

关于express:Java中如何解析SQL语句格式化SQL语句生成SQL语句

昨天在群里看到有小伙伴问,Java里如何解析SQL语句而后格式化SQL,是否有现成类库能够应用?之前TJ没有做过这类需要,所以去钻研了一下,并找到了一个不过的解决方案,明天举荐给大家,如果您正要做相似内容,那就拿来试试,如果临时没需要,就先理解珍藏(技多不压身)。JSqlParserJSqlParser是一个用Java编写的SQL解析器,能够将SQL语句解析为Java对象,从而使开发人员可能轻松地剖析、批改和重构SQL查问。比方,这样的一句SQL语句SELECT 1 FROM dual WHERE a = bSELECT 1 FROM dual WHERE a = bJSqlParser能够将其解析为如下对象构造 SQL Text └─Statements: net.sf.jsqlparser.statement.select.Select └─selectBody: net.sf.jsqlparser.statement.select.PlainSelect ├─selectItems -> Collection<SelectExpressionItem> │ └─selectItems: net.sf.jsqlparser.statement.select.SelectExpressionItem │ └─LongValue: 1 ├─Table: dual └─where: net.sf.jsqlparser.expression.operators.relational.EqualsTo ├─Column: a └─Column: b复制代码而后咱们就能够通过其提供的API来拜访这句SQL语句中的各个因素:Statement statement = CCJSqlParserUtil.parse(sqlStr);if (statement instanceof Select) { Select select = (Select) statement;PlainSelect plainSelect = (PlainSelect) select.getSelectBody();SelectExpressionItem selectExpressionItem = (SelectExpressionItem) plainSelect.getSelectItems().get(0);Table table = (Table) plainSelect.getFromItem();EqualsTo equalsTo = (EqualsTo) plainSelect.getWhere();Column a = (Column) equalsTo.getLeftExpression();Column b = (Column) equalsTo.getRightExpression();}复制代码目前,JSqlParser反对了大部分次要的关系型数据库,包含: ...

March 10, 2023 · 1 min · jiezi

关于express:面试官什么是双亲委派模型

加入过校招面试的同学,应该对这个问题不生疏。个别发问 JVM 知识点的时候,就会顺带问你双亲委派模型(顺当的翻译。。。)。就算是不筹备面试,学习双亲委派模型对于咱们也十分有帮忙。咱们比拟相熟的 Tomcat 服务器为了实现 Web 利用的隔离,就自定义了类加载并突破了双亲委派模型。这篇文章我会先介绍类加载器,再介绍双亲委派模型,这样有助于咱们更好地了解。目录概览: 回顾一下类加载过程开始介绍类加载器和双亲委派模型之前,简略回顾一下类加载过程。 类加载过程:加载->连贯->初始化。连贯过程又可分为三步:验证->筹备->解析。 加载是类加载过程的第一步,次要实现上面 3 件事件: 通过全类名获取定义此类的二进制字节流将字节流所代表的动态存储构造转换为办法区的运行时数据结构在内存中生成一个代表该类的 Class 对象,作为办法区这些数据的拜访入口 类加载器类加载器介绍类加载器从 JDK 1.0 就呈现了,最后只是为了满足 Java Applet(曾经被淘汰) 的须要。起初,缓缓成为 Java 程序中的一个重要组成部分,赋予了 Java 类能够被动静加载到 JVM 中并执行的能力。依据官网 API 文档的介绍: A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.Every Class object contains a reference to the ClassLoader that defined it.Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader. ...

March 10, 2023 · 4 min · jiezi

关于express:Java为什么不支持多继承

首先,思考这么一种场景,如果当初A类继承了B类和C类,并且B类和C类中,都存在test()办法,那么当A类对象调用test()办法时,该调用B类的test()呢?还是C类的test()呢?是没有答案的,所以Java中不容许多继承。然而,Java中接口是能够多继承的,比方:public interface A { void test();} public interface B { void test();} public interface C extends A, B{ }复制代码为什么接口能够?因为都是A、B、C都是接口,就算A、B两个接口中都定义了test办法,因为接口中只是申明了办法,并没有真正实现办法,所以对于C接口而言并不会照成困扰,对于C接口而言它只是继承了同一个test()办法的申明而已,在应用时须要C接口的实现类来实现这个test()办法就能够了。public class C1 implements C{ public void test() { System.out.println("hello Hoeller");}}复制代码那么接口中不是有default办法吗?那不是也能够在接口中来实现办法吗?咱们间接来测试一下:public interface A { default void test() { System.out.println("a");}} public interface B { default void test() { System.out.println("b");}} public interface C extends A, B{ }复制代码此时C接口会编译报错,报错信息为: com.hoeller.C inherits unrelated defaults for test() from types com.hoeller.A and com.hoeller.B 翻不翻译都无所谓了,反正就是报错了,示意C接口不能同时继承两个接口中default办法test()。如果你问,那为什么C++中能够反对多继承,那得解释菱形继承、虚继承,本文就不剖析了(因为我也不晓得)。

March 10, 2023 · 1 min · jiezi

关于express:既然有Map了为什么还要有Redis

一、同样是缓存,用map不行吗? Redis能够存储几十个G的数据,Map行吗?Redis的缓存能够进行本地长久化,Map行吗?Redis能够作为分布式缓存,Map只能在同一个JVM中进行缓存;Redis反对每秒百万级的并发,Map行吗?Redis有过期机制,Map有吗?Redis有丰盛的API,反对十分多的利用场景,Map行吗? 二、Redis为什么是单线程的? 代码更清晰,解决逻辑更简略;不必思考各种锁的问题,不存在加锁和开释锁的操作,没有因为可能呈现死锁而导致的性能问题;不存在多线程切换而耗费CPU;无奈施展多核CPU的劣势,但能够采纳多开几个Redis实例来欠缺; 三、Redis真的是单线程的吗? Redis6.0之前是单线程的,Redis6.0之后开始反对多线程;Redis外部应用了基于epoll的多路服用,也能够多部署几个Redis服务器解决单线程的问题;Redis次要的性能瓶颈是内存和网络;内存好说,加内存条就行了,而网络才是大麻烦,所以Redis6内存好说,加内存条就行了;而网络才是大麻烦,所以Redis6.0引入了多线程的概念,Redis6.0在网络IO解决方面引入了多线程,如网络数据的读写和协定解析等,须要留神的是,执行命令的外围模块还是单线程的。 四、Redis优缺点1、长处 Redis是KV数据库,MySQL是关系型数据库,Redis速度更快;Redis数据操作次要在内存中,MySQL次要将数据存储在硬盘,Redis速度更快;Redis同样反对长久化(RDB+AOF),Redis反对将数据异步将内存的数据长久化到硬盘上,防止Redis宕机呈现数据失落的问题;Redis性能极高,读的速度是110000次/秒,写的速度是81000次/秒;Redis数据类型丰盛,不仅反对KV键值对,还反对list、set、zset、hash等数据结构的存储;Redis反对数据的备份,即master-slave模式的数据备份;Redis反对简略的事务,操作满足原子性;Redis反对读写拆散,分担读的压力;Redis反对哨兵模式,实现故障的主动转移;单线程操作,防止了频繁的上下文切换;采纳了非阻塞I/O多路复用机制,性能卓越; 2、毛病 数据存储在内存,容易造成数据失落;存储容量受内存的限度,只能存储大量的罕用数据;缓存和数据库双写一致性问题;用于缓存时,容易呈现内存穿透、缓存击穿、缓存雪崩的问题;批改配置文件后,须要进行重启,将硬盘中的数据同步到内存中,耗费的工夫较长,而且数据同步的工夫里Redis不能提供服务; 五、Redis常见业务场景 Redis是基于内存的nosql数据库,能够通过新建线程的模式进行长久化,不影响Redis单线程的读写操作通过list取最新的N条数据模仿相似于token这种须要设置过期工夫的场景公布订阅音讯零碎定时器、计数器缓存减速、分布式会话、排行榜、分布式计数器、分布式锁;Redis反对事务、长久化、LUA脚本、公布/订阅、缓存淘汰、流技术等个性; 六、Redis常见数据类型 1、String(1)String简介String 是最根本的 key-value 构造,key 是惟一标识,value 是具体的值,value其实不仅是字符串, 也能够是数字(整数或浮点数),value 最多能够包容的数据长度是 512M。(2)利用场景① 作为缓存数据库在Java管理系统体系中,大多数都是用MySQL存储数据,redis作为缓存,因为Redis具备撑持高并发的个性,通常能起到减速读写和升高数据库服务器压力的作用,大多数申请都会先申请Redis,如果Redis中没有数据,再申请MySQL数据库,而后再缓存到Redis中,以备下次应用。 ② 计数器Redis字符串中有一个命令INCR key,incr命令会对值进行自增操作,比方CSDN的文章浏览,视频的播放量,都能够通过Redis来计数,每浏览一次就+1,同时将这些数据异步存储到MySQL数据库中,升高MySQL服务器的写入压力。③ 共享session在分布式系统中,用户每次申请个别会拜访不同的服务器 ,这就会导致session不同步的问题,这时,个别会应用Redis来解决这个问题,将session存入Redis,应用的时候从Redis中取出就能够了。④ 分布式锁 setnx key value,加锁del key,开释锁 (3)key操作命令 (4)set key valueSET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL] EX seconds,设置过期工夫,单位秒PX milliseconds,设置过期工夫,单位毫秒EXAT timestamp-seconds,设置过期工夫,以秒为单位的UNIX工夫戳PXAT timestamp-milliseconds,设置过期工夫,以毫秒为单位的UNIX工夫戳NX,键不存在的时候设置键值XX,键存在的时候设置键值KEEPTTL,保留设置前指定键的生存工夫GET,返回指定键本来的值,若键不存在返回nil 备注:命令不辨别大小写,而key是辨别大小写的。help @类型:查看以后类型相干的操作命令。Since the SET command options can replace SETNX, SETEX, PSETEX, GETSET, it is possible that in future versions of Redis these commands will be deprecated and finally removed。(5)同时设置多个键值(6)获取指定区间范畴内的值getrange、setrange。(7)数值增减 ...

March 10, 2023 · 2 min · jiezi

关于express:为啥一个-main-方法就能启动项目

在 Spring Boot 呈现之前,咱们要运行一个 Java Web 利用,首先须要有一个 Web 容器(例如 Tomcat 或 Jetty),而后将咱们的 Web 利用打包后放到容器的相应目录下,最初再启动容器。在 IDE 中也须要对 Web 容器进行一些配置,才可能运行或者 Debug。而应用 Spring Boot 咱们只须要像运行一般 JavaSE 程序一样,run 一下 main() 办法就能够启动一个 Web 利用了。这是怎么做到的呢?明天咱们就一探到底,剖析一下 Spring Boot 的启动流程。概览回看咱们写的第一个 Spring Boot 示例,咱们发现,只须要上面几行代码咱们就能够跑起一个 Web 服务器:@SpringBootApplicationpublic class HelloApplication { public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args);}} 复制代码去掉类的申明和办法定义这些样板代码,外围代码就只有一个 @SpringBootApplication 注解和 SpringApplication.run(HelloApplication.class, args) 了。而咱们晓得注解相当于是一种配置,那么这个 run() 办法必然就是 Spring Boot 的启动入口了。接下来,咱们沿着 run() 办法来顺藤摸瓜。进入 SpringApplication 类,来看看 run() 办法的具体实现:public class SpringApplication { ......public ConfigurableApplicationContext run(String... args) { // 1 利用启动计时开始 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 2 申明上下文 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; // 3 设置 java.awt.headless 属性 configureHeadlessProperty(); // 4 启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 5 初始化默认利用参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 6 筹备应用环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); // 7 打印 Banner(Spring Boot 的 LOGO) Banner printedBanner = printBanner(environment); // 8 创立上下文实例 context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 9 构建上下文 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 10 刷新上下文 refreshContext(context); // 11 刷新上下文后处理 afterRefresh(context, applicationArguments); // 12 利用启动计时完结 stopWatch.stop(); if (this.logStartupInfo) { // 13 打印启动工夫日志 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 14 公布上下文启动实现事件 listeners.started(context); // 15 调用 runners callRunners(context, applicationArguments); } catch (Throwable ex) { // 16 利用启动产生异样后的解决 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { // 17 公布上下文就绪事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context;}......} ...

March 7, 2023 · 5 min · jiezi

关于express:从源码角度查看SpringBoot是怎样获取到Bean的

背景:咱们都晓得在SpringBoot启动类上增加@SpringBootApplication注解后执行main办法就能够主动启动服务 Spring会主动帮咱们找到须要治理的Bean的呢探索: 经典的八股文AbstractApplicationContext#refresh()办法 置信大家曾经比拟相熟了 进入invokeBeanFactoryPostProcessors()调用BeanFactory后置处理器办法 进入PostProcessorRegistrationDelegate的invokeBeanDefinitionRegistryPostProcessors办法 留神此办法执行后registry参数(BeanDefinitionRegistry)中的beanDefinitionMap会扫描到须要的bean信息 阐明此办法才是真正起到扫描作用的中央 重点!!! 持续进 兄弟们 往里进 ConfigurationClassPostProcessor#processConfigBeanDefinitions 两张图都是此办法 ps:代码太长 其中的这个parser.parse()就是真正解析的办法 ConfigurationClassParser#doProcessConfigurationClass到了 很近了 你要问我 我只能说 快到顶了 认真的同学应该曾经看进去了 图上的这个Set会获取@ComponentScan类扫描注解 而这个入参即为咱们的启动类Class 其中启动注解@SpringBootApplication中正蕴含了@CompentScan这个注解 所以此时这个Set中获取到了咱们的启动类 红线标注的这个中央持续走哦 componentScanAnnotationParser#parse中的scanner.doScan(StringUtils.toStringArray(basePackages)) 这里阐明一下这个basePackages因为咱们没有指定 所以默认是启动类所在的包门路 ps:这也是须要将启动类放到最外层包的起因 放外面的话无奈扫描到对应Bean ClassPathBeanDefinitionScanner#doScan 持续往里 还是那句 红线标注的中央 ClassPathScanningCandidateComponentProvider#scanCandidateComponents 好了 到站 请各位乘客下车吧 这个办法就是实在找到底层bean的中央 原理很简略 参数basePackage为咱们的包根门路 即启动类所在的门路 假如为com/juejin/drink 那么此办法会递归调用扫描com/juejin/drink下的所有类和目录 如果是须要注册的bean 那么放入new的LinkedHashSet中返回 通过如上步骤 程序会返回到PostProcessorRegistrationDelegate的invokeBeanDefinitionRegistryPostProcessors办法继续执行 但此时咱们的目标达到了 实际上SpringBoot就是通过@SpringBootApplication的@CompentScan注解 拿到启动类的包门路 最终去递归调用 获取到哪些是咱们标注了@Compent这些须要注册进容器的 此步骤是refresh办法的invokeBeanFactoryPostProcessors()中执行的 结语:本文只是简略的叙述了下Spring是如何将咱们的Bean加载到beanDefinitionMap中的 比较简单 不波及其余简单逻辑

February 27, 2023 · 1 min · jiezi

关于express:CompletableFuture实现异步编排

前言 为什么须要异步执行? 场景:电商零碎中获取一个残缺的商品信息可能分为以下几步:①获取商品根本信息 ②获取商品图片信息 ③获取商品促销流动信息 ④获取商品各种类的根本信息 等操作,如果应用串行形式去执行这些操作,假如每个操作执行1s,那么用户看到残缺的商品详情就须要4s的工夫,如果应用并行形式执行这些操作,可能只须要1s就能够实现。所以这就是异步执行的益处。 JDK5的Future接口 Future接口用于代表异步计算的后果,通过Future接口提供的办法能够查看异步计算是否执行实现,或者期待执行后果并获取执行后果,同时还能够勾销执行。列举Future接口的办法: get():获取工作执行后果,如果工作还没实现则会阻塞期待直到工作执行实现。如果工作被勾销则会抛出CancellationException异样,如果工作执行过程产生异样则会抛出ExecutionException异样,如果阻塞期待过程中被中断则会抛出InterruptedException异样。get(long timeout,Timeunit unit):带超时工夫的get()办法,如果阻塞期待过程中超时则会抛出TimeoutException异样。cancel():用于勾销异步工作的执行。如果异步工作曾经实现或者曾经被勾销,或者因为某些起因不能取消,则会返回false。如果工作还没有被执行,则会返回true并且异步工作不会被执行。如果工作曾经开始执行了然而还没有执行实现,若mayInterruptIfRunning为true,则会立刻中断执行工作的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断工作执行线程。isCanceled():判断工作是否被勾销,如果工作在完结(失常执行完结或者执行异样完结)前被勾销则返回true,否则返回false。isDone():判断工作是否曾经实现,如果实现则返回true,否则返回false。须要留神的是:工作执行过程中产生异样、工作被勾销也属于工作已实现,也会返回true。 应用Future接口和Callable接口实现异步执行:public static void main(String[] args) { // 疾速创立线程池ExecutorService executorService = Executors.newFixedThreadPool(4);// 获取商品根本信息(能够应用Lambda表达式简化Callable接口,这里为了便于察看不应用)Future<String> future1 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "获取到商品根本信息"; }});// 获取商品图片信息Future<String> future2 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "获取商品图片信息"; }});// 获取商品促销信息Future<String> future3 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "获取商品促销信息"; }});// 获取商品各种类根本信息Future<String> future4 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "获取商品各种类根本信息"; }}); // 获取后果try { System.out.println(future1.get()); System.out.println(future2.get()); System.out.println(future3.get()); System.out.println(future4.get());} catch (InterruptedException | ExecutionException e) { e.printStackTrace();}finally { executorService.shutdown();}}复制代码 ...

February 1, 2023 · 3 min · jiezi

关于express:express-router的初次使用

首先说下需要,mockApi指的就是模仿后盾,咱们在前台收回的申请能够不禁后盾来接管而是由咱们本人编写的模仿后盾来承受来初步验证前台的正确性也防止了前后台进度不一导致的开发上的工夫延误。 在之前的我的项目中,咱们所应用的mockApi的原理是拦挡由前台向后盾发动的申请,以做到不让后盾进行解决而是由咱们的mockApi进行解决。 而咱们当初用的是另一种解决办法——通过配置后缀来扭转其申请的地址,并且express router能够做到间接在前台接管url申请并进行自定义解决返回数据或是报错。 比方咱们能够设置为申请以api/结尾去申请后盾,以mockApi/结尾的申请去申请mockapi。 上面来说一下具体操作:此mockapi相当于是一个独立的node我的项目,能够npm run tart启动主体代码: import express, {Express, Request, Response} from 'express';import dotenv from 'dotenv';import bodyParser from 'body-parser';import md5 from 'md5';import {base64encode} from 'nodejs-base64';import {OaController} from './controller/oa.controller';const fs = require('fs');// 获取.env文件中的配置信息dotenv.config();// 初始化express, 端口,json 解析器const app: Express = express();const port = process.env.PORT;//咱们能够间接在此文件中书写接口并做出相应解决//辨认get办法,url:/api/hrm/resful/app.get('/', (req: Request, res: Response) => { res.send('Express + TypeScript Server');});//实体化rouerconst router = express.Router();//调用静态方法,解决对应的url并返回mock数据OaController.registerRouter(router);//设定url前缀app.use('/api/hrm/resful', router);app.listen(port, () => { console.log(`⚡️[server]: Server is running at http://localhost:${port}`);});从这里咱们能够看出这个我的项目很简略,咱们能够通过.env文件来配制我的项目启动的端口从而失常启动我的项目;.env: PORT=8088//静态方法,通过传入的express中的router来实现express解决url的性能import {Router, Request, Response} from 'express';export class OaController { private static instance: OaController; static registerRouter(router: Router): OaController { if (!OaController.instance) { OaController.instance = new OaController(); } router.post('/page, (res, req) => { OaController.instance.getHrmUserInfoWithPage(res, req); }); return OaController.instance; } //解决page申请,设定其响应 private page(res: Request, req: Response) { req.send({ code: 400, message: 'Internal Error' }) }}由此咱们能够看出通过express构建的mockApi更贴近于理论后盾,咱们能够对其进行更多的操作,如依据传入的数据进行更为实在的错误处理而不是简略的进行断言。 ...

November 1, 2022 · 1 min · jiezi

关于express:学习express从这里开始

一、express 是什么?Express 是一个简洁而灵便的 node.js Web利用框架,能够疾速地搭建一个性能残缺的网站。 二、装置express并创立我的项目通过 express-generator 创立带有根本配置的 express 我的项目,包含 路由(routes)、package.json、view(hbs模板)等,能够间接编写业务代码了。 # 1、装置工具npm install -g express-generator# 2、创立我的项目:learn-expressexpress --view=hbs ~/learn-express && cd ~/learn-express# 3、初始化我的项目npm install# 4、启动npm start# 5、拜访:http://localhost:3000/三、新增页面应用下面新建我的项目 learn-express,因为 routes、views 曾经配置结束,间接新建一个页面并用 http 的 get 办法获取,步骤如下: 采纳 vs code 关上下面我的项目。vs code 下载新建 routes/test.js 文件 var express = require('express');var router = express.Router();/* GET test page. */router.get('/', function(req, res, next) { res.render('test', { title:'测试页面' });});module.exports = router;新建 views/test.hbs 文件 <h1>{{title}}</h1>批改 app.js ,减少路由,找到对应中央减少上面内容 var testRouter = require('./routes/test');app.use('/test', testRouter);重启服务,即可通过 http://localhost:3000/test 拜访新增的页面。四、POST 提交页面应用 form 表单 提交数据,后端解决表单提交的数据。 ...

November 24, 2021 · 1 min · jiezi

关于express:express实现通过浏览器url地址访问音乐资源

成果实现某些音乐网站会提供一些音乐外链,以供用户拜访,听或下载music。不过可能明天这个外链还能用,今天就不能用了,“过期了”之类的。这篇文章记录了,应用nodejs框架express,实现一个简略的“mp3音乐资源服务器”性能。咱们先看一下效果图 浏览器url拜访到资源当前,Ctrl+S能够间接下载哦效果图有音乐资源返回音乐资源 没音乐资源返回文字提醒 代码实现app.js文件// 引入express插件包并生成一个实例appconst express = require('express')const app = express()// 应用body-parser中间件解析post申请主体app.use(express.urlencoded({ extended: false }))app.use(express.json())const Router = require('./router') // 引入分模块治理的路由// 路由分模块app.use(Router) // 在10000端口上启动后端服务app.listen(10000, (req,res) => { console.log('后端服务端口地址为:http://localhost:10000');})router.js文件次要看这个代码 const express = require('express') // 引入expressconst route = express.Router() // 实例化一个路由对象// 引入文件读取模块const fs = require('fs')// 动静路由传参形式,通过params获取动静参数route.get('/musicSrc/:fileName', (req, res) => { // console.log('获取文件名参数', req.params.fileName); // 拼接成残缺的文件名,这里假如对立应用mp3格局的音乐文件 let fileName = req.params.fileName // 破茧.mp3 try { // 存储一份音乐的门路,这里咱们在music文件夹外面寄存音乐资源 let mp3Url = './music/' + fileName // fs.statSync判断目录文件是否存在,不存在就会抛出异样,所以须要try catch捕捉一下 let stat = fs.statSync(mp3Url) // 设置申请头 res.writeHead(200, { // 有的话,就把对应的资源以流的模式返回去 'Content-Type': 'audio/mp3', // 类型为音频mp3格局 'Content-Length': stat.size, // 指定一下文件大小 "Accept-Ranges": "bytes" // 不加的话,前端google浏览器中音频无奈拖动 }) //创立可读流 let readStream = fs.createReadStream(mp3Url) // 将读取的后果以管道pipe流的形式返回给前端 readStream.pipe(res); } catch (error) { // 读取不到相应文件,就间接返回找不到即可 res.send('暂无此音乐数据') }})module.exports = route // 裸露给app.js方便管理目录结构图次要看左侧的music文件夹 ...

November 1, 2021 · 1 min · jiezi

关于express:expresswinston-库的学习笔记

库地址 express-winston 为 express.js 应用程序的申请和谬误记录提供中间件。 它应用“白名单”从申请和(0.2.x 中新增的)响应对象中抉择属性。 要应用 express-winston,您须要将以下内容增加到您的应用程序中: 在 package.json 中: { "dependencies": { "...": "...", "winston": "^3.0.0", "express-winston": "^4.0.4", "...": "..." }}server.js: var winston = require('winston'), expressWinston = require('express-winston');Request Logging应用 expressWinston.logger(options) 创立一个中间件来记录您的 HTTP 申请。 var router = require('./my-express-router'); app.use(expressWinston.logger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ), meta: true, // optional: control whether you want to log the meta data about the request (default to true) msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}" expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true colorize: false, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red). ignoreRoute: function (req, res) { return false; } // optional: allows to skip some log messages based on request and/or response })); app.use(router); // notice how the router goes after the logger.参数定义: ...

October 27, 2021 · 5 min · jiezi

关于express:express-vue-搭建项目

创立目录为了方便管理我的项目,创立根目录,外部分成两局部,server 和 viewserver 用来搭建 express 框架view 用来搭建 vue 框架搭建 express 框架全局装置 express 脚手架,并检测是否装置胜利npm install express-generator -g检测是否装置胜利express --version在 server 文件夹中创立 express 我的项目express 项目名称目录构造 bin 目录下的 www 文件用来启动我的项目public 目录用来寄存动态文件router 目录用来寄存路由文件,对应资源和引入门路的问题view 目录用来寄存模板引擎文件app.js 文件是整个我的项目的入口文件 装置依赖、启动我的项目npm installnpm start默认拜访 localhost:3000如果呈现上面的内容,阐明启动胜利 初始化的脚手架中,router 目录下存在 user.js,用来退出本人的接口内容router.get('/', function(req, res, next) { res.send('respond with a resource');});在根目录 app.js 中,无关 user.js 的配置为var usersRouter = require('./routes/users');app.use('/users', usersRouter);此时在浏览器中拜访路由 users,如果呈现 respond with a resource 阐明脚手架搭建胜利 搭建 vue 框架全局装置脚手架npm install -g @vue/cli检测是否装置胜利vue -V在 view 文件夹中创立 vue 我的项目vue create 项目名称目录构造 ...

September 15, 2021 · 2 min · jiezi

关于express:express中使用nodexlsx插件下载excel表格

node-xlsx是一个轻量级的excel插件,下载导出excel根本的性能这个插件都能实现,本文记录一下express框架中应用node-xlsx插件下载excel表格的步骤。状况一、读取本地文件并返回前端excel流文件这种状况实用于下载excel模板场景,毕竟模板是固定的内容,咱们在代码的文件夹中寄存一个固定的excel模板,读取并返回即可。第一步,必定是要下载安装这个插件npm i node-xlsx第二步,在对应代码中引入这个插件const xlsx = require('node-xlsx')第三步,就是在对应的路由url中写对应代码,代码如下:// excel导出下载模板接口route.get("/exportExcel", (req, res) => { // 首先,读取本地excel模板文件,并解析成node-xlsx插件须要的数据格式, // (比方我的表格文件在代码中的excel文件夹下)要引入fs文件模块能力读取哦 const dataByParse = xlsx.parse(fs.readFileSync('./excel/统计模板.xlsx')); /* 打印进去的数据是一个数组,数组中的每一项(每一个对象)都是一个sheet数据,name属性指定的是每一个sheet的名字 data属性是一个数组,数组中寄存的是表格对应每个sheet的数据,data数组中的第一项是“表头”的数据,也能够了解为是 第一行的数据,前面的每一项就是对应每一行“表体”的数据,具体格局,后续也会举例。 */ console.log("解析数据格式",dataByParse); // 最初一步,应用xlsx插件自带的build办法将解析后的数据转换成为excel表格(buffer模式的流文件) // 以流文件的模式返回给前端,前端接管解析下载即可 res.send(xlsx.build(dataByParse))})node-xlsx须要的数据格式举例子比方这样的数据格式,咱们看一下数据结构 let excelData = [ // 第一个sheet内容 { name:"我是sheet1", // 给第一个sheet指名字 data:[ // 留神,这里是一个二维数组 ["姓名","年龄","他乡","备注"], // 第一行 ["孙悟空","500","花果山","人送外号斗战败佛"], // 第二行 ["猪八戒","88","高老庄","天蓬元帅"], // 第三行 ] }, // 第二个sheet内容 { name:"我是sheet2", // 给第二个sheet指名字 data:[ ["城市","国家","人口","经济程度"], // 同上 ["上海","中国","14亿","越来越好"], ["伦敦","英国","7000万","还行"], ["华盛顿","美国","3.4亿","凑活"] ] } ]上述数据格式对应效果图很显然,数据结构和对应导出的excel后果都是对应的 ...

September 1, 2021 · 1 min · jiezi

关于express:express基本使用

1.express基于nodejs平台、疾速、凋谢、极简的Web开发框架,官网地址(中文版),官网地址 1.根本应用下载expressnpm install express --save引入express模块 let express = require('express');构建服务实例 // 构建服务实例 相当于 http.createServer();let app = express();接管服务端申请 // 当服务端收到 get申请 / 的时候,执行回调函数app.get('/',(req,res) => { res.send('Hello World');})绑定端口 // 绑定端口 相当于http.listen()app.listen(3000,()=> { console.log('server is running...');})残缺代码 let express = require('express');// 构建服务实例 相当于 http.createServer();let app = express();// 公开指定目录,则能够通过/public间接进行拜访其文件夹内的内容,能够写多个,灵便应用app.use('/public/',express.static('./public/'));// 当服务端收到 get申请 / 的时候,执行回调函数app.get('/',(req,res) => { // 在express中能够间接通过req.query来获取查问字符串参数 console.log(res.query); res.send('Hello World');})// 绑定端口 相当于http.listen()app.listen(3000,()=> { console.log('server is running...');})如果拜访其余的门路下的内容,express框架默认会解决为404,并显示相干的提示信息。如果要写多个门路下申请的解决,则能够写多个app.get(),不用像nodejs原生写http服务一样本人判断。同时如果要对某个门路下的资源进行凋谢,能够采取以下的代码进行配置 // 公开指定目录,则能够通过/public间接进行拜访其文件夹内的内容,能够写多个,灵便应用// 第一个参数配置客户端能怎么样进行拜访,第二个参数是服务器端绝对于以后文件的文件门路app.use('/public/',express.static('./public/'));

March 11, 2021 · 1 min · jiezi

关于express:vuepress打包项目如何在express框架渲染

如果要在 express 我的项目上加一个 vuepress 写的文档应如何渲染? Vuepress 是 Vue 驱动的动态网站生成器1、.vuepress/config.js 批改 base 配置指定动态资源目录,如:test目录 base: "/test/",2、打包去缓存配置 package.json "scripts": { "build": "vuepress build docs --no-cache"},3、把打包目录 dist 复制到 express 我的项目 public 目录下 4、路由配置 router.get('/test', function(req, res, next) { res.render('test');});视图目录:views/test.html 5、把 dist 目录下的 index.html 重命名为 test.html 而后剪切替换 views/test.html 即可 拜访:http://localhost/test 欢送关注:技术开发分享录

March 10, 2021 · 1 min · jiezi

关于express:第四代Express框架koa简介

简介相熟Spring MVC的敌人应该都分明Spring MVC是基于servlet的代码框架,这是最传统的web框架。而后在Spring5中引入了Spring WebFlux,这是基于reactive-netty的异步IO框架。 同样的,nodejs在最后的Express 3根底上倒退起来了异步的koa框架。koa应用了promises和aysnc来防止JS中的回调天堂,并且简化了错误处理。 明天咱们要来介绍一下这个优良的nodejs框架koa。 koa和expresskoa不再应用nodejs的req和res,而是封装了本人的ctx.request和ctx.response。 express能够看做是nodejs的一个利用框架,而koa则能够看成是nodejs 的http模块的形象。 和express提供了Middleware,Routing,Templating,Sending Files和JSONP等个性不同的是,koa的性能很繁多,如果你想应用其余的一些性能比方routing,sending files等性能,能够应用koa的第三方中间件。 koa并不是来替换express的,就像spring webFlux并不是用来替换spring MVC的。koa只是用Promises改写了控制流,并且防止了回调天堂,并提供了更好的异样解决机制。 koa应用介绍koa须要node v7.6.0+版本来反对ES2015和async function。 咱们看一个最最简略的koa利用: const Koa = require('koa');const app = module.exports = new Koa();app.use(async function(ctx) { ctx.body = 'Hello World';});if (!module.parent) app.listen(3000);koa应用程序就是一个蕴含了很多个中间件的对象,这些中间件将会依照相似stack的执行程序一个相应request。 中间件的级联关系koa.use中传入的是一个function,咱们也能够称之为中间件。 koa能够use很多个中间件,举个例子: const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => { await next(); console.log('log3');});app.use(async (ctx, next) => { await next(); console.log('log2');});app.use(async ctx => { console.log('log3');});app.listen(3000);下面的例子中,咱们调用了屡次next,只有咱们调用next,调用链就会传递到下一个中间件进行解决,始终到某个中间件不再调用next为止。 ...

November 30, 2020 · 2 min · jiezi

关于express:express中异步函数异常捕获

在express中时应用 Async/await 编写异步代码时,每个 async 函数都要包裹在try/catch中,代码量多了看着冗余不优雅,express又不像koa的异步机制能够订阅全局的error事件,为了解决这个问题,须要写个捕捉异步函数异样的中间件。 uncaughtException开始能想到的必定是try/catch了,然而也想过是否应用nodejs提供的uncaughtException事件,在全局捕捉异样,例如上面的代码: process.on("uncaughtException", (err) => console.log("uncaught Exception"));const asyncError=()=>{ throw new Error("some Error");}asyncError();asyncError办法外面抛出的异样会被 uncaughtException订阅,然而在异步函数中,并没走到 uncaughtException,还是会抛出异样: process.on("uncaughtException", (err) => console.log("uncaught Exception"));const asyncError=()=>{ throw new Error("some Error");}(async ()=>{ // 抛出异样 asyncError();})()而且Promise.reject也没走到uncaughtException外面: const asyncError=()=>{ return Promise.reject("some error")}(async ()=>{ // 抛出异样 await asyncError();})()所以在express中应用nodejs提供的uncaughtException解决异步谬误不太适合,一方面没法捕捉和定位上下文谬误,另一方面也没法将谬误异样提供给中间件函数解决 解决思路要解决express中的异步函数谬误,最好的办法当然是编写解决异样的中间件了,try/catch开路,包裹中间件办法,catch到的异样间接交给next函数解决,代码如下: const asyncHandler = fn =>{ return (req,res,next)=>{ try{ fn(req,res,next) }catch(next) }}module.exports = asyncHandler;接下来,在异步函数中引入中间件解决: app.use(asyncHandler(async(req, res, next) => { await authenticate(req); next();}));app.get('/async', asyncHandler(async(req, res) => { const result = await request('http://example.com'); res.end(result);}));应用asyncHandler办法包裹的async/await函数,如果呈现谬误就会被Error-handling中间件捕捉了 ...

November 27, 2020 · 1 min · jiezi

关于express:express-学习笔记慕课

(本人温习用)一、 创立脚手架sudo npm i express-generator -gmkdir express-clicd express-cliexpress express-testnpm intsallnpm start 二、第二步(不晓得什么题目了) nodemon监听代码文件变动,随时重启cross-env 不用放心平台设置或应用环境变量npm i nodemon cross-env --save-dev bin:编译的可执行文件www 为了提供一个http的服务。 publick 、views 前端用。 // package.json "dev": "cross-env NODE_ENV=dev nodemon ./bin/www"三、介绍express的入口文件 各个插件的作用。 var cookieParser = require('cookie-parser'); 解析cookievar logger = require('morgan'); 记录日志用 app 本次http 申请的实例 var express = require('express');var router = express.Router();/* GET home page. */router.get('/', function(req, res, next) { res.render('index', { title: 'Express' });});module.exports = router;四、演示如何解决路由新建路由文件user.js, var express = require('express');var router = express.Router();/* GET users listing. */router.post('/login', function (req, res, next) { const { username, password } = req.body; res.json({ error: 0, data: { username, password } })});module.exports = router;poatman 申请形式: ...

November 10, 2020 · 1 min · jiezi

关于express:三步法解析Express源码

关注公众号“执鸢者”,获取大量教学视频及私人总结面筋并进入业余交换群.在抖音上有幸看到一个程序员讲述如何浏览源代码,次要分为三步:领悟思维、把握设计、领会细节。 领悟思维:只需领会作者设计框架的初衷和目标把握设计:只需领会代码的接口和抽象类以及宏观的设计领会细节:是基于顶层的形象接口设计,逐步开展代码的画卷基于上述三步法,急不可待的拿Express开刀了。本次源码解析有什么不到位的中央各位读者能够在上面留言,咱们一起交换。 一、领悟思维在Express中文网上,介绍Express是基于Node.js平台,疾速、凋谢、极简的Web开发框架。在这句话外面能够失去解读出以下几点含意: Express是基于Node.js平台,并且具备疾速、极简的特点,阐明其初衷就是为了通过扩大Node的性能来进步开发效率。凋谢的特点阐明该框架不会对开发者过多的限度,能够自在的施展设想进行性能的扩大。Express是Web开发框架,阐明作者的定位就是为了更加不便的帮忙咱们解决HTTP的申请和响应。二、把握设计了解了作者设计的思维,上面从源码目录、外围设计原理及形象接口三个层面来对Express进行整体的把握。2.1 源码目录如下所示是Express的源码目录,相比拟来说还是比较简单的。├─application.js---创立Express利用后可间接调用的api均在此处(外围)<br/>├─express.js---入口文件,创立一个Express利用<br/>├─request.js---丰盛了http中request实例上的性能<br/>├─response.js---丰盛了http中response实例上的性能<br/>├─utils.js---工具函数<br/>├─view.js---与模板渲染相干的内容<br/>├─router---与路由相干的内容(外围)<br/>| ├─index.js<br/>| ├─layer.js<br/>| └route.js<br/>├─middleware---与中间件相干的内容<br/>| ├─init.js---会将新减少在request和response新减少的性能挂载到原始申请的request和response的原型上<br/>| └query.js---将申请url中的query局部增加到request的query属性上<br/> 2.2 形象接口对源码的目录构造有了肯定理解,上面利用UML类图对该零碎各个模块的依赖关系进一步理解,为后续源码剖析打好根底。 2.3 设计原理这一部分是整个Express框架的外围,下图是整个框架的运行流程,一看是不是很懵逼,为了搞清楚这一部分,须要明确四个概念:Application、Router、Layer、Route。 为了明确上述四个概念,先引入一段代码const express = require('./express');const res = require('./response');const app = express();app.get('/test1', (req, res, next) => { console.log('one'); next();}, (req, res) => { console.log('two'); res.end('two');})app.get('/test2', (req, res, next) => { console.log('three'); next();}, (req, res) => { console.log('four'); res.end('four');})app.listen(3000);Application示意一个Express利用,通过express()即可进行创立。Router<路由零碎,用于调度整个零碎的运行,在上述代码中该路由零碎蕴含app.get('/test1',……)和app.get('/test2',……)两大部分Layer代表一层,对于上述代码中app.get('/test1',……)和app.get('/test2',……)都能够成为一个LayerRoute 一个Layer中会有多个处理函数的状况,这多个处理函数形成了Route,而Route中的每一个函数又成为Route中的Layer。对于上述代码中,app.get('/test1',……)中的两个函数形成一个Route,每个函数又是Route中的Layer。理解完上述概念后,联合该幅图,就大略能对整个流程有了直观感触。首先启动服务,而后客户端发动了http://localhost:3000/test2的申请,该过程应该如何运行呢? 启动服务时会顺次执行程序,将该路由零碎中的门路、申请办法、处理函数进行存储(这些信息依据肯定构造存储在Router、Layer和Route中)对相应的地址进行监听,期待申请达到。申请达到,首先依据申请的path去从上到下进行匹配,门路匹配正确则进入该Layer,否则跳出该Layer。若匹配到该Layer,则进行申请形式的匹配,若匹配形式匹配正确,则执行该对应Route中的函数。上述解释的比较简单,后续会在细节局部进一步论述。 三、领会细节通过上述对Express设计原理的剖析,上面将从两个方面做进一步的源码解读,上面流程图是一个常见的Express我的项目的过程,首先会进行app实例初始化、而后调用一系列中间件,最初建设监听。对于整个工程的运行来说,次要分为两个阶段:初始化阶段、申请解决阶段,上面将以app.get()为例来论述一下该外围细节。 3.1 初始化阶段上面利用app.get()这个路由来理解一下工程的初始化阶段。 首先来看一下app.get()的内容(源代码中app.get()是通过遍历methods的形式产生) app.get = function(path){ // …… this.lazyrouter(); var route = this._router.route(path); route.get.apply(route, slice.call(arguments, 1)); return this;};在app.lazyrouter()会实现router的实例化过程 ...

October 17, 2020 · 3 min · jiezi

关于express:建立一个PDF转docx的在线服务

首先,构建一个typeScript的express利用: package.json { "name": "pdf2docx", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "nodemon ./src/index.ts", "build": "tsc --project ./", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "codetyphon", "license": "ISC", "dependencies": { "@types/multer": "^1.4.4", "cors": "^2.8.5", "express": "^4.17.1", "multer": "^1.4.2", "winax": "^1.20.0" }, "devDependencies": { "@types/cors": "^2.8.7", "@types/express": "^4.17.8", "@types/node": "^14.11.2", "nodemon": "^2.0.4", "ts-node": "^9.0.0", "tslint": "^6.1.3", "typescript": "^4.0.3" }}tsconfig.json { "compilerOptions": { "target": "es6", "module": "commonjs", "rootDir": "./src", "outDir": "./build", "esModuleInterop": true, "strict": true }}srcindex.ts ...

October 14, 2020 · 3 min · jiezi

微信公众号网页开发

基本配置1.设置—公众号设置—功能设置—配置JS接口安全域名安全域名配置规则如下 2.开发—基本配置开发者密码第一次使用需要重新设置记录 开发者ID(AppID) 开发者密码(AppSecret)后面会用到 3.IP白名单配置推荐填写当前本地开发IP地址和服务器IP地址本地开发地址获取方式服务器IP地址(根据自己的服务器Ip地址自行填写)多个IP地址填写用回车隔开 4重要的一步在:微信公众号-开发-接口权限查看想要调用的开发接口是否可用如果有相关接口权限无法开启,推荐使用:微信公众平台-开发-开发者工具-公众平台测试帐号开发 开始开发1.引入JS文件在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/... 2通过config接口注入权限验证配置(最重要的一步)wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,需要使用的JS接口列表});appID(前面在微信公众号基本配置中已经拿到了)jsApiList:['uploadImage','updateAppMessageShareData'] (例:上传图片接口,和自定义分享接口) 签名算法(微信官方提供)jsapi_ticket生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):https://developers.weixin.qq....用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi...2.1签名获取拆解第一步GET请求access_tokenaccess_token的有效期为7200秒(不必反复请求)https://api.weixin.qq.com/cgi... grant_type是获取access_token填写client_credentialappid是第三方用户唯一凭证secret是第三方用户唯一凭证密钥,即appsecret**appid 和 secret 在前面的基本配置中其实都已经拿到。但是由于开发者密码(AppSecret)是校验公众号开发者身份的密码,具有极高的安全性。不能直接暴露在前端代码中,所以access_token的请求需在后端完成,这里签名的生成过程都在后端完成。 当前以node搭建后端服务//获取到access_token示例var url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`; request(url, function (error, response, body) { if (!error && response.statusCode == 200) { console.log("access_token值" +JSON.parse(body).access_token) } });第二步GET请求jsapi_ticketjsapi_ticket的有效期为7200秒(不必反复请求)https://api.weixin.qq.com/cgi...用第一步获取到的access_token的值进行请求 //var url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi` request(url, function (error, response, body) { if (!error && response.statusCode == 200) { console.log("jsapi_ticket值" + JSON.parse(body).ticket); } });第三步生成算法签名const timestamp = parseInt(Date.now() / 1000) //生成签名的时间戳const nonceStr = Math.random().toString(36).substr(2, 15) //生成签名的随机串let jsapi_ticket //在第二步生成let url//签名用的url必须是调用JS接口页面的完整URL(前端请求服务端接口带入) ...

October 16, 2019 · 2 min · jiezi

模型设计对博客中评论的合理建模-MongoDB

最近,闲着没事,又把上个月写得代码拿出来了,随便完善一下没完成的评论的路由接口。 评论应该是在整个博客数据存储中,模型最为复杂的一部分了。首先要考虑的是和文章进行关联。这个可以用 mongoDB 的 ref 进行关联,随后可以使用 populate 计算出被关联的字段。 最后关系复杂的是父子层级的评论,又或者是多级评论。这个时候就要想该怎么做才能合理的管理这些层级关系,在删除父评论的同时又能把所有子评论一起删除。查询的时候如何去由根到叶顺序输出层级关系。 建立评论模型const schema = new mongoose.Schema({ // comment id cid: { type: Number, required: true, unique: true }, // post id pid: { type: Number, required: true }, post: { type: mongoose.SchemaTypes.ObjectId, ref: 'Post' }, content: { type: String, required: true }, createTime: { type: Number, default: Date.now() }, author: { type: String, required: true }, owner: { type: String, required: true }, isOwner: { type: Boolean, required: true }, email: { type: String }, url: { type: String }, key: { type: String, required: true, unique: true }, parent: { type: mongoose.SchemaTypes.ObjectId, ref: 'Comment' }, hasChild: { type: Boolean, default: false }, ipAddress: { type: String, required: true }, userAgent: { type: String }, // 0 审核 1 发布 2 垃圾 state: { type: Number, required: true, default: 0 }})在模型中,post列中关联引用表(post表)的 _id(文章),在 hasChild 中记录是否存在回复。在后期处理回复路由的时候不要忘记修改他的值。最关键的是 key 列,这个用来记录平行层级。如 post 中的一篇 pid 为 11 的文章下有一条评论,那么 key 中命名 11#001,这是第一条评论,如果该评论下存在一条回复,则回复的 key 为 11#001#001,下层亦是如此。使用该命名方式可以容纳的每条评论的回复量为 999,可以根据需求调整0的数量。 ...

October 14, 2019 · 2 min · jiezi

nodeexpressnginx架构关于上传文件出现-Request-Entity-Too-Large-的问题

解决方法分两步走: 1、修改express框架设置请求的允许最大值将原框架中这两行代码: app.use(bodyParser.json());app.use(bodyParser.urlencoded({ extended: false }));修改为: app.use(bodyParser.json({limit: '50mb'})); // 这里limit值可以根据实际情况自由设定app.use(bodyParser.urlencoded({ limit: '50mb', extended: true}));重启服务继续上传大文件,如果问题解决,到此OK。如果仍然没有解决就有可能是代理nginx配置文件设置的问题,进入第二步。 2、修改nginx的配置文件nginx.conf在 http{} 中添加或者修改client_max_body_size设置 http { client_max_body_size 100m; //添加或修改本行配置,最大允许值可根据需求自由设定 include mime.types; default_type application/octet-stream; ...}修改后上传覆盖掉原来的配置 路径/usr/local/nginx/conf/nginx.conf 一般路径是这样,特殊情况自己去查。重启nginx nginx -s reload或者/usr/local/nginx/sbin/nginx -s reload问题解决。

September 10, 2019 · 1 min · jiezi

高德全链路压测平台TestPG的架构与实践

导读2018年十一当天,高德DAU突破一个亿,不断增长的日活带来喜悦的同时,也给支撑高德业务的技术人带来了挑战。如何保障系统的稳定性,如何保证系统能持续的为用户提供可靠的服务?是所有高德技术人面临的问题,也是需要大家一起解决的问题。 高德业务规模支撑一亿DAU的高德服务是什么体量?可能每个人的答案都不相同,这里从基础设施的角度给大家做个简单的介绍,我们有数千个线上应用,分别部署在全国各地多个机房中的数万台机器上。 这张图是高德业务核心链路的架构,从图中可以看出高德业务具有相当高的复杂性。当然,真实系统远远要比图表示的复杂,如果用这张图来代表高德整体业务形态,无异于管中窥豹,太过于片面。 对于如此大规模,高复杂度的系统,如何保障系统的稳定性,是高德技术人长期面临和解决的问题。 保障稳定性的手段 如何保障系统稳定性是几乎所有互联网企业都需要面对的问题。通常来讲,有五种手段来从理论上保障系统的稳定性,分别是: 容量规划:根据以往业务的流量,估算出未来(通常是即将来临的大促,节假日)的流量。以整体流量为基础,估算出每个子系统需要满足的容量大小。然后根据子系统的容量来计算出需要的资源数量,对系统进行适当的扩容。计算方式可以简单的表示为如下公式:<p style="text-align:center">机器数量 = 预估容量 / 单机能力 + Buffer (一定数量的冗余)</p> 流量控制:系统需要防止流量超过设计的容量,对超出设计的流量进行限流。各业务也需要对超出子系统服务能力的流量进行限流,对超负荷的服务进行降级。灾备:一旦系统发生灾难性故障,需要将流量切换到容灾机房,避免对大量用户造成损失。监控:对服务进行全方面的监控,实时掌控系统的状态,对系统中出现的问题及时预警,做到早发现,早治理。预案演练:对系统可能面临的问题要进行全面的预演,结合断网,断电等等灾难模拟的手段来检验系统在灾难面前的表现。有了稳定性保障的五大法宝,我们是否就可以高枕无忧了呢?答案是令人遗憾的,这里有两个残酷的现实例子,告诫我们不要太乐观。 多年前的某年春节前夕,我们对高德核心链路进行了压测,压测设计的流量要高于预估的春节流量,系统在当时表现良好,各项指标都满足要求。可是春节期间,服务因某种原因发出告警,而此刻线上流量的水位并没有超过我们的预期。 还有一次在某年五一期间,该服务再次发出预警,而且和春节的那次预警的原因不一样。 我们的稳定性保障手段是基于对于系统的认知来实现的,而认知往往是真实世界在头脑中的映射,是一种模型,或是真实系统的快照。系统的真实状态往往和我们观测到的不太一致,基于观测到的模型对系统进行的测量也往往会不够准确,对于系统,我们要保持敬畏。对系统进行不厌其烦的模拟,无限的接近真实系统的状态,是永恒不变的追求。 上述稳定性保障的工作,只能在理论上保证系统的抗压能力,但是不能确定在真实流量到来的时候,系统的表现如何! 因此,我们需要演习,需要让真实的流量提前到来! 全链路压测如何让真实的流量提前到来?我们需要借助全链路压测,那么什么是全链路压测呢? 我的理解是:把全链路压测拆分为两个部分来看。一是全链路,一是压测,分别来理解: 全链路:分为两层意思;一是自顶向下,一个请求在系统中经过的完整路径。二是一系列动作的集合,比如从用户登录,到浏览商品,到选择商品,到加入购物车,到支付等等这整个环节。对于高德业务而言,我们关注的是第一种全链路。压测:通过对海量用户行为模拟的方式,对系统逐步施压,用于检验系统的稳定性。集团的战友们把全链路压测比作 "终极武器",非常的形象。既然是 "终极武器",那就需要有足够的威慑力,对于高德来说,目标是:提供真实的流量,在真实的环境中来检验系统的稳定性。 这里面包含三个关键点: 真实的流量:流量的量级和特征贴近真实的世界。真实的环境:直接在线上环境进行。提前进行:在流量洪峰到来之前。做到这三点,才能称得上是一次真正意义上的演习,才可以叫做全链路压测。 高德全链路压测面临的挑战高德全链路压测面临众多方面的挑战,除了分布式系统固有的挑战外,高德全链路压测还面临着业务特殊性的挑战。 分布式系统的特性 不确定性 我们认为的流量有序的,而真实的流量却是随机的,不确定的。 抖动性在理论的世界里面,吞吐量是请求量的函数,可以表示为如下的公式: <p style="text-align:center">Throughput=f(load)</p> 如果没有其他因子的干扰,在系统达到饱和之前,吞吐量和请求量的关系应该是线性的。而现实世界里面,这种理论模型几乎是不可能出现的。为什么?因为抖动。为什么会出现抖动?因为网络,磁盘等等的不确定性。 排队系统的特性 我们的业务可以简单的抽象成为一个排队系统,请求从左边随机的进入系统,等待被处理,处理完成之后,从右边离开队列。在系统未达到饱和状态时,系统可以很好的处理用户的请求,而一旦队列接近饱和,那么队列的长度可能会显著的增加,而且请求的平均响应时间也会出现增加,甚至会出现部分请求超时的情况。对于一个理论上能处理 1000q/s的系统,在不同流量的情况下,可能的状态如下(特定系统的测量结果): 从图中可以看出: 请求达到率增加2倍,队列的长度会增加约60倍。当系统接近饱和时,请求端极小的变化将会对系统造成很大的影响。请求达到率从0.95 ~ 0.99,队列的长度将增加40%。排队系统的特征是:系统会在接近饱和时出现拥堵,拥堵会导致服务的时间变长,而这反过来又会加重拥堵的程度。随着情况的恶化,系统的资源(cpu,mem)最终会被耗尽。拥堵和服务质量下降表现为正相关性,这种特性会随着时间急剧的恶化,最终拖垮系统,出现故障。 高德业务特点 出行业务有其特有的特殊性,会受到诸多因素的影响。具体到高德业务而言,系统的行为会受到区域,地形,路况,路网密度,季节,天气,政府活动等等因素的影响。 以驾车导航为例,导航系统会受到如下因素的影响: 区域:不同的区域经济发展水平不一致,人们选择出行的交通工具也会不一样,经济发达地区的人们汽车拥有率会更高,使用汽车出行的频率也会更高。地形:山区,城市繁华地区会因gps信号遮挡导致定位不准确,可能被系统认为偏航,从而引发路径重新规划。路况:事故,拥堵,施工,限行,管制 这些路况都对导航服务造成影响。路网密度:导航算法所要求的计算资源和路网的密度有很强的关联,路网越密,路径规划所消耗的cpu资源,内存资源就会越大,同时计算的时间也会越长。距离:路径规划的距离越长,导航算法对计算资源的要求就越高。季节&天气:人们的出行行为和季节也会呈现相关性,如 五一,端午 人们可能集中前往景区旅游。十一,春节 人们可能会集中返乡。这样在导航行为上就会出现导航距离分布不同的情况,不同的导航距离对服务的要求会不一样,越长距离的导航对服务资源的要求越高。政府活动:交通管制,修路,限行等等。对于高德而言不能单纯的通过放大流量来对系统进行压测,在流量构造阶段我们需要考虑到流量的特征,考虑到所有影响服务行为的因素。 高德全链路压测平台的自建起因身在阿里,说起全链路压测,首先想到的肯定是大名鼎鼎的Amazon平台,Amazon 诞生于2013年,自2013年起,Amazon一直作为淘宝、天猫稳定性保障体系中神一般的存在。经过多年的发展和演进,Amazon平台已经日臻稳定和成熟,在施压能力,链路治理,数据构造,用户模拟方面已经做到极致。 所以,在落地高德全链路压测的时候,首先考虑的就是借助Amazon平台。经过充分的调研和部分项目的试用,我们发现Amazon平台并不能满足我们的要求。 Amazon平台诞生于淘宝,作为淘宝,天猫稳定性保障体系中重要的成员。Amazon 追求的是超高的施压能力和极强的平台稳定性。另外,淘宝的双11,双12全链路压测是多团队合作的项目,一次全链路压测可能需要数百人的参与,对于这种超大规模的全链路压测项目,成败的关键在于团队的合作。平台需要搞定的是人无法搞定的事情,对于Amazon来讲,要做的就是把事情做到极致。 高德的全链路压测流量的要求远远不及淘宝的全链路压测,并且通常全链路压测的周期都不会太长(通常在2周左右,这个时间还需要缩减),所以我们比较关注压测成本的问题,例如压测数据的构造成本,压测资源申请的成本,错误排查成本,以及便捷性方面的问题,例如压测过程的可视化,压测报告等。 另外,高德的日常环境的压测需求比较旺盛,除了全链路压测外,我们还需要借助别的平台(如PAP,PAS)来满足日常的压测需求。 从成本收益的角度出发,最终我们选择自研压测平台来满足高德的全链路压测需求,以及日常的压测需求。 高德压测平台的自建思路如何打造一款全新的压测平台? 回答这个问题,先要回答平台的目的是什么。高德压测平台的目的是什么?一定是为了解决业务的问题!结合上文对全链路压测的描述,对高德业务特点的描述,我们建设压测平台就需要回答这几个问题: 如何保证场景的真实性?线上压测,如何保证压测流量不影响线上用户,如果保证压测数据不污染线上数据?如何构建超高流量?如何降低使用成本?如何降低资源成本?如何保证场景的真实性 压测要保证真实,需要在压测场景上做到全覆盖,需要从协议支持和用户行为两个方面来满足场景的真实性。 协议支持:对于高德服务而言,不同的用户场景使用到的通信协议不一样,例如PC端主要是http协议,而移动端则是accs协议。因此全链路压测的场景设计上首先需要满足对全协议的支持。 用户行为:除协议外,场景构造还需要考虑到真实场景的用户行为,目前的做法是使用线上日志作为原材料,对日志进行过滤,整理,最终形成标准化语料文件,这样可以在一定程度上保证压测数据的真实性。这种低成本的做法,只能借助业务同学的经验去保证。人总会出错,因此未来在场景真实性保障方面不能仅仅依靠人的经验,平台需要通过技术的手段,通过模型,依靠机器去保证。 ...

August 21, 2019 · 1 min · jiezi

expressautoloadrouter源码分析

模块介绍express-autoload-router模块用于自动加载并注册路由。 模块使用基本用法基本使用步骤如下。 安装模块$ npm install express-autoload-router构建路由程序将路由程序文件放在专门的目录app/controllers下面。格式上有两点需要注意: 路由程序文件名称必须为xxx_controller.js;路由程序中的action函数/对象名称必须为yyyAction;$ cat app/controllers/user_controller.js/** * 对象方式,多个路由放在一起 */module.exports = { listAction: { method: ["GET"], middlewares: [], handler: function(req, res) { return res.send("user list action"); } }, getAction: { method: ["GET"], middlewares: [], handler: function(req, res) { return res.send("user get action"); } }};在主程序中引入并加载路由$ cat app.jsconst express = require("express");const path = require('path');const loadRouter = require('express-autoload-router');const app = express();loadRouter(app, "/demo", path.join(__dirname, "app/controllers"));app.listen(3000, function() { console.log("Listening on port 3000!");});其中,loadRouter()函数的第二个参数指定一级路由,第二个参数指定路由程序文件所在的目录。 ...

July 16, 2019 · 1 min · jiezi

VueNodeExpressMySql的尝试

前言这是一次很简单的尝试,初衷是使用nodejs替换PHP,搭建一个完整的web项目。 项目逻辑vue开发前端,目前还在dev模式,使用proxy代理和node后端进行通信。node+express构建后端web服务,连接mysql,进行增删改查。 功能实现1.前端通过axios,已经实现了get、post,formdata图片上传的功能。2.后端接收get、post、formdata数据,查询数据库返回数据,保存图片返回图片地址的功能。3.图片存储在指定的文件夹,通过xampp指定了一个静态目录做图片存储的功能。 下一步的开发1.目前只实现了对mysql的select操作,下一步需要实现inset、update、delete操作。2.vue项目目前还是dev模式,需要build项目进入product模式,服务器为xampp。 最后最后一步,所有项目迁移到外网,暂定为阿里云。 codeVue端的图片上传代码:upfile.vue: change(){ let that = this let file = that.$refs.avatar.files[0]; let URL = window.URL || window.webkitURL; let imageURL = URL.createObjectURL(file); that.avatar = imageURL let fd = new FormData() fd.append('file', file) that.$store.dispatch('upfile', { fd: fd, callback: function(res){ that.avatar = that.$store.state.imageURL + res.data console.log(res) } }) }Vue端的vuex代码:store.js: upfile (context, data) { axios.create().post('/upfile', data.fd, { headers: { 'Content-Type': 'multipart/form-data' } }).then(response => { console.log('success') data.callback(response.data) }).catch(res => { console.log('error') data.callback(res) }) },Node端的upfile.js: ...

July 15, 2019 · 2 min · jiezi

译如何连接-React-和-NodeExpress

原文:How to create a React frontend and a Node/Express backend and connect them作者:João Henrique译者:博轩 在本文中,我将引导你创建一个简单的 React 应用,以及一个简单的 Node/Express API,并将两者相互连接。 我不会详细介绍本文中提到的任何技术,但是我会留下链接,以便你想了解更多信息。 您可以在我为本教程制作的代码库中找到源码。 译注:嘤嘤嘤,我也写了Demo的... client-react-001 , api-node-001这里的目标是为您提供有关如何设置和连接前端客户端和后端API的实用指南。 在我们开始之前,确保您的机器上已经安装了 Node.js 创建项目主目录在终端,导航到你要保存项目的目录。现在为您的项目创建一个新目录并导航到它: mkdir my_awesome_projectcd my_awesome_project创建一个 React 应用这个过程非常简单。 我们将使用 Facebook 的 create-react-app 来... 你猜对了,简单的创建一个名为 client 的应用程序: npx create-react-app clientcd clientnpm start让我们看看这里做了什么: 1.使用 npm 的 npx 创建一个 React 应用,并将其命名为 client。2.cd (更改目录)到客户端目录中。3.启动了应用程序。 在浏览器中,访问:http://localhost:3000/ 如果一切正常,您将看到 React 欢迎页面。恭喜!这意味着您现在在本地计算机上运行了一个基本的 React 应用程序。是不是很简单? 要停止您的 React 应用程序,只需在终端按下 Ctrl + c 即可。 ...

July 15, 2019 · 2 min · jiezi

Express框架中间件bodyparser处理FormData的POST表单数据reqbody接收不到数据

问题在使用node的过程中,express框架是必不可少的。之前表单提交数据使用的是submit按钮,使用express的中间件body-parser来处理,在req.body中可以拿到表单传来的值。 但是今天在使用ajax发送数据时,使用了XMLHttpRequest 2.0 提供的FormData来提交表单数据,出现了req.body一直是个空对象的情况,具体代码如下: 前端JS代码: $('.addBtn').click(function() { let fd = new FormData($('.form')[0]); $.ajax({ url : '/add', type : 'POST', contentType : false, processData :false, data : fd, success: function(res) { console.log(res); } })});contentType: false这一项必须设置为false,否则jQuery会默认将contentType设为 application/x-www-form-urlencoded,导致后端拿不到数据,因为 FormData 默认的数据类型是 multipart/form-data,我在网上查这个问题的相关资料时,有些文档竟然写着将 contentType 的类型改为 application/x-www-form-urlencoded 可以解决问题(白眼) processData: false这一项也是,不需要jQuery帮我们做数据处理,否则也会导致后端拿不到数据 后端代码: router.post('/add', (req, res) => { console.log(req.body); //{}});原因后来通过查阅资料才知道 body-parser 并不支持 contentType: multipart/form-data 的格式类型,也就是不支持formData格式 使用 connect-multiparty 第三方模块解决具体步骤如下: 安装 npm i connect-multiparty -S 引用 ...

July 9, 2019 · 1 min · jiezi

VueCli30中集成MockApi

VueCli3.0中集成MockApi一:使用场景哎哟,好烦啊,这个需求还么结束就来下一个需求,程序员不要排期的吗? 没办法啊,资本主义的XX嘴脸啊 来吧,技术评审我俩把接口格式对一把,你先开发,我这边结束了我跟上,再联调 MMP,那又增加了我的工作量啊,每次我都要自己先把数据放在一个配置文件中,引入使用,然后对接的时候还得删除无用代码,好气 你自己Mock接口啊,就向我们后端经常用PostMan一样模拟请求啊 Mock??我去查查看 二:Mock的概念1:Mock的描述 Mock接口其实就是模拟真实接口提供一个在开发环境的假数据,甚至是真实数据,在开发时,经常出现接口内容不能够及时的跟进,导致开发过程中添加一些额外的工作量。接下来的例子全部围绕着Vue为主体介绍前后端提前确定好通信的JSON格式之后,我们在不依赖后端进度的同时,能提供一套好的开发体验。 2:Mock能解决的问题 减少额外工作,在没有Mock接口的时候我们模拟数据的方式很烦躁,比如list列表,需要在data中声明list,去调试内容,或者引入一个mock文件,这样做导致在联调调用接口的部分代码没有写,联调成功的时候要删除很多无用代码 ---> 通过Mock只需在联调的时候把Mock接口的地址换成真实地址即可 import { mockList2 } from 'mock/list.js';export default { data () { return { mockList: [ { "name": 'tx', "age": 12 } ], mockList2 } }}如果采用上述的方式去模拟数据,缺少真正缺口所具备的状态,比如删除接口,有成功和失败的区分,这个模拟就很恶心了 ----> 通过Mock,可以直接通过实在的query或者其他的操作来达到同样的目的3:Mock的几种方式以及对应的优缺点 Mock的方式优缺点本地Mock接口优点:可以更加细粒度的控制mock的内容。缺点:需要增加本地的代码量,以及需要配置webapckMock.js实现ajax拦截优点:数据通过mock.js会更丰富。缺点:增加一些本地配置,拦截ajax后端Controller的静态JSON优点:接口联调不需要修改任何东西。缺点:修改Mock内容沟通成本高,跟后端扯皮利用FastMock去模拟Mock优点:可控内容以及实现动态Restful api。缺点:如果项目包装axios等请求库之后需要针对接口转发做不同处理4:本地Mock接口 该篇文章针对本地Mock接口进行操作,其他的方式会简要介绍并给出对应的链接,如果有需要,自行去查阅。 三:本地Mock周边知识本地Mock的思想就是利用Node + express完成Restful Api。结合webpack配置项devServer同时利用Vue-cli3.0的暴露的配置利用本地express完成mock接口的添加 Node+Express的相关知识点,用node+express写过Restful Api的就应该知道接下来Mock怎么处理了,这里我先简要介绍一下我们需要用到的技术吧(Express的路由以及node的fs模块) Express路由相关,具体的见文档,这里不区分请求方法,直接app.use const express = require('express');const app = express();// 这样一个简单的路由就完成了,请求到/ajax-get-info的请求就能拿到对应的JSON数据app.use('/ajax-get-info', (req, res) => { res.send({ "success": true, "code": 0, "data": {} }) });针对不同的请求生成动态的内容,我们可以通过req.query和req.params等来生成动态内容,在express中,我们传入的body内容,在req.body中并获取不到,需要添加中间件body-parser,需要注意的是这个中间件不能在app全局路由使用,不然会影响到代码到测服的接口,利用http-proxy-middleware转发的接口,所以我们需要单独的设置一个Mock路由,针对路由级别的使用中间件,代码如下 ...

July 5, 2019 · 1 min · jiezi

Parse-Server-快速实现-Serverless-的利器

原文地址 近年来NODEJS发展迅速,很多工程师尤其是前端工程师,用NODEJS来开发一些后端应用。同时,研发效率和研发成本成为开发者关注的重点,对于一些基础常用功能,如何避免重复开发,成为大家关注的重点,而 Parse Server 就是抽象了常用功能的NODEJS开源项目。 首先,从整体上看看 Parse Server 提供了哪些基础功能: 用户的登录注册用户身份的认证数据存储 && 灵活查询文件存储实时查询消息推送缓存服务与云平台很好的对接自定义业务逻辑与Hook机制服务快速搭建默认情况下,Parse Server 使用的默认数据库是 MonogDB,所以需要提前安装该数据库。关于数据库的安装与使用不是本文的重点,暂且跳过。 const config = require('./config');const app = express();var api = new ParseServer({ databaseURI: config.databaseURI, cloud: './cloud/main.js', appId: config.appId, masterKey: config.masterKey, // push: { ... }, // See the Push wiki page // filesAdapter: ..., // 对应不同云厂商的 FilesAdapter // javascriptKey: config.javascriptKey, // js客户端认证 liveQuery: { // 建立websocket链接,实现实时通信 classNames: ['Sdtuent'] }});var dashboard = new ParseDashboard({ "apps": [ { "serverURL": "http://localhost:1337/parse", "appId": config.appId, "masterKey": config.masterKey, "appName": "test" }, ]});// Serve the Parse API at /parse URL prefixapp.use('/parse', api);app.use('/dashboard', dashboard);const port = 1337;const httpServer = http.createServer(app);httpServer.listen(port, function() { console.log('parse-server-example running on port ' + port + '.');});var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer);通过上述少量的代码,快速完成服务的搭建,/parse 是API的前缀,/dashboard 是后台的页面路由前缀,这样就可以快速使用 Parse Server 提供的各种功能。 ...

July 5, 2019 · 3 min · jiezi

基于WebpackTypeScriptKoa的环境配置

TypeScript是一种开源编程语言,在软件开发社区中越来越受欢迎。TypeScript带来了可选的静态类型检查以及最新的ECMAScript特性。作为Javascript的超集,它的类型系统通过在键入时报告错误来加速和保障我们的开发,同时越来越多对的库或框架提供的types文件能够让这些库/框架的API一目了然。我对这门语言垂涎已久,但是迟迟无法找到练手的地方。很显然的,个人博客又一次的成了我的学习试验田????。我放弃了上一版Vue单页面的框架,改为基于TypeScript/Koa的多页面应用。在改造的过程中,我试着将服务端(Koa)代码以及前端代码都使用TypeScript来开发,中间使用了webpack作为开发时前后端的桥梁。 目录结构.├── .babelrc├── bin│ ├── dev.server.ts│ ├── pm2.json│ └── app.ts├── config # 配置目录│ ├── dev.ts│ └── prod.ts├── nodemon.json├── package.json├── postcss.config.js├── scripts│ └── webpack.config.js├── src # 源码│ ├── assets # 静态资源│ │ ├── imgs│ │ ├── scss│ │ └── ts│ ├── entries # webpack入口│ │ ├── blog.ts│ │ └── index.ts│ └── views # 模板(文件名与入口一一对应)│ ├── blog.html│ ├── index.html│ └── layout # 模板布局│ ├── footer.html│ └── header.html├── server # 服务端│ ├── app.ts│ └── middleware│ └── webpack-dev-middleware.ts├── test # 单元测试│ └── .gitkeep ├── tsconfig.front.json└── tsconfig.json安装项目依赖npm i --save koa koa-{router,bodyparser,static,ejs}npm i -D typescript ts-node nodemon @types/{node,koa,koa-router,koa-bodyparser}开发环境(development)流程 ...

July 2, 2019 · 3 min · jiezi

nodejs的Web开发框架的选择

node.js的Web开发框架的选择?这个问题貌似在其它的后端开发领域不存在。没错,我说的就是隔壁的Java。我要是写java的应用,可以毫不犹豫的选择Spring。但是node可选择的余地多的多。 现有node服务端框架1. Express、Koaexpress框架肯定不用说了,写node服务这块的同学肯定是非常熟悉的框架了。我早期的时候也是express的粉丝。 优点:express的框架结构非常的简单。经过短暂的学习就可以用来开发一个项目。非常适合作为node新手的入门框架。 缺点:开发阶段:Express的缺点也很明显,由于结构简单,自由度高。每个人会有不同的文件编排方式。前期设计阶段需要人工的把项目约定做好。但是团队来新人了,又要重新学习项目约定,无形中增加了学习成本。说到底还是缺乏项目的工程化约束。在项目的开发初期需要自己手工的搭建一些通用的脚手架代码,来方便的之后的开发工作。开发流程会拖的比较长。 运维阶段:由于node单进程,js主线程运行的机制。如果在js主线程中没有做好错误的处理。会导致进程意外退出的问题。这在项目运行阶段是不可接受的问题。需要进程守护的机制来保证程序的健壮性。Express和Koa需要依赖第三方的工具来实现。如PM2。讲道理这些功能应该是一个web开发框架应该具备的基础功能。 总结:不管是Express还是Koa框架。还是处于比较简单的基于http模块的封装。在Reuest和Response这两个对象基础上进行扩展开发。我们业务开发团队需要的是稳定、快速的开发框架。实际开发中往往需要在Express和Koa的基础上封装大量的代码,来适应不同的业务场景,这对追求快速开发的互联网行业是不受欢迎的。 2. Egg.js我在2018年3月份开始接触egg框架。发现这是一个具备较完善功能的web开发框架。 优点:方便、好用、少写很多的脚手架级别的代码。专注于业务逻辑的开发。内建插件机制,兼容koa插件。约定大于配置。内置多进程管理。阿里巴巴开源。文档是中文的。估计没点自虐倾向的同学一般都会选择母语版本的文档来看吧。 缺点:由于目前的使用层面还不够深入。除了对应用配置方式的不太满意外,没有发现大的开发痛点。项目开发实践下来,开发效率杠杠的。 总结:估计写到这里,应该能看出我对egg框架的喜爱程度了。那么下面学习一下egg入门。

June 30, 2019 · 1 min · jiezi

express中间件的理解

nodejs(这指express) 中间件铺垫:一个请求发送到服务器,要经历一个生命周期,服务端要: 监听请求-解析请求-响应请求,服务器在处理这一过程的时候,有时候就很复杂了,将这些复杂的业务拆开成一个个子部分,子部分就是一个个中间件。对于处理请求来说,在响应发出之前,可以对请求和该级响应做一些操作,并且可以将这个处理结果传递给下一个处理步骤 express 这样描述中间件的:执行任何代码。 修改请求和响应对象。 终结请求-响应循环。 调用堆栈中的下一个中间件分类:应用级中间件 路由级中间件 错误处理中间件 内置中间件 第三方中间件举个栗子: var express = require('express'); var app = express(); app.get('/', function(req, res, next) { // req 修改请求 // res 响应对象 next(); // 当前中间件函数没有结束请求/响应循环, 调用next(), // 将控制权传递给下一个中间件函数继续往下处理,否则页面到此会被挂起 });app.get('/end', function(req, res) { res.send('程序到我这里就结束了,没有next方法');})app.listen(3000);上面next()的说明: next()函数不是nodejs或者express的函数,而是传递中间件函数的第三变量,它是一个统称,可以为任意名称,为了名称统一,不造成混淆,约定为next(),调用它之后会将调用应用程序中的下一个中间件程序 中间件的使用说明: var express = require('express'); var app = express(); app.use(requestTime); // var requestTime = function(req,res, next) { req.requestTime = new Date().getTime(); next(); } app.get('/time', function(req, res, next) { var timeText = '当前时间为:'; timeText = timeText + req.requestTime; // 这里的req.requestTime 是上一个中间件函数传递过来的, // 一个业务处理流程,多个中间件函数对请求 req进行修改处理,并且通过next() 传递给下一个中间件函数, // 这样下面的中间件函数都能拿到上一个中间件函数处理的结果 res.send(timeText); });app.get('/end', function(req, res) { res.send('程序到我这里就结束了,没有next方法');})app.listen(3000);下面是多个中间件函数,在各自函数中处理请求和响应的例子: ...

June 28, 2019 · 2 min · jiezi

ReactNodeExpress搭建一个前后端demo

1.简述demo:以前用过原生JS写计算器,这里我想要react来写,并且用数据库记录每次计算公式和结果,并且可发请求获取后台部分:建立数据库后,通过Node.js的express框架写接口操作数据库前端页面:通过react的router控制路由编写,然后通过axios发送请求给后台2.Express简介和上手express官方链接,具体的通过对官方文档的学习还是比较容易上手的,这里我就简要说明2.1新建express项目webstorm可以直接点击newporject来选择expressApp 2.2项目结构与路由挂载├── app.js # 应用的主入口├── bin # 启动脚本├── node_modules # 依赖的模块├── package.json # node模块的配置文件├── public # 静态资源,如css、js等存放的目录├── routes # 路由规则存放的目录└── views # 模板文件存放的目录 2.3路由路由写到routes下面 var express = require('express');var router = express.Router();router.get('/list', function(req, res, next) {//pool.query是数据库的连接池下面会说到 pool.query('SELECT * FROM counter;', function (err, rows, fields) { if (err) throw err; console.log(rows) res.json( rows ) })});router.post('/add', function(req, res, next) { console.log(req.body) //var mysqltl='INSERT INTO counter(id,counter, time) VALUES(\''+req.body.counter+'\','+'now()'+');' var mysqltl=`INSERT INTO counter(counter, time) VALUES('${req.body.counter}',now());` console.log(mysqltl) pool.query(mysqltl, function (err, rows, fields) { if (err) throw err; console.log(rows) res.json( rows ) })});2.4允许跨域请求设置在app.js里面添加 ...

June 28, 2019 · 3 min · jiezi

手写一个-Express初级版

序:因为公司 Node 方面业务都是基于一个小型框架写的,这个框架是公司之前的一位同事根据 Express 的中间件思想写的一个小型 Socket 框架,阅读其源码之后,对 Express 的中间件思想有了更深入的了解,接下来就手写一个 Express 框架 ,以作为学习的产出 。 在阅读了同事的代码与 Express 源码之后,发现其实 Express 的核心就是中间件的思想,其次是封装了更丰富的 API 供我们使用,废话不多说,让我们来一步一步实现一个可用的 Express。 本文的目的在于验证学习的收获,大概细致划分如下: 服务器监听的原理路由的解析与匹配中间件的定义与使用核心 next() 方法错误处理中间件定义与使用内置 API 的封装正文:在手写框架之前,我们有必要去回顾一下 Express 的简单使用,从而对照它给我们提供的 API 去实现其相应的功能: 新建一个 app.js 文件,添加如下代码: // app.jslet express = require('express');let app = express();app.listen(3000, function () { console.log('listen 3000 port ...')})现在,在命令行中执行: node app.js 可以看到,程序已经在我们的后台跑起来了。 当我们为其添加一个路由: let express = require('Express');let app = express();app.get('/hello', function (req, res) { res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end('我是新添加的路由,只有 get 方法才可以访问到我 ~')})app.listen(3000, function () { console.log('listen 3000 port ...')})再次重启:在命令行中执行启动命令:(每次修改代码都需要重新执行脚本)并访问浏览器本地 3000 端口: ...

June 28, 2019 · 5 min · jiezi

Express-的使用

以下内容,基于 Express 4.x 版本 Node.js 的 ExpressExpress 估计是那种你第一次接触,就会喜欢上用它的框架。因为它真的非常简单,直接。 在当前版本上,一共才这么几个文件: lib/├── application.js├── express.js├── middleware│   ├── init.js│   └── query.js├── request.js├── response.js├── router│   ├── index.js│   ├── layer.js│   └── route.js├── utils.js└── view.js这种程度,说它是一个“框架”可能都有些过了,几乎都是工具性质的实现,只限于 Web 层。 当然,直接了当地实现了 Web 层的基本功能,是得益于 Node.js 本身的 API 中,就提供了 net 和 http 这两层, Express 对 http 的方法包装一下即可。 不过,本身功能简单的东西,在 package.json 中却有好长一串 dependencies 列表。 Hello World在跑 Express 前,你可能需要初始化一个 npm 项目,然后再使用 npm 安装 Express: mkdir pcd pnpm initnpm install express --save新建一个 app.js : const express = require('express');const app = express();app.all('/', (req, res) => res.send('hello') );app.listen(8888);调试信息是通过环境变量 DEBUG 控制的: const process = require('process');process.env['DEBUG'] = 'express:*';这样就可以在终端看到带颜色的输出了,嗯,是的,带颜色控制字符,vim 中直接跑就 SB 了。 应用 ApplicationApplication 是一个上层统筹的概念,整合“请求-响应”流程。 express() 的调用会返回一个 application ,一个项目中,有多个 app 是没问题的: ...

June 26, 2019 · 7 min · jiezi

基于ReactDvaJSUmiJSAntdesignexpressmongo搭建的CMS内容管理后台

项目地址前端部分:React+Ant-design+DvaJS+UmiJS - 地址(欢迎star&fork&issue)后端部分:express+mongoose+redsi(鉴权)+pm2 - 地址(欢迎star&fork&issue)项目简介此项目为初中级项目,主要为理解React+Ant-design+DvaJS+UmiJS搭建中后台系统结构后端项目为通用模型,包含用户管理,角色管理,内容管理,评论管理,内容分类管理,适用于任何内容型产品后台(博客、社区等,可以直接套个简易版CNode)项目预览在线演示地址

June 18, 2019 · 1 min · jiezi

express-jwt-持久化登录vue前端篇

jwt 持久化验证前端篇,node 配置详情请移步这里 我用的是vue3,下面是 src 的目录 用到的依赖 验证思路Home 页写登录,然后在 About 页获取到登录名。登录成功缓存 token,进入About页时,通过判断是否有 token 来判断是否登录/登录超时登录页在登录页输入用户名和密码,将其提交到vuex // src/views/Home.vue<template> <div class="home"> <input type="text" v-model="user" placeholder="账号"> <input type="text" v-model="password" placeholder="密码"> <button @click="login">点击</button> </div></template><script>import {mapActions} from 'vuex'export default { data(){ return{ user:'', password:'' } }, name: 'home', methods:{ ...mapActions(["toLogin"]), login(){ // 请求之后能拿到用户名,nickname,把用户名存在state // 传入多个参数 改成对象 // action moutation只能拿第一个参数哦,所以要改成对象 this.toLogin({user:this.user,password:this.password}) } }}</script>后台 jwt后台的 jwt 验证,我们把过期时间设置成60s // src/app.jslet express = require('express')let cors = require('cors')let bodyParser = require('body-parser')let jwt = require("jsonwebtoken")let app = express()app.use(cors())app.use(bodyParser.json())app.use(bodyParser.urlencoded({extended:false}))// 模拟一个登陆的接口app.post('/login',function(req,res){ // 登录成功获取用户名 let username = req.body.user res.json({ // 进行加密的方法 // sing 参数一:加密的对象 参数二:加密的规则 参数三:对象 token:jwt.sign({username:username},'abcd',{ // 过期时间 expiresIn:"60s" }), username, code:200 })})// 登录持久化验证接口 访问这个接口的时候 一定要访问token(前端页面每切换一次,就访问一下这个接口,问一下我有没有登录/登陆过期)// 先访问登录接口,得到token,在访问这个,看是否成功app.post('/validate',function(req,res){ let token = req.headers.authorization; // 验证token合法性 对token进行解码 jwt.verify(token,'abcd',function(err,decode){ if(err){ res.json({ msg:'当前用户未登录' }) }else { // 证明用户已经登录 res.json({ token:jwt.sign({username:decode.username},'abcd',{ // 过期时间 expiresIn:"60s" }), username:decode.username, msg:'已登录' }) } })})app.listen(8000,function(){ console.log('OK')})后台接口// src/api/login.jsimport axios from 'axios'axios.defaults.baseURL = 'http://localhost:8000'// axios 请求拦截axios.interceptors.request.use(function(response){ // 在 headers 中设置authorization 属性放token,token是存在缓存中的 response.headers.authorization = localStorage["token"] return response}, function (error) { return Promise.reject(error); })// axios 响应拦截器axios.interceptors.response.use(function (response) { return response.data; }, function (error) { return Promise.reject(error); });// 登录的接口export let loginApi = (user,password) => { return axios.post('/login',{user,password})}// 验证是否登录的接口export let valiApi = () => { return axios.post('/validate')}vuex// src/store.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)import {loginApi,valiApi} from './api/login'export default new Vuex.Store({ state: { username:"" }, mutations: { setusername(state,payload){ // 改变state里的 username state.username = payload } }, actions: { async toLogin({commit},{user,password}){ let res = await loginApi(user,password) console.log(res) let {username,token} = res // 提交到 mutations commit("setusername",username) // token 具有时效性 登录成功 把token存在本地存储 localStorage["token"] = token }, async valiApi({commit}){ const { username, token } = await valiApi(); commit('setusername', username); localStorage["token"] = token return username !== undefined; } }})验证持久化登录页 从 vuex 中拿到用户名,打开页面就请求是否登录的 api ,从而实现持久化登录验证 ...

June 14, 2019 · 2 min · jiezi

最近正在重构之前开发过的记账本

最近正在重构之前开发过的记账本,之前做得太烂了,现在想重新使用重构一般移动端的记账本,加入了新的功能,主要技术站为 webapp vue.js 后端php,可能原生写。 也可能使用框架写。 也可能使用 node.js写。 也可能使用go语言写。 也可能使用java写小程序版本app安卓版本,还有可能使用IOS版本码云地址 Ken / 记账本,重构 暂时页面的安排,以及目录的排版vueaccountProject setupnpm installCompiles and hot-reloads for developmentnpm run serveCompiles and minifies for productionnpm run buildRun your testsnpm run testLints and fixes filesnpm run lintCustomize configurationSee Configuration Reference. 启动页面|---------loading/ 启动页面 |----component/ 组件模块 |----loading.vue |----common/ 公共模块 |----style/ 样式模块注册页面|---------login/ 注册页面 |--component/ 组件模块 |----login.vue |--common/ 公共模块 |--sytle/ 样式模块首页|---------home/ 首页 |--component/ 组件模块 |----date.vue 日期组件 |----inout.vue 收入支出组件 |----visualize.vue 可视化组件 |----importaccout.vue 导入账单组件 |----voiceaccount.vue 语音账单组件 |--common/ 公共组模块 |--style/ 样式模块账本|---------account/ 账本 |---component/ 组件模块 |-----head.vue 头部组件 |-----account.vue 账本组件 |-----scroll.vue 滚动组件 |---common/ 公共模块 |---style/ 样式模块我的资金|---------usermoney/ 用户资金 |---component/ 组件模块 |---inputmoney.vue 输入资金组件 |---showmoney.vue 展示资金模块 |---common/ 公共模块 |---style/ 样式模块我|---------user/ 用户模块 |---component/ 组件模块 |---head.vue 导航 |---usernote.vue 账号密码修改 |---dailyremind.vue 每日提醒 |---exportbooks.vue 导出账本 |---aboutus.vue 关于我们 |---softwareupdates.vue 软件更新 |---common/ 公共模块 |---style/ 样式模块添加记账页面|---------addaccount/ 添加记账 |-----inaccount 收入页面 |-----component/ |-----head.vue 图表 |-----show.vue 显示各种收入 |-----common/ |-----style/ |-----outaccount 支出页面 |-----component/ |-----head.vue 图标 |-----show.vue 显示各种支出 |-----common/ |-----style/ |------transferaccounts/ 转账页面 |------component/ |----head.vue 图标 |----show.vue 显示转出转入 |------common/ |------style/ |-------blance/ 余额 |------component/ |---inputblance.vue 转入余额 |---showblance.vue 显示余额 |------common/ |------style/ ...

June 13, 2019 · 1 min · jiezi

GraphQL一个简单的入门示例

GraphQL一个简单的入门示例准备npm i --save express express-graphql graphql cors服务端代码var express = require('express');var graphqlHTTP = require('express-graphql');const { buildSchema } = require('graphql');const cors = require('cors'); // 用来解决跨域问题// 创建 schema,需要注意到:// 1. 感叹号 ! 代表 not-null// 2. rollDice 接受参数const schema = buildSchema(` type Query { username: String age: Int! }`)const root = { username: () => { return '李华' }, age: () => { return Math.ceil(Math.random() * 100) },}const app = express();app.use(cors());app.use('/graphql', graphqlHTTP({ schema: schema, rootValue: root, graphiql: true}))app.listen(3300);console.log('Running a GraphQL API server at http://localhost:3300/graphql')客户端代码<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>graphql demo</title></head><body> <button class="test">获取当前用户数据</button> <p class="username"></p> <p class="age"></p></body><script> var test = document.querySelector('.test'); test.onclick = function () { var username = document.querySelector('.username'); var age = document.querySelector('.age'); fetch('http://localhost:3300/graphql', { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, method: 'POST', body: JSON.stringify({ query: `{ username, age, }` }), mode: 'cors' // no-cors, cors, *same-origin }) .then(function (response) { return response.json(); }) .then(function (res) { console.log('返回结果', res); username.innerHTML = `姓名:${res.data.username}`; age.innerHTML = `年龄:${res.data.age}` }) .catch(err => { console.error('错误', err); }); }</script></html>运行结果 ...

June 11, 2019 · 1 min · jiezi

express-jwt-postMan验证-实现持久化登录

原理第一次登陆时会返回一个经过加密的token,下一次访问接口(携带登录返回你的token)的时候,会对token进行解密,如果解密正在进行,说明你已经登录,再把过期时间延长下载npm init -y // 一键初始化npm install express -s // 下载expressnpm install cors // 跨域中间件npm install body-parser // body-parser中间件 解析带请求体的数据(post,put)npm install jsonwebtoken // 持久化登录 jwt json web token基本配置// 引入expresslet express = require('express')let cors = require('cors')let bodyParser = require('body-parser')let jwt = require("jsonwebtoken")let banner = require("./banner")// 拿到服务器let app = express()app.use(cors())app.use(bodyParser.json())app.use(bodyParser.urlencoded({extended:false}))// listen 后面跟着的是端口app.listen(8000,function(){ console.log('OK')})模拟一个登陆的接口app.post('/login',function(req,res){ let {username} = req.body console.log(username) res.json({ // 进行加密的方法 // sing 参数一:加密的对象 参数二:加密的规则 参数三:对象 token:jwt.sign({username:username},'abcd',{ // 过期时间 expiresIn:"1h" }), username, code:200 })})postMan模拟 发送POST请求 ...

June 4, 2019 · 1 min · jiezi

利用-jwt-配合-postman-实现-持久化登录

jwt 实现持久化登录原理第一次登录时会返回一个经过加密的token下一次访问接口时(携带登录返回给你的token)会对token进行解密 如果解密正确 证明你已经登录 再把过期时间延长// 首先 npm init -y 一键初始化// 引入 express 下载 npm install expresslet express = require('express');let app = express();// 用于跨域 下载 npm install corslet cors = require('cors');// 解析带请求体(post,put)的数据 下载 npm install body-parserlet bodyParser = require('body-parser');// 引入 jwt 全称 json web token 下载 npm install jsonwebtokenlet jwt = require('jsonwebtoken');// 解析 json 格式app.use(bodyParser.json())// 解析 form 格式app.use(bodyParser.urlencoded({extended:true}))app.use(cors())// 进行登录持久化验证的接口// 访问这个接口时 一定要携带 token 前端页面每切换一次就访问一下这个接口 问一下我有没有登录 或者登录过期app.post('/validate',function(req,res){ let token = req.headers.authorization; // 验证token的合法性 jwt.verify(token,'sxq',function(err,decode){ if(err){ res.json({ msg:'当前用户未登录' }) }else{ // 证明用户已经登录 只要用户操作就会有过期时间 res.json({ username:decode.user, token:jwt.sign({username:decode.user},'sxq',{ // 过期时间 expiresIn:'1h' }) }) } })})// 持久化登录的原理// 第一次登录时会返回一个经过加密的token// 下一次访问接口时(携带登录返回给你的token)会对token进行解密 如果解密正确 证明你已经登录 再把过期时间延长// 模拟一个登录接口 username passwordapp.post('/login',function(req,res){ let {username} = req.body res.json({ // sign 参数 加密的对象 加密的规则 token:jwt.sign({username},'sxq',{ // 过期时间 expiresIn:'1h' }), username })})// 接口app.listen(3000)

June 4, 2019 · 1 min · jiezi

RN-从上手到放弃

RN 从上手到“放弃”前言: react-native,相对于最近????的飞起的flutter,不算是一个新技术,2015年Facebook 开源,到现在已经4 5 个年头,一直在维护当中,但是至今未发布 v1 版本,目前已经更新到0.59。 该技术目标: 跨平台实现原生应用。 GitHub start 数目: 77602(2019-5-29)。 正文1、项目预览现在已完成的功能展示: 入手demo项目,本打算模仿微信的功能做一遍。现在已经完成微信的一级界面。截图如下:首页: 通信录: 发现: 我: 朋友圈(上拉加载和下拉刷新): (未完成,就是调用了接口) 聊天界面: 摄像头拍照(安卓虚拟机): github地址: https://github.com/adouwt/rea... nodejs后台:https://github.com/adouwt/nod... 项目主要使用插件(库): react-native-camera (调用摄像头)react-native-vector-icons (图标库)react-navigation (路由导航)参考资料: https://reactnative.cn/https://oblador.github.io/rea...https://github.com/react-nati...https://shenbao.github.io/ish...2、项目运行~前提: 环境搭建及相关软件、安卓或者ios 的模拟器安装, 参考官网即可,https://reactnative.cn/docs/g... git clone https://github.com/adouwt/rea...cd react-native-wxnpm inpm run and (安卓)npm run ios (苹果)(上面的运行命令,我在package.json 做了封装,一些处理编译错误的命令,我也已经封装进去) 执行命令后,会自动弹出nodejs 执行终端界面,这个是程序运行的一个监控 模拟器显示: 3、分步实现3.1 初始化并运行项目react-native init AwesomeProjectreact-native run-ios3.2 项目结构说明3.3 新建文件夹 app,接下来所有的源码文件代码将在这里 ...

May 30, 2019 · 4 min · jiezi

全栈开发入门实战后台管理系统

本文首发于 GitChat 平台,免费 Chat,链接:全栈开发入门实战:后台管理系统 感谢你打开了这篇 Chat,在阅读之前,需要让你了解一些事情。 第一,本 Chat 虽然免费,不代表没有价值,我会将个人全栈开发的经历叙述给你,希望对你有一些帮助;第二,文中所使用的技术栈并非最新,也并非最优。后台管理系统更多是 2B 端的产品,通常是业务优先。本 Chat 的目的是为了让你能够快速上手全栈开发。第三,本 Chat 虽然名为全栈开发,但是不会带你做完一个完整的后台管理系统项目。一是由于篇幅有限,二是由于时间关系,个人精力也有限。 正文本 Chat 的内容,正如 Chat 简介中所描述,将分为以下 5 大块: 开发准备前台样式数据库连接前后台交互线上部署你可能会发现,好像不知道要做什么,没错,后台管理系统一般都是企业内部定制开发,通常是对业务的数据管理,具体做什么功能由业务决定,但多数功能都是围绕着表格或者表单。 上面列举的仅仅是全栈开发的大致流程。首先,要做一些准备工作,例如:开发环境、编辑器环境以及依赖包配置等等工作;其次,我们要选定一个后台模版样式,快速套用,实现业务功能。(当然,你要自己开发也行,但不建议这么做,除非为了学习);然后,根据业务做数据库的设计,编写后台数据处理逻辑,以及前后台数据交互等功能;最后,测试并部署上线。 这里的示例,将实现一个学生数据的在线管理需求,其实就是一个在线表格,包括添加,删除功能,系统层面包括登录退出功能。麻雀虽小,五脏俱全,整体架子搭好了,再在上面添加功能就简单多了。好了,现在就开始全栈之旅吧。 开发准备启动项目首先要做的是,开发环境的安装,这里就不多说了,关于 Node 环境的安装,默认你已经搞定了。 既然采用 Express 作为 Web 框架,Express 也是要安装的,有了 Node 环境,安装 Express 就简单多了。我们直接上手 Express 的脚手架,全栈开发关键要速度。 npm install express-generator -g一定记得是全局安装。安装完成之后,输入 express -h 可以查看帮助。这里选用 ejs 模版引擎,为什么?因为我顺手而已,这个不重要。找个合适的目录,运行下面命令: express -e node-web-fullstack-demo生成项目目录之后,首先要安装依赖,如下命令: cd example-node-web-fullstacknpm install等待安装完成,我们就可以启动项目了,使用命令 npm start ,去浏览器中,打开网址:http://localhost:3000,看到写着 Express 的首页,代表你的项目启动成功了。 编辑器环境配置一个好的编码环境,可以让你项目开发效率加倍。 首先介绍一个编辑器配置 EditorConfig,这是一个编辑器的小工具。它有什么作用呢?简而言之,就是让你可以在不同的编辑器上,获得相同的编码风格,例如:空格缩进还是 Tab 缩进?缩进几个空格? 你可能觉得诧异,这个不是在编辑器上设置就可以了吗?没错,假设你从始至终都是在同一个电脑同一个编辑器上编码,那么可以忽略它。如果存在多电脑配合,亦或是多个编辑器配合,那么它就是神器。它几乎支持所有的主流编辑器,不用单独去编辑器中设置,配置文件就在项目中,用那个编辑器打开项目,都能获得一致的编码风格。 ...

May 30, 2019 · 5 min · jiezi

VueExpressMysql-全栈初体验

前言原文地址 曾几何时,你有没有想过一个前端工程师的未来是什么样的?这个时候你是不是会想到了一个词”前端架构师“,那么一个合格的前端架构只会前端OK吗?那当然不行,你必须具备全栈的能力,这样才能扩大个人的形象力,才能升职加薪,才能迎娶白富美,才能走向人生巅峰... 最近我在写一些后端的项目,发现重复工作太多,尤其是框架部分,然后这就抽空整理了前后端的架子,主要是用的Vue,Express,数据存储用的Mysql,当然如果有其他需要,也可以直接切换到sqlite、postgres或者mssql。 先献上项目源码地址 项目项目以todolist为????,简单的实现了前后端的CURD。 后端技术栈框架 Express热更新 nodemon依赖注入 awilix数据持久化 sequelize部署 pm2前端技术栈vue-routervuexaxiosvue-class-componentvue-property-decoratorvuex-class项目结构先看项目架构,client为前端结构,server为后端结构 |-- express-vue-web-slush |-- client | |-- http.js // axios 请求封装 | |-- router.js // vue-router | |-- assets // 静态资源 | |-- components // 公用组件 | |-- store // store | |-- styles // 样式 | |-- views // 视图 |-- server |-- api // controller api文件 |-- container // ioc 容器 |-- daos // dao层 |-- initialize // 项目初始化文件 |-- middleware // 中间件 |-- models // model层 |-- services // service层代码介绍前端代码就不多说,一眼就能看出是vue-cli生成的结构,不一样的地方就是前端编写的代码是以Vue Class的形式编写的,具体细节请见从react转职到vue开发的项目准备 ...

May 26, 2019 · 5 min · jiezi

VueExpress全栈购物商城

一、前言提纲基于Vue和Express框架写的一个全栈购物商城,记录项目过程中遇到的一些问题以及经验和技巧。 二、历史版本基于Vue-CLI2.0:点我查看这个分支版本是一两年前的,基于Vue-CLI2.0写的,数据请求是Mock,纯前端的项目。 基于 Vue-CLI3.0:点我查看 这个分支版本是基于Vue-CLI3.0的,将脚手架从2.0迁移升级到了3.0,遇到的一些问题和坑也都填完了~也是纯Web端Mock模拟数据的项目。 当前版本:点我查看基于Vue-CLI3.0,前端用Vue全家桶,后端用Express+MongoDB+Redis,后台管理系统CMS是用的Vue-Element-Admin 三、详情1.前端在线预览:https://www.fancystore.cn手机直接扫描二维码真机体验: 1.1 技术栈:Vue全家桶(Vue-CLI3,Vue2.x)Vue-Router(页面KeepAlive的处理)Vuex(状态管理库,刷新保存状态)Axios(二次封装配置的数据请求)Less(CSS预处理)I18n(国际化处理)Vant(UI库,按需加载+rem)SEO(预渲染)Sentry(线上错误日志监控)Travic(自动构建,持续部署)1.2适配项目代码px自动转换为rem,需要在main.js中引入amfe-flexible库 Vant UI库也有REM单位,需要在vue.config.js中配置: 1.3 SEO单页(SPA)SEO是一个痛点,目前有两种方式,一种是SSR,一种是预渲染(PrerenderSPAPlugin)。 这个项目是用预渲染(PrerenderSPAPlugin)+vue-meta-info这两个库来做SEO优化的。 将rouer.js模式改为mode:history下载安装PrerenderSPAPluginPrerenderSPAPlugin是Google的一个库,基于Chromium是获取数据,安装PrerenderSPAPlugin的时候会自动下载Chromium浏览器,国内npm安装Chromium会经常安装失败,建议用淘宝的cnpm安装 npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install PrerenderSPAPlugin --save在vue.config.js中引入PrerenderSPAPlugin,配置需要预渲染的路由。下载安装 vue-meta-info在main.js中引入vue-meta-info,在每个页面配置meta信息,这样每个单页路由都有不同的title,理由爬虫引擎抓取重要内容,利于SEO。 预渲染前只有一个index.html,预渲染后最后打包出来的预渲染目录文件如下: 1.4 路由懒加载以及缓存keep-alive动的态判断项目中会使用keep-alive会提高用户体验和网站的性能,如果想实现部分页面缓存,部分页面不需要缓存,可以在router.js里面的路由添加meta.keepalive在跟router-vier加入判断: 1.5 Vuex状态管理页面刷新失效问题用Vuex管理全局的状态,会遇到刷新页面的时候所有的状态丢失或者重置,可以在App.vue的钩子函数添加代码,会在页面刷新的时候将Vuex存储到Storage中,刷新完成后又再从Storage取出来存到Vuex里面: 1.6 封装数据请求封装Axios,添加Axios请求(request)和相应(response),统一处理错误信息或者登录认证的消息,所有的数据请求都存放到api目录下,对应的页面方便后续的维护和管理。 1.7 打包构建优化vue.config.js区分开发环境和生产环境alias的方式直接指定目录。CDN生产环境中将一些共有库Vue,vuex,vue-router等库不打包到项目中,而是通过CDN的方式引入这些共有库,这样可以减少项目的大小,也可以借助CDN的优势,让网站加载更快。推荐一个强大的cdn库:[https://www.bootcdn.cn/](https://www.bootcdn.cn/) 生产环境压缩和出去console打印日志生产环境开启gzip压缩生产环境启用预渲染生产环境分离css,外链CSS可以缓存1.7 错误日志监控Sentry集成Sentry开源日志监控系统,在官网注册获取key,在main.js中引入RavenVue并配置即可 1.8 自动构建和持续集成Github自动构建和持续集成基于Travis 登录Travis选择需要持续集成的项目。在.travis.yml写上相应的config,服务器配置ssh_key,每次代码push到指定分支(比如master)的时候,Travis会自动执行项目上的.travis.yml文件,开始自动构建,构建成功通过scp密令传送到服务器,完成自动部署的功能。每次需要发版,只需要push代码,然后去喝杯咖啡,回来就已经构建发布完成,解放劳动力 1.9 代码自动格式化优化团队合作的时候,每个成员用的编辑器不同,缩进格式也不同,这样合并代码的时候会出现各种意外的情况,团队统一编辑器和编辑器不太现实,因为每个人的写代码习惯和风格不一致。可以借助husky 和 link-stage,每次commit的时候都会安装配置的规则格式化代码,参考文章:https://segmentfault.com/a/1190000009546913 1.10代码优化设计模式表单验证需要写很多判断条件,if-else 或者swith,当条件越多时或者后面需要修改需求条件的时候,会变得不是很好维护,可以用策略模式来重构业务代码: 善用Mixin,提取共用的组件,将项目组件化Vue的Mixin复用代码,可以更好的提高开发效率和可维护性除了将一些共用的页面做成组件引入的方式之外,大文件项目也分好几个模块,将文件才成模块的方式会更好维护和更好的阅读。 2.服务端2.1 技术栈:NodeExpressMongoMongooseRedisQiniuPM22.2 登录授权用Session认证机制,来实现登录登出。配置Session的加密解密,将Session存储到Redis,提高性能,如果有多台服务器,Redis可以共享Session。 2.3 中间件判断用户是否登录:有些API请求是需要用户登录才可以访问的,可以写中间件来判断: 2.4 中间件判断用户的权限:有些API的请求是需要判断用户是否有权限,比如添加、删除和更新,会在中间件判断是否有权限 2.5 PM2多进程启动项目2.6 Mongodb优化设置索引2.7 Redis做缓存2.8 七牛云对象存储配置Key还有域名的绑定以及HTTPS证书的申请3.后台管理系统CMS在线预览:https://www.fancystore.cn/admin ...

May 18, 2019 · 1 min · jiezi

Apollo-Server-集成性能监控

Apollo Server开箱支持Apollo Engine,只是由于某些不可知的原因Apollo Engine的 API 在国内不可访问(我是真不知道为什么这个 API 会被墙的),所以只能另外想办法了. Apollo Server本身有一个Apollo Tracing可以用于性能监控的扩展,通过扩展Apollo Tracing收集指标传输到分布式跟踪系统中.另外有一个开源库Apollo Opentracing可以收集指标,传输到Jaeger或者Zipkin中,通过Jaeger或Zipkin实现性能监控和分析. 秉着方便,直接使用Apollo Opentracing.分布式跟踪系统使用Jaeger. 使用 Docker 搭建JaegerJaeger 官方文档 在浏览器打开 http://localhost:16686 访问Jaeger 搭建Apollo Servermkdir apollo-opentracing-democd apollo-opentracing-demoyarn init -yyarn add apollo-server// index.jsconst { ApolloServer, gql } = require('apollo-server')const typeDefs = gql` type Query { hello: String }`const resolvers = { Query: { hello: () => 'world', },}const server = new ApolloServer({ typeDefs, resolvers,})server.listen().then(({ url }) => { console.log(`???? Server ready at ${url}`)})运行 ...

April 29, 2019 · 1 min · jiezi

如何选择正确的Node框架ExpressKoa还是Hapi

简介Node.js是10年前首次推出的,目前它已经成为世界上最大的开源项目,在GitHub上有+59,000颗星,下载次数超过10亿。流行度快速增长的部分原因是Node.js允许开发人员在应用程序的客户端和服务器端部分使用相同的语言:JavaScript。Node.js是一个开源和跨平台的JavaScript运行时环境,专为构建可扩展的服务器端WEB应用而设计,自身具有高并发、扩展性强等特点。由于社区其呈指数级增长和普及,因此创建了许多框架来提高生产力。在本文中,我们将探讨Node.js中三个最流行的框架之间的差异:Express,Koa和Hapi。在以后的文章中,我们将研究Next,Nuxt和Nest。比较基于: GitHub Stars和npm下载安装基本的Hello World应用程序好处缺点性能安全社区参与ExpressExpress是一个最小且灵活的Web应用程序框架,为Web和移动应用程序提供了一组强大的功能,它的行为就像一个中间件,可以帮助管理服务器和路由star GitHub star:+43,000npm每周下载 6,881,035安装 确保你已经安装node和npm // 你可以将express安装到项目依赖 npm install express --save // 如果要临时安装Express而不是将其添加到依赖项列表,则可以使用 npm install express --no-saveHello World 这是关于如何创建一个侦听端口3000并响应“Hello World!”的快速应用程序的最基本示例 // 这里只创建根目录 其他目录返回404 const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on port ${port}!`))好处 几乎是Node.js Web中间件的标准简单,简约,灵活和可扩展快速开发应用程序完全可定制学习曲线低轻松集成第三方服务和中间件主要关注浏览器,模板和渲染集成开箱即用缺点 尽管Express.js是一个非常方便且易于使用的框架,但它有一些可能影响开发过程的小缺点。组织需要非常清楚,以避免在维护代码时出现问题随着代码库大小的增加,重构变得非常具有挑战性需要大量的手工劳动,因为您需要创建所有端点性能 Express是对web应用的一层基本封装,继承了Node.js的特性当天也有一些express性能的最佳实践包括: 使用gzip压缩不要使用同步功能正确记录(用于调试,使用特殊模块,如调试,应用程序活动使用winston或bunyan)使用try-catch或promises正确处理异常确保您的应用程序使用流程管理器自动重新启动,或使用systemd或upstartinit等系统在群集中运行您的应用。您可以通过启动进程集群来大大提高Node.js应用程序的性能缓存请求结果,以便您的应用不会重复操作以反复提供相同的请求使用负载均衡器运行它的多个实例并分配流量,如Nginx或HAProxy对静态资源使用反向代理。它可以处理错误页面,压缩,缓存,提供文件和负载平衡等更多性能最佳实践一个简单的“Hello World”应用程序每秒具有以下性能请求: 安全 Node.js漏洞直接影响Express,因此确保使用最新的稳定版Node.js查看express 最佳安全实践社区参与 贡献者数量:220Pull Requests:821Express社区定期活动包括 Gitter,IRC channel, issues, Wiki等等最后,express可能是Node.js最流行的框架,还有许多其他流行的框架都是基于Express构建的。koaKoa 是一个新的 web 框架,由 Express幕后的原班人马打造,致力于成为web应用和API开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa帮你丢弃回调函数,并有力地增强错误处理Koa并没有捆绑任何中间件而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序star ...

April 24, 2019 · 1 min · jiezi

Node爬虫练手项目

First项目地址:Crawler-for-Github-Trending 项目中基本每一句代码都写有注释(因为就这么几行????),适合对Node爬虫感兴趣的同学入入门。Introduction50 lines, minimalist node crawler for Trending. 一个50行的node爬虫,一个简单的 axios, express, cheerio 体验项目。Usage首先保证电脑已存在node环境,然后 1.拉取本项目git clone https://github.com/ZY2071/Crawler-for-Github-Trending.gitcd Crawler-for-Github-Trendingnpm inode index.js2.或者下载本项目压缩包,解压cd Crawler-for-Github-Trending-master // 进入项目文件夹npm inode index.jsExamples当启动项目后,可以看到控制台输出Listening on port 3000!此时打开浏览器,进入本地服务 http://localhost:3000/dailyhttp://localhost:3000/time-language // time表示周期,language代表语言 例如:http://localhost:3000/daily // 代表今日 可选参数:weekly,monthlyhttp://localhost:3000/daily-JavaScript // 代表今日的java分类 可选参数:任意语言稍微等待即可看到爬取完毕的返回数据:[ { “title”: “lib-pku / libpku”, “links”: “https://github.com/lib-pku/libpku”, “description”: “贵校课程资料民间整理”, “language”: “JavaScript”, “stars”: “14,297”, “forks”: “4,360”, “info”: “3,121 stars this week” }, { “title”: “SqueezerIO / squeezer”, “links”: “https://github.com/SqueezerIO/squeezer”, “description”: “Squeezer Framework - Build serverless dApps”, “language”: “JavaScript”, “stars”: “3,212”, “forks”: “80”, “info”: “2,807 stars this week” }, …]More本项目仅供爬取体验,每次访问都会实时爬取数据,所以数据返回速度会比较慢,实际操作应该是定时爬取数据然后将数据存进数据库,数据从数据库返回从而提高数据返回效率。但项目很基础,可以作为以上各个node模块最基础的练手使用,希望可以帮到大家 ???? ...

April 20, 2019 · 1 min · jiezi

面向云音乐完成一次有趣的数据分析

2019.03.09, 我决定爬取云音乐的数据, 对云音乐用户进行数据分析项目地址Github演示地址:云音乐用户信息可视化效果展示项目原理Node.js制作爬虫, 爬取网易云接口和部分页面, 进行数据整理分析Vue搭建前端, 进行数据可视化写在最后如果你觉得项目对你有帮助, 可以点个赞或是给个star吗?

April 16, 2019 · 1 min · jiezi

Express中间件body-parser简单实现

Express中间件body-parser简单实现之前文章写了怎么用body-parser中间件处理post请求,今天就大概实现下body-parser中urlencoded 这个方法。首先通过命令提示输入 mkdir lib && cd lib。再输入touch body-parser.js。把下面的代码在body-parser.js 敲一遍。// lib/body-parser.jsconst querystring = require(‘querystring’);module.exports.urlencoded = function (req, res, next) { let chunks = []; req.on(‘data’, data => { chunks.push(data); }); req.on(’end’, () => { // 合并Buffer。 let buf = Buffer.concat(chunks).toString(); // 把querystring解析过的json 放到 req.body上。 req.body = querystring.parse(buf); next(); });}下面是主程序代码。// app.jsconst express = require(’express’);const bodyParser = require(’./lib/body-parser’);let app = express();app.use(bodyParser.urlencoded);app.post(’/’, (req, res) => { res.send(req.body);});app.listen(8000);现在就完成和body-parser中间件类似的功能了,req.body上面有请求过来的post数据。我的博客和github,喜欢就去点点星吧,谢谢。https://github.com/lanpangzhihttp://blog.langpz.com

April 11, 2019 · 1 min · jiezi

使用Express开发小说API接口服务1.0(三)

使用Express开发小说API接口服务1.0(三)线上访问地址https://api.langpz.com/之前发现追书神器API详情页竟然没有下一章和上一章的返回值,只能自己动手封装一下。app.js 增加错误处理// catch 404 and forward to error handlerapp.use(function (req, res, next) { const err = new Error(‘Not Found’); err.status = 404; next(err);});// error handlerapp.use(function (err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get(’env’) === ‘development’ ? err : {}; // render the error page res.status(err.status || 500); res.render(’error’);});这些代码放到module.exports = app; 上面就可以了。列表页增加返回ID找到routes/chapter.js 29行替换 res.send(JSON.stringify({ “flag”: 1,“id”: body._id, “chapters”: body.chapters, “msg”: “OK” }));详情页增加上一章和下一章的返回值let express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取小说文章内容 返回小说文章内容 param link {String} 是小说文章列表接口 chapters[0].link http://chapter2.zhuishushenqi.com/chapter/${link}*/router.get(’/’, function (req, res, next) { if (!req.query.link) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请传入link…” })); } // req.query.link 编码转义 let link = encodeURIComponent(req.query.link); request.get(${common.CHAPTER}/chapter/${link}, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); if (body.ok){ // 再次请求列表页获取上一页和下一页 if(req.query.id){ // req.query.id 编码转义 let id = encodeURIComponent(req.query.id); let n = parseInt(req.query.n); if (isNaN(n)){ n = 0; } request.get(${common.API}/atoc/${id}?view=chapters, function (err, response, body2) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } if (body2 == “wrong param”){ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入错误的ID…” })); }else{ // 解析返回的数据 body2 = JSON.parse(body2); // 检查页码是否超过小说的章节数 if(n > body2.chapters.length - 1){ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入的页码过大” })); }else{ // 如果有上一页或者下一页就返回link否则返回false let prev,next; body2.chapters[n - 1] ? prev = body2.chapters[n - 1].link : prev = false; body2.chapters[n + 1] ? next = body2.chapters[n + 1].link : next = false; if (body2.chapters.length > 0) { res.send(JSON.stringify({ “flag”: 1,“id”: id, “chapter”: body.chapter, “prev”: prev,“next”: next, “msg”: “OK” })); } } } }); }else{ res.send(JSON.stringify({ “flag”: 1, “chapter”: body.chapter, “msg”: “OK” })); } }else{ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入link有错误” })); } });});module.exports = router;访问http://localhost:3000/article?link=http://www.69shu.com/txt/1463…新增n和id参数。n 代表是第几页。id 是书籍ID。github仓库访问地址https://github.com/lanpangzhi/novel-api ...

April 10, 2019 · 2 min · jiezi

使用Express开发小说API接口服务1.0(二)

使用Express开发小说API接口服务1.0(二)线上访问地址https://api.langpz.com/之前完成了首页和搜索的接口,现在就开始写剩下的接口。获取小说源因为追书神器正版源是收费加密的,所以只能使用盗版源,所以要封装一个获取小说源的接口。修改app.js 文件路由中间件配置,增加一个路由let sourceRouter = require(’./routes/source’);app.use(’/source’, sourceRouter);在routes下面新建 source.jslet express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取小说源 返回盗版源和正版源 param id {String} 是首页和搜索返回接口 books[i].id param n {Number || String} 使用第几个源,可以不用传参默认 1 http://api.zhuishushenqi.com/atoc?view=summary&book=${bookID}/router.get(’/’, function (req, res, next) { if (!req.query.id) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请传入ID…” })); } // req.query.id 编码转义 let id = encodeURI(req.query.id); request.get(${common.API}/atoc?view=summary&amp;book=${id}, function (err, response, body){ if(err){ res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); // 判断是否返回内容 if (body.length == 0){ res.send(JSON.stringify({ “flag”: 0, “msg”: “没有获取到小说源,换个小说看吧” })); } // 第一个源是正版源,是收费加密的,所以默认选中第二个源 let n = parseInt(req.query.n); if (isNaN(n) || n == 0){ n = 1; } // 判断n是否大于源数据的长度 if (n >= body.length){ res.send(JSON.stringify({ “flag”: 0, “msg”: “n的参数值不正确,没有那个源” })); }else{ res.send(JSON.stringify({ “flag”: 1, “books”: body[n], “msg”: “OK” })); } });});module.exports = router;访问http://localhost:3000/source/?id=50864bf69dacd30e3a000014&n=3 就可以看到返回第四个源的数据。小说文章列表修改app.js 文件路由中间件配置,增加一个路由let chapterRouter = require(’./routes/chapter’);app.use(’/chapter’, chapterRouter);在routes下面新建 chapter.jslet express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取小说文章列表 返回小说文章列表 param id {String} 是小说源接口 books.id http://api.zhuishushenqi.com/atoc/${id}?view=chapters/router.get(’/’, function (req, res, next) { if (!req.query.id){ res.send(JSON.stringify({ “flag”: 0, “msg”: “请传入ID…” })); } // req.query.id 编码转义 let id = encodeURIComponent(req.query.id); request.get(${common.API}/atoc/${id}?view=chapters, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } if (body == “wrong param”){ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入错误的ID…” })); }else{ // 解析返回的数据 body = JSON.parse(body); if (body.chapters.length > 0) { res.send(JSON.stringify({ “flag”: 1, “chapters”: body.chapters, “msg”: “OK” })); } } });});module.exports = router;访问http://localhost:3000/chapter/?id=57416370ccc94e4b41df80cc 就可以看到数据。id小说源接口返回的id。小说文章内容修改app.js 文件路由中间件配置,增加一个路由let articleRouter = require(’./routes/article’);app.use(’/article’, articleRouter);在routes下面新建 article.jslet express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取小说文章内容 返回小说文章内容 param link {String} 是小说文章列表接口 chapters[0].link http://chapter2.zhuishushenqi.com/chapter/${link}/router.get(’/’, function (req, res, next) { if (!req.query.link) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请传入link…” })); } // req.query.link 编码转义 let link = encodeURIComponent(req.query.link); request.get(${common.CHAPTER}/chapter/${link}, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); if (body.ok){ res.send(JSON.stringify({ “flag”: 1, “chapter”: body.chapter, “msg”: “OK” })); }else{ res.send(JSON.stringify({ “flag”: 0, “msg”: “传入link有错误” })); } });});module.exports = router;访问http://localhost:3000/article?link=http://www.69shu.com/txt/1463… 就可以看到数据。排行榜修改app.js 文件路由中间件配置,增加一个路由let rankingRouter = require(’./routes/ranking’);app.use(’/ranking’, rankingRouter);在routes下面新建 ranking.jslet express = require(’express’);let request = require(‘request’);let common = require(’../common/common.json’); // 引用公共文件let router = express.Router();/** 获取排行榜 返回排行榜 param id {String} 没有传参数就是获取全部榜单,否则根据参数获取榜单 http://api.zhuishushenqi.com/ranking/gender http://api.zhuishushenqi.com/ranking/${id}/router.get(’/’, function (req, res, next) { // 获取全部榜单 request.get(${common.API}/ranking/gender, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); if (body.ok) { let ranking = { male: body.male, picture: body.picture, epub: body.epub, female: body.female }; res.send(JSON.stringify({ “flag”: 1, “ranking”: ranking, “msg”: “OK” })); } else { res.send(JSON.stringify({ “flag”: 0, “msg”: “出错了” })); } });});router.get(’/:id’, function (req, res, next) { if (req.params.id) { // req.param.id 编码转义 let id = encodeURIComponent(req.params.id); // 根据id获取榜单 request.get(${common.API}/ranking/${id}, function (err, response, body) { if (err) { res.send(JSON.stringify({ “flag”: 0, “msg”: “请求出错了…” })); } // 解析返回的数据 body = JSON.parse(body); if (body.ok) { res.send(JSON.stringify({ “flag”: 1, “ranking”: body.ranking, “msg”: “OK” })); } else { res.send(JSON.stringify({ “flag”: 0, “msg”: “传入id错误” })); } }); }else{ res.send(JSON.stringify({ “flag”: 0, “msg”: “id没有传” })); }});module.exports = router;分别访问http://localhost:3000/ranking/ 和 http://localhost:3000/ranking/54d43437d47d13ff21cad58b 就可以获取到榜单的数据。1.0版本的开发就告于段落了。github仓库访问地址https://github.com/lanpangzhi/novel-api我的博客和GitHub地址https://github.com/lanpangzhihttp://blog.langpz.com参考https://github.com/expressjs/morganhttps://juejin.im/entry/593a3fdf61ff4b006c737ca4https://github.com/jianhui1012/bookreader/wiki/API-%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3 ...

April 8, 2019 · 3 min · jiezi

搭建node的项目并支持ES6写法

现在ES6在前端开发中已经非常普及了,所以前端转向node开发还可以继续保持ES6写法,现在node对ES6语法友好度越来越高,下面我们开始搭建一个支持ES6写法的node项目。同时使用nodemon配置项目源码修改时自动重启服务。首先要初始化一个项目npm init或npm init -y先安装一个express(根据自己的需求选择web服务框架)npm install express –save安装babel工具npm install babel-cli –save-devnpm install babel-plugin-transform-runtime –save-devnpm install babel-preset-env –save-devnpm install babel-preset-es2015 –save-devnpm install babel-preset-latest –save-devnpm install babel-register –save-dev安装@babel/runtimenpm install @babel/runtime –save安装 nodemon NODE项目代码修改时我们需要让项目自动编译更新npm install nodemon –save-dev安装 “rimraf” 项目文件打包时需要删除上次打包的文件目录npm install rimraf –save-devcross-env能跨平台地设置及使用环境变量npm install cross-env –save下面我们配置一下package.json文件,在scripts中添加下面的代码"build": “rimraf dist/ && babel ./ –out-dir dist/ –ignore ./node_modules,./.babelrc,./package.json,./npm-debug.log –copy-files”,“start”: “cross-env NODE_ENV=development nodemon –harmony index-api.js"然后新建一个文件.babelrc,将下面的代码写进入{ “presets”: [“es2015”], “plugins”: [“transform-runtime”]}到此基本的配置已经完成了,下面建立一个服务器文件 app.jsimport express from ’express’const app = express()app.listen(3000, function() { console.log(‘服务器启动成功!’)})此时如果急用node命令启动app.js会出现错误,因为import模块引入命令node不认,所以要用一个中间页面做一下过渡新建一个index.jsrequire(‘babel-register’) ({ presets: [ ’env’ ]})require(’./app.js’)此时用npm start index.js启动项目(用node命令直接启动index.js文件也不会报错了)下面我把我项目中的配置贴出来仅供大家参考package.json文件{ “name”: “node-api”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo "Error: no test specified" && exit 1”, “build”: “rimraf dist/ && babel ./ –out-dir dist/ –ignore ./node_modules,./.babelrc,./package.json,./npm-debug.log –copy-files”, “start”: “cross-env NODE_ENV=development nodemon –harmony index.js” }, “keywords”: [], “author”: “”, “license”: “ISC”, “dependencies”: { “@babel/runtime”: “^7.4.2”, “cookie-parser”: “^1.4.4”, “cross-env”: “^5.2.0”, “express”: “^4.16.4”, “express-session”: “^1.15.6”, “mysql”: “^2.16.0”, “node-uuid”: “^1.4.8” }, “devDependencies”: { “babel-cli”: “^6.26.0”, “babel-plugin-transform-runtime”: “^6.23.0”, “babel-preset-env”: “^1.7.0”, “babel-preset-es2015”: “^6.24.1”, “babel-preset-latest”: “^6.24.1”, “babel-register”: “^6.26.0”, “nodemon”: “^1.18.10”, “rimraf”: “^2.6.3” }}.babelrc{ “presets”: [“es2015”], “plugins”: [“transform-runtime”]}app.jsimport express from ’express’import router from ‘./router/index.js’import bodyParser from ‘body-parser’import cookieParser from ‘cookie-parser’import session from ’express-session’;const app = express();app.set(‘port’, process.env.PORT || 200); // 设定监听端口app.all(’’, (req, res, next) => { const { origin, Origin, referer, Referer } = req.headers; const allowOrigin = origin || Origin || referer || Referer || ‘’; res.header(“Access-Control-Allow-Origin”, allowOrigin); res.header(“Access-Control-Allow-Headers”, “Content-Type, Authorization, X-Requested-With”); res.header(“Access-Control-Allow-Methods”, “PUT,POST,GET,DELETE,OPTIONS”); res.header(“Access-Control-Allow-Credentials”, true); //可以带cookies res.header(“X-Powered-By”, ‘Express’); if (req.session) { console.log(‘进入’) } if (req.method == ‘OPTIONS’) { res.sendStatus(200); } else { next(); }});app.use(express.static(’./view’));app.use(bodyParser.json({limit: ‘1mb’})); // 这里指定参数使用 json 格式app.use(cookieParser()); // 挂载cookieapp.set(’trust proxy’, 1)app.use(session({ secret: ‘keyboardcat’, name: ’token’, //这里的name值得是cookie的name,默认cookie的name是:connect.sid rolling: true, cookie: {maxAge: 60 * 60 * 1000 , httpOnly:true}, //设置maxAge是80000ms,即80s后session和相应的cookie失效过期 resave: true, saveUninitialized: false}));router(app);app.listen(app.get(‘port’), () => { console.log(服务端口:${app.get('port')})})index.jsrequire(‘babel-register’) ({ presets: [ ’env’ ]})require(’./app.js’)到此就结束了,这是我项目中最基本的配置 ...

March 28, 2019 · 2 min · jiezi

React 服务端渲染从入门到精通

前言这篇文章是我自己在搭建个人网站的过程中,用到了服务端渲染,看了一些教程,踩了一些坑。想把这个过程分享出来。我会尽力把每个步骤讲明白,将我理解的全部讲出来。文中的示例代码来自于这个仓库,也是我正在搭建的个人网站,大家可以一起交流一下。本文中用到的技术React V16 | React-Router v4 | Redux | Redux-thunk | expressReact 服务端渲染服务端渲染的基本套路就是用户请求过来的时候,在服务端生成一个我们希望看到的网页内容的HTML字符串,返回给浏览器去展示。浏览器拿到了这个HTML之后,渲染出页面,但是并没有事件交互,这时候浏览器发现HTML中加载了一些js文件(也就是浏览器端渲染的js),就直接去加载。加载好并执行完以后,事件就会被绑定上了。这时候页面被浏览器端接管了。也就是到了我们熟悉的js渲染页面的过程。需要实现的目标:React组件服务端渲染路由的服务端渲染保证服务端和浏览器的数据唯一css的服务端渲染(样式直出)一般的渲染方式服务端渲染:服务端生成html字符串,发送给浏览器进行渲染。浏览器端渲染:服务端返回空的html文件,内部加载js完全由js与css,由js完成页面的渲染优点与缺点服务端渲染解决了首屏加载速度慢以及seo不友好的缺点(Google已经可以检索到浏览器渲染的网页,但不是所有搜索引擎都可以)但增加了项目的复杂程度,提高维护成本。如果非必须,尽量不要用服务端渲染整体思路需要两个端:服务端、浏览器端(浏览器渲染的部分)第一: 打包浏览器端代码第二: 打包服务端代码并启动服务第三: 用户访问,服务端读取浏览器端打包好的index.html文件为字符串,将渲染好的组件、样式、数据塞入html字符串,返回给浏览器第四: 浏览器直接渲染接收到的html内容,并且加载打包好的浏览器端js文件,进行事件绑定,初始化状态数据,完成同构React组件的服务端渲染让我们来看一个最简单的React服务端渲染的过程。要进行服务端渲染的话那必然得需要一个根组件,来负责生成HTML结构import React from ‘react’;import ReactDOM from ‘react-dom’;ReactDOM.hydrate(<Container />, document.getElementById(‘root’));当然这里用ReactDOM.render也是可以的,只不过hydrate会尽量复用接收到的服务端返回的内容,来补充事件绑定和浏览器端其他特有的过程引入浏览器端需要渲染的根组件,利用react的 renderToString API进行渲染import { renderToString } from ‘react-dom/server’import Container from ‘../containers’// 产生htmlconst content = renderToString(<Container/>)const html = &lt;html&gt; &lt;body&gt;${content}&lt;/body&gt; &lt;/html&gt;res.send(html)在这里,renderToString也可以替换成renderToNodeStream,区别在于前者是同步地产生HTML,也就是如果生成HTML用了1000毫秒,那么就会在1000毫秒之后才将内容返回给浏览器,显然耗时过长。而后者则是以流的形式,将渲染结果塞给response对象,就是出来多少就返回给浏览器多少,可以相对减少耗时路由的服务端渲染一般场景下,我们的应用不可能只有一个页面,肯定会有路由跳转。我们一般这么用:import { BrowserRouter, Route } from ‘react-router-dom’const App = () => ( <BrowserRouter> {/…Routes/} <BrowserRouter/>)但这是浏览器端渲染时候的用法。在做服务端渲染时,需要使用将BrowserRouter 替换为 StaticRouter区别在于,BrowserRouter 会通过HTML5 提供的 history API来保持页面与URL的同步,而StaticRouter则不会改变URLimport { createServer } from ‘http’import { StaticRouter } from ‘react-router-dom’createServer((req, res) => { const html = renderToString( <StaticRouter location={req.url} context={{}} > <Container /> <StaticRouter/>)})这里,StaticRouter要接收两个属性:location: StaticRouter 会根据这个属性,自动匹配对应的React组件,所以才会实现刷新页面,服务端返回的对应路由的组与浏览器端保持一致context: 一般用来传递一些数据,相当于一个载体,之后讲到样式的服务端渲染的时候会用到Redux同构数据的预获取以及脱水与注水我认为是服务端渲染的难点。这是什么意思呢?也就是说首屏渲染的网页一般要去请求外部数据,我们希望在生成HTML之前,去获取到这个页面需要的所有数据,然后塞到页面中去,这个过程,叫做“脱水”(Dehydrate),生成HTML返回给浏览器。浏览器拿到带着数据的HTML,去请求浏览器端js,接管页面,用这个数据来初始化组件。这个过程叫“注水”(Hydrate)。完成服务端与浏览器端数据的统一。为什么要这么做呢?试想一下,假设没有数据的预获取,直接返回一个没有数据,只有固定内容的HTML结构,会有什么结果呢?第一:由于页面内没有有效信息,不利于SEO。第二:由于返回的页面没有内容,但浏览器端JS接管页面后回去请求数据、渲染数据,页面会闪一下,用户体验不好。我们使用Redux来管理状态,因为有服务端代码和浏览器端代码,那么就分别需要两个store来管理服务端和浏览器端的数据。组件的配置组件要在服务端渲染的时候去请求数据,可以在组件上挂载一个专门发异步请求的方法,这里叫做loadData,接收服务端的store作为参数,然后store.dispatch去扩充服务端的store。class Home extends React.Component { componentDidMount() { this.props.callApi() } render() { return <div>{this.props.state.name}</div> }}Home.loadData = store => { return store.dispatch(callApi())}const mapState = state => stateconst mapDispatch = {callApi}export default connect(mapState, mapDispatch)(Home)路由的改造因为服务端要根据路由判断当前渲染哪个组件,可以在这个时候发送异步请求。所以路由也需要配置一下来支持loadData方法。服务端渲染的时候,路由的渲染可以使用react-router-config这个库,用法如下(重点关注在路由上挂载loadData方法):import { BrowserRouter } from ‘react-router-dom’import { renderRoutes } from ‘react-router-config’import Home from ‘./Home’export const routes = [ { path: ‘/’, component: Home, loadData: Home.loadData, exact: true, }]const Routers = <BrowserRouter> {renderRoutes(routes)}<BrowserRouter/>服务端获取数据到了服务端,需要判断匹配的路由内的所有组件各自都有没有loadData方法,有就去调用,传入服务端的store,去扩充服务端的store。同时还要注意到,一个页面可能是由多个组件组成的,会发各自的请求,也就意味着我们要等所有的请求都发完,再去返回HTML。import express from ’express’import serverRender from ‘./render’import { matchRoutes } from ‘react-router-config’import { routes } from ‘../routes’import serverStore from “../store/serverStore"const app = express()app.get(’*’, (req, res) => { const context = {css: []} const store = serverStore() // 用matchRoutes方法获取匹配到的路由对应的组件数组 const matchedRoutes = matchRoutes(routes, req.path) const promises = [] for (const item of matchedRoutes) { if (item.route.loadData) { const promise = new Promise((resolve, reject) => { item.route.loadData(store).then(resolve).catch(resolve) }) promises.push(promise) } } // 所有请求响应完毕,将被HTML内容发送给浏览器 Promise.all(promises).then(() => { // 将生成html内容的逻辑封装成了一个函数,接收req, store, context res.send(serverRender(req, store, context)) })})细心的同学可能注意到了上边我把每个loadData都包了一个promise。const promise = new Promise((resolve, reject) => { item.route.loadData(store).then(resolve).catch(resolve) console.log(item.route.loadData(store));})promises.push(promise)这是为了容错,一旦有一个请求出错,那么下边Promise.all方法则不会执行,所以包一层promise的目的是即使请求出错,也会resolve,不会影响到Promise.all方法,也就是说只有请求出错的组件会没数据,而其他组件不会受影响。注入数据我们请求已经发出去了,并且在组件的loadData方法中也扩充了服务端的store,那么可以从服务端的数据取出来注入到要返回给浏览器的HTML中了。来看 serverRender 方法const serverRender = (req, store, context) => { // 读取客户端生成的HTML const template = fs.readFileSync(process.cwd() + ‘/public/static/index.html’, ‘utf8’) const content = renderToString( <Provider store={store}> <StaticRouter location={req.path} context={context}> <Container/> </StaticRouter> </Provider> ) // 注入数据 const initialState = &lt;script&gt; window.context = { INITIAL_STATE: ${JSON.stringify(store.getState())} }&lt;/script&gt; return template.replace(’<!–app–>’, content) .replace(’<!–initial-state–>’, initialState)}浏览器端用服务端获取到的数据初始化store经过上边的过程,我们已经可以从window.context中拿到服务端预获取的数据了,此时需要做的事就是用这份数据去初始化浏览器端的store。保证两端数据的统一。import { createStore, applyMiddleware, compose } from ‘redux’import thunk from ‘redux-thunk’import rootReducer from ‘../reducers’const defaultStore = window.context && window.context.INITIAL_STATEconst clientStore = createStore( rootReducer, defaultStore,// 利用服务端的数据初始化浏览器端的store compose( applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f=>f ))至此,服务端渲染的数据统一问题就解决了,再来回顾一下整个流程:用户访问路由,服务端根据路由匹配出对应路由内的组件数组循环数组,调用组件上挂载的loadData方法,发送请求,扩充服务端store所有请求完成后,通过store.getState,获取到服务端预获取的数据,注入到window.context中浏览器渲染返回的HTML,加载浏览器端js,从window.context中取数据来初始化浏览器端的store,渲染组件这里还有个点,也就是当我们从路由进入到其他页面的时候,组件内的loadData方法并不会执行,它只会在刷新,服务端渲染路由的时候执行。这时候会没有数据。所以我们还需要在componentDidMount中去发请求,来解决这个问题。因为componentDidMount不会在服务端渲染执行,所以不用担心请求重复发送。样式的服务端渲染以上我们所做的事情只是让网页的内容经过了服务端的渲染,但是样式要在浏览器加载css后才会加上,u偶遇最开始返回的网页内容没有样式,页面依然会闪一下。为了解决这个问题,我们需要让样式也一并在服务端渲染的时候返回。首先,服务端渲染的时候,解析css文件,不能使用style-loader了,要使用isomorphic-style-loader。{ test: /.css$/, use: [ ‘isomorphic-style-loader’, ‘css-loader’, ‘postcss-loader’ ],}我们想,如何在服务端获取到当前路由内的组件样式呢?回想一下,我们在做路由的服务端渲染时,用到了StaticRouter,它会接收一个context对象,这个context对象可以作为一个载体来传递一些信息。我们就用它!思路就是在渲染组件的时候,在组件内接收context对象,获取组件样式,放到context中,服务端拿到样式,插入到返回的HTML中的style标签。来看看组件是如何读取样式的吧:import style from ‘./style/index.css’class Index extends React.Component { componentWillMount() { if (this.props.staticContext) { const css = styles._getCss() this.props.staticContext.css.push(css) } }}在路由内的组件可以在props里接收到staticContext,也就是通过StaticRouter传递过来的context,isomorphic-style-loader 提供了一个 _getCss() 方法,让我们能读取到css样式,然后放到staticContext里。不在路由之内的组件,可以通过父级组件,传递props的方法,或者用react-router的withRouter包裹一下在服务端,经过组件的渲染之后,context中已经有内容了,我们这时候把样式处理一下,返回给浏览器,就可以做到样式的服务端渲染了const serverRender = (req, store) => { const context = {css: []} const template = fs.readFileSync(process.cwd() + ‘/public/static/index.html’, ‘utf8’) const content = renderToString( <Provider store={store}> <StaticRouter location={req.path} context={context}> <Container/> </StaticRouter> </Provider> ) // 经过渲染之后,context.css内已经有了样式 const cssStr = context.css.length ? context.css.join(’\n’) : ’’ const initialState = &lt;script&gt; window.context = { INITIAL_STATE: ${JSON.stringify(store.getState())} }&lt;/script&gt; return template.replace(’<!–app–>’, content) .replace(‘server-render-css’, cssStr) .replace(’<!–initial-state–>’, initialState)}至此,服务端渲染就全部完成了。总结React的服务端渲染,最好的解决方案就是Next.js。如果你的应用没有SEO优化的需求,又或者不太注重首屏渲染的速度,那么尽量就不要用服务端渲染。因为会让项目变得复杂。此外,除了服务端渲染,SEO优化的办法还有很多,比如预渲染(pre-render)。 ...

March 27, 2019 · 2 min · jiezi

一个基于套接字实现长连接的express

一个基于套接字实现长连接的小型express逻辑: 首先把routerUrl目录下的函数初始化缓存起来,通过Router.request调用缓存起来的函数,这个函数实际上是register.set方法,主要是开始运行函数链,通过register.next 运行下一个函数。函数流 main.js –> Router.request –> register.set –> register.next –> sock.writemain.js’use strict’;const routerUrl = ‘router’; // 当前目录下的router地址const Router = require(’./net/Router’); // 初始化路由const net = require(’net’);const port = ‘3000’;Router.init(routerUrl);const app = sock => { sock.on(‘data’, function (data) { try { Router.request(data, sock); } catch (error) { console.log(error) } }); sock.on(’error’, (err) => { console.log(err) }) // 为这个socket实例添加一个"close"事件处理函数 sock.on(‘close’, function (data) { console.log(‘clone’) })}const server = net.createServer(app);server.listen(port, () => { console.log(Startu in env ${process.env.NODE_ENV || 'development'} on port ${port});});server.on(’error’, (err) => { console.log(err)})路由加载:Router.js文件const fs = require(‘fs’);const _ = require(’lodash’);var path = require(“path”);var ROOT_PATH = path.resolve(__dirname);class Router { constructor() { this.routeMap = {}; } /** * 通过routerUrl来匹配目录下的文件,加载进来 * @param {} routerUrl / init(routerUrl) { let files = fs.readdirSync(path.join(ROOT_PATH, ../${routerUrl})); return _.reduce(files, (config, file) => { let svc = require(path.join(ROOT_PATH, ../${routerUrl}/${file})); this.routeMap = { [file.split(’.’)[0]]: svc.get() }; }, {}) } /* * 通过url匹配加载的router, 其他字段可自定义,url这里的逻辑也可改成配置文件进行配置,类似于protobuf * @param {} data {url, body} * @param {*} sock */ request(data, sock) { try { this.routeMap[result.url.split(’/’)[1]][result.url.replace(/${result.url.split('/')[1]}, ‘’)](data, sock); } catch (error) { sock.write(error); } }}module.exports = new Router();中间件:register.js文件const Next = require(’./next’);class Register { constructor() { this._init = {}; } <!– 初始化router函数,开始运行函数链 –> set(url, …handlers) { this._init[url] = async (data, sock) => { try { let next = new Next(handlers); next.run(data, sock); } catch (error) { sock.write(error); } }; } <!– 获取初始化的router函数 –> get() { return this._init; }}module.exports = new Register();nest.js文件class Next { constructor(stack) { this.index = 0; this.stack = stack; this.data = null; this.sock = null; } <!– 运行中间件 –> run(data, sock) { this.data = data; this.sock = sock; this.stack[this.index](data, sock, this.next.bind(this)); } <!– 调到下一个中间件,若带参数就跳到第arguments[0]步 –> next() { if (arguments[0] && arguments[0] === +arguments[0] && +arguments[0] < this.stack.length) { this.index = +arguments[0]; return this.run(data, this.sock); } this.index++; this.run(this.data, this.sock); }}module.exports = Next;注册文件const init = require(’../net/register’);init.set(’/test’, (data, sock, next) => { next() }, async (data, sock) => { try { sock.write(test); } catch (e) { sock.write(e); } }); 总结:这个项目只是用来歇息express的思想,要用在实际开发中还需要断线重连,优化连接,异常处理等功能。 ...

March 26, 2019 · 2 min · jiezi

联科首个开源项目启动!未来可期,诚邀加入!

OpenEA开源组织是广州市联科软件有限公司旗下的一个“开放·共享·全球化”的开源组织。OpenEA全称“Open+Enterprise+Application”,意为开放的企业应用,致力让所有企业都能轻松用上流程应用开发平台。2019年联科将逐步开源基于流程应用的快速开发平台,以“专业·高效·创新·自由·开放·回馈”为理念,以“开源之林”为目标,构建开放的技术生态圈。最近【流程设计器组件FlowDesigner】作为第一个开源项目,主要用于设计和控制流程各个运转过程,欢迎广大技术好友前来码云交流探讨!流程设计器组件FlowDesigner前言FlowDesigner来源于Linkey BPM中的流程设计器,作用于流程运行过程中的图形描述。它的操作简捷轻巧,能快速绘制出流程图。组件单独也可以使用,并能嵌入到任何需要该组件的系统中。分享,是“开源”的真谛。机不可失失不再来,准备好加入我们了吗?立即前往码云Fork项目吧,地址:https://gitee.com/openEA/Flow…

March 25, 2019 · 1 min · jiezi

前端监控数据收集(请求拦截)

所谓web,即使你我素未谋面,便知志趣相投;足不出户,亦知世界之大。01 — 为什么拦截请求现在的web应用,大都是通过请求(http)去获取资源,拿到资源后再呈现给用户,一个页面中可以有多个这样的请求。每一次请求的开始,等待,完成,异常都会有相应的状态来标识。我们在自己的框架中通常都会使用一个全局过滤器,来拦截请求,目的大同小异:在发送请求之前,修改请求参数,添加请求头请求发送中的进度计算(通常是文件上传)请求出错后的捕获请求结束后,处理后台返回数据结构,进行适配……看看请求的整个流程图:而我们最常用的发送请求的便是XMLHttpRequest。XMLHttpRequest.readyState的五种就绪状态:0:请求未初始化(还没有调用 open())。1:请求已经建立,但是还没有发送(还没有调用 send())。2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。3:请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。4:响应已完成;您可以获取并使用服务器的响应了。并且XMLHttpRequest还提供了每个阶段的事件:abort如果请求中止,会触发abort事件。error网络错误(如太多重定向)会阻止请求完成,会触发error事件。load当事件完成,会触发load事件。loadend当一个请求完成,无论成功(load)或者不成功(abort/error)后触发loadstart当调用send()时,触发单个loadstart事件。progress当等待服务器的响应时,XHR对象会发生progress事件。通常每隔50毫秒左右,所以可以使用这事件给用户反馈请求的进度。timeout当等待服务器的响应超时会触发。02 — 如何拦截请求了解了XMLHttpRequest的请求流程后,我们就可以开始去拦截浏览器发出的请求,去做我们想做的事。方式一:(function (xhr) {// Capture request before any network activity occurs: var send = xhr.send; xhr.send = function (data) { this.addEventListener(’loadstart’, onLoadStart); this.addEventListener(’loadend’, onLoadEnd); this.addEventListener(’error’, onError); return send.apply(this, arguments); };})(XMLHttpRequest.prototype);这种是最简单直接的方式,修改XMLHttpRequest的原型,在发送请求时开启事件监听。大多数情况下都是没什么大问题的,但后来发现在Angular4+以上版本中这样去拦截,请求触发loadend事件后获取到的请求响应成功与否状态始终为false,因为Angualr2后来的版本也使用事件监听来处理拦截,有些地方就冲突了。方式二:出现问题总要解决吧,然后就采用方法一的升级版本,完全重写XMLHttpRequest。(function () { // create XMLHttpRequest proxy object var oldXMLHttpRequest = XMLHttpRequest; // define constructor for my proxy object window.XMLHttpRequest = function () { var actual = new oldXMLHttpRequest(); var self = this; this.onreadystatechange = null; // this is the actual handler on the real XMLHttpRequest object actual.onreadystatechange = function () { if (this.readyState == 1) { onLoadStart.call(this); } else if (this.readyState == 4) { if(this.status==200) onLoadEnd.call(this); else{ onError.call(this); } } if (self.onreadystatechange) { return self.onreadystatechange(); } };// add all proxy getters[“status”, “statusText”, “responseType”, “response”,“readyState”, “responseXML”, “upload”].forEach(function (item) { Object.defineProperty(self, item, { get: function () { return actual[item]; }, set: function (val) { actual[item] = val; } });});// add all proxy getters/setters[“ontimeout, timeout”, “withCredentials”, “onload”, “onerror”, “onprogress”].forEach(function (item) { Object.defineProperty(self, item, { get: function () { return actual[item]; }, set: function (val) { actual[item] = val; } });});// add all pure proxy pass-through methods[“addEventListener”, “send”, “open”, “abort”, “getAllResponseHeaders”,“getResponseHeader”, “overrideMimeType”, “setRequestHeader”, “removeEventListener”].forEach(function (item) { Object.defineProperty(self, item, { value: function () { return actual[item].apply(actual, arguments); } }); }); }})();03 — 项目实战现在我们可以放心的拦截浏览器发出的请求了,妈妈再也不用担心我的学习了,哈哈。说一千道一万,来点干货,直接看项目。传送门:web-monitor喜欢请点个赞呗或者去https://github.com/kisslove/w… Star一下或者打赏一下再或者……哈哈,想法有点多了。 ...

March 19, 2019 · 1 min · jiezi

express.js中间件说明

express的新开发人员往往对路由处理程序和中间件之间的区别感到困惑。因此他们也对app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()方法的区别感到困惑。在本文中,我将解释中间件和路由处理程序之间的区别。以及如何正确使用app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()方法。路由处理app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()全部是用来定义路由的。这些方法都用于定义路由。路由用于处理HTTP请求。路由是路径和回调的组合,在请求的路径匹配时执行。回调被称为路由处理程序。它们之间的区别是处理不同类型的HTTP请求。例如: app.get()方法仅仅处理get请求,而app.all()处理GET、POST等请求。下面是一个例子,如何定义一个路由:var app = require(“express”)();app.get("/", function(req, res, next){ res.send(“Hello World!!!!”);});app.listen(8080);每个路由处理程序都获得对当前正在提供的HTTP请求的请求和响应对象的引用。可以为单个HTTP请求执行多个路由处理程序。这是一个例子:var app = require(“express”)();app.get("/", function(req, res, next){ res.write(“Hello”); next();});app.get("/", function(req, res, next){ res.write(" World !!!"); res.end();});app.listen(8080);这里第一个句柄写入一些响应,然后调用next()。 next()方法用于调用与路径路径匹配的下一个路由处理程序。路由处理程序必须结束请求或调用下一个路由处理程序。我们还可以将多个路由处理程序传递给app.all(),app.get(),app.post(),app.delete()和app.put()方法。这是一个证明这一点的例子:var app = require(“express”)();app.get("/", function(req, res, next){ res.write(“Hello”); next();}, function(req, res, next){ res.write(" World !!!"); res.end();});app.listen(8080);中间件中间件是一个位于实际请求处理程序之上的回调。它采用与路由处理程序相同的参数。要了解中间件,我们来看一个带有dashboard和profile页面的示例站点。要访问这些页面,用户必须登录。还会记录对这些页面的请求。以下是这些页面的路由处理程序的代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.get("/dashboard", function(req, res, next){ logRequest(); if(checkLogin()){ res.send(“This is the dashboard page”); } else{ res.send(“You are not logged in!!!”); }});app.get("/profile", function(req, res, next){ logRequest(); if(checkLogin()){ res.send(“This is the dashboard page”); } else{ res.send(“You are not logged in!!!”); }});app.listen(8080);这里的问题是有很多重复的代码,即我们不得不多次使用logRequest()和checkLogin()函数。这也使得更新代码变得困难。因此,为了解决这个问题,我们可以为这两条路径编写一条通用路径。这是重写的代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.get("/", function(req, res, next){ logRequest(); next();})app.get("/", function(req, res, next){ if(checkLogin()){ next(); } else{ res(“You are not logged in!!!”); }})app.get("/dashboard", function(req, res, next){ res.send(“This is the dashboard page”);});app.get("/profile", function(req, res, next){ res.send(“This is the dashboard page”);});app.listen(8080);这里的代码看起来更清晰,更易于维护和更新。这里将前两个定义的路由处理程序称为中间件,因为它们不处理请求,而是负责预处理请求。Express为我们提供了app.use()方法,该方法专门用于定义中间件。 app.use()方法可能看起来与app.all()类似,但它们之间存在很多差异,这使得app.use()非常适合于声明中间件。让我们看看app.use()方法是如何工作的:app.use() 和 app.all() 的不同:CALLBACKapp.use()只需要一个回调,而app.all()可以进行多次回调。PATHapp.use()只查看url是否以指定路径开头,app.all()匹配完整路径。这里有一个例子来说明:app.use( “/product” , mymiddleware);// will match /product// will match /product/cool// will match /product/fooapp.all( “/product” , handler);// will match /product// won’t match /product/cool <– important// won’t match /product/foo <– importantapp.all( “/product/” , handler);// won’t match /product <– Important// will match /product/cool// will match /product/fooNEXT()中间件内的next()调用下一个中间件或路由处理程序,具体取决于接下来声明的那个。但是路由处理程序中的next()仅调用下一个路由处理程序。如果接下来有中间件,则跳过它。因此,必须在所有路由处理程序之前声明中间件。这里有一个例子来说明:var express = require(’express’);var app = express();app.use(function frontControllerMiddlewareExecuted(req, res, next){ console.log(’(1) this frontControllerMiddlewareExecuted is executed’); next();});app.all(’’, function(req, res, next){ console.log(’(2) route middleware for all method and path pattern “*”, executed first and can do stuff before going next’); next();});app.all(’/hello’, function(req, res, next){ console.log(’(3) route middleware for all method and path pattern “/hello”, executed second and can do stuff before going next’); next();});app.use(function frontControllerMiddlewareNotExecuted(req, res, next){ console.log(’(4) this frontControllerMiddlewareNotExecuted is not executed’); next();});app.get(’/hello’, function(req, res){ console.log(’(5) route middleware for method GET and path patter “/hello”, executed last and I do my stuff sending response’); res.send(‘Hello World’);});app.listen(80);现在我们看到了app.use()方法的唯一性以及它用于声明中间件的原因。让我们重写我们的示例站点代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.use(function(req, res, next){ logRequest(); next();})app.use(function(req, res, next){ if(checkLogin()){ next(); } else{ res.send(“You are not logged in!!!”); }})app.get("/dashboard", function(req, res, next){ res.send(“This is the dashboard page”);});app.get("/profile", function(req, res, next){ res.send(“This is the dashboard page”);});app.listen(8080); ...

March 18, 2019 · 2 min · jiezi

一次网站的性能优化之路 -- 天下武功,唯快不破

首屏作为直面用户的第一屏,其重要性不言而喻,如何加快加载的速度是非常重要的一课。本文讲解的是:笔者对自己搭建的个人博客网站的速度优化的经历。效果体验地址: http://biaochenxuying.cn1. 用户期待的速度体验2018 年 8 月,百度搜索资源平台发布的《百度移动搜索落地页体验白皮书 4.0 》中提到:页面的首屏内容应在 1.5 秒内加载完成。也许有人有疑惑:为什么是 1.5 秒内?哪些方式可加快加载速度?以下将为您解答这些疑问!移动互联网时代,用户对于网页的打开速度要求越来越高。百度用户体验部研究表明,页面放弃率和页面的打开时间关系如下图所示:根据百度用户体验部的研究结果来看,普通用户期望且能够接受的页面加载时间在 3 秒以内。若页面的加载时间过慢,用户就会失去耐心而选择离开,这对用户和站长来说都是一大损失。百度搜索资源平台有 “闪电算法” 的支持,为了能够保障用户体验,给予优秀站点更多面向用户的机会,“闪电算法”在 2017 年 10 月初上线。闪电算法 的具体内容如下:移动网页首屏在 2 秒之内完成打开的,在移动搜索下将获得提升页面评价优待,获得流量倾斜;同时,在移动搜索页面首屏加载非常慢(3 秒及以上)的网页将会被打压。2. 分析问题未优化之前,首屏时间居然大概要 7 - 10 秒,简直不要太闹心。开始分析问题,先来看下 network :主要问题:第一个文章列表接口用了 4.42 秒其他的后端接口速度也不快另外 js css 等静态的文件也很大,请求的时间也很长我还用了 Lighthouse 来测试和分析我的网站。Lighthouse 是一个开源的自动化工具,用于改进网络应用的质量。 你可以将其作为一个 Chrome 扩展程序运行,或从命令行运行。 为 Lighthouse 提供一个需要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。未优化之前:上栏内容分别是页面性能、PWA(渐进式 Web 应用)、可访问性(无障碍)、最佳实践、SEO 五项指标的跑分。下栏是每一个指标的细化性能评估。再看下 Lighthouse 对性能问题给出了可行的建议、以及每一项优化操作预期会帮我们节省的时间:从上面可以看出,主要问题:图片太大一开始图片就加载了太多知道问题所在就已经成功了一半了,接下来便开始优化之路。2. 优化之路网页速度优化的方法实在太多,本文只说本次优化用到的方法。2.1 前端优化本项目前端部分是用了 react 和 antd,但是 webpack 用的还是 3.8.X 。2.1.1 webpack 打包优化因为 webpack4 对打包做了很多优化,比如 Tree-Shaking ,所以我用最新的 react-create-app 重构了一次项目,把项目升级了一遍,所有的依赖包都是目前最新的稳定版了,webpack 也升级到了 4.28.3 。用最新 react-create-app 创建的项目,很多配置已经是很好了的,笔者只修改了两处地方。打包配置修改了 webpack.config.js 的这一行代码:// Source maps are resource heavy and can cause out of memory issue for large source files.const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== ‘false’;// 把上面的代码修改为: const shouldUseSourceMap = process.env.NODE_ENV === ‘production’ ? false : true;生产环境下,打包去掉 SourceMap,静态文件就很小了,从 13M 变成了 3M 。还修改了图片打包大小的限制,这样子小于 40K 的图片都会变成 base64 的图片格式。{ test: [/.bmp$/, /.gif$/, /.jpe?g$/, /.png$/,/.jpg$/,/.svg$/], loader: require.resolve(‘url-loader’), options: { limit: 40000, // 把默认的 10000 修改为 40000 name: ‘static/media/[name].[hash:8].[ext]’, }, }2.1.2 去掉没用的文件比如之前可能觉得会有用的文件,后面发现用不到了,注释或者删除,比如 reducers 里面的 home 模块。import { combineReducers } from ‘redux’import { connectRouter } from ‘connected-react-router’// import { home } from ‘./module/home’import { user } from ‘./module/user’import { articles } from ‘./module/articles’const rootReducer = (history) => combineReducers({ // home, user, articles, router: connectRouter(history)})2.1.3 图片处理把一些静态文件再用 photoshop 换一种格式或者压缩了一下, 比如 logo 图片,原本 111k,压缩后是 23K。首页的文章列表图片,修改为懒加载的方式加载。之前因为不想为了个懒加载功能而引用一个插件,所以想自己实现,看了网上关于图片懒加载的一些代码,再结合本项目,实现了一个图片懒加载功能,加入了 事件的节流(throttle)与防抖(debounce)。代码如下:// fn 是事件回调, delay 是时间间隔的阈值function throttle(fn, delay) { // last 为上一次触发回调的时间, timer 是定时器 let last = 0, timer = null; // 将throttle处理结果当作函数返回 return function() { // 保留调用时的 this 上下文 let context = this; // 保留调用时传入的参数 let args = arguments; // 记录本次触发回调的时间 let now = +new Date(); // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值 if (now - last < delay) { // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器 clearTimeout(timer); timer = setTimeout(function() { last = now; fn.apply(context, args); }, delay); } else { // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应 last = now; fn.apply(context, args); } };}// 获取可视区域的高度const viewHeight = window.innerHeight || document.documentElement.clientHeight;// 用新的 throttle 包装 scroll 的回调const lazyload = throttle(() => { // 获取所有的图片标签 const imgs = document.querySelectorAll(’#list .wrap-img img’); // num 用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出 let num = 0; for (let i = num; i < imgs.length; i++) { // 用可视区域高度减去元素顶部距离可视区域顶部的高度 let distance = viewHeight - imgs[i].getBoundingClientRect().top; // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出 if (distance >= 100) { // 给元素写入真实的 src,展示图片 let hasLaySrc = imgs[i].getAttribute(‘data-has-lazy-src’); if (hasLaySrc === ‘false’) { imgs[i].src = imgs[i].getAttribute(‘data-src’); imgs[i].setAttribute(‘data-has-lazy-src’, true); // } // 前 i 张图片已经加载完毕,下次从第 i+1 张开始检查是否露出 num = i + 1; } }}, 1000);注意:给元素写入真实的 src 了之后,把 data-has-lazy-src 设置为 true ,是为了避免回滚的时候再设置真实的 src 时,浏览器会再请求这个图片一次,白白浪费服务器带宽。具体细节请看文件 文章列表2.2 后端优化后端用到的技术是 node、express 和 mongodb。后端主要问题是接口速度很慢,特别是文章列表的接口,已经是分页请求数据了,为什么还那么慢呢 ?所以查看了接口返回内容之后,发现返回了很多列表不展示的字段内容,特别是文章内容都返回了,而文章内容是很大的,占用了很多资源与带宽,从而使接口消耗的时间加长。从上图可以看出文章列表接口只要返回文章的 标题、描述、封面、查看数,评论数、点赞数和时间即可。所以把不需要给前端展示的字段注释掉或者删除。// 待返回的字段 let fields = { title: 1, // author: 1, // keyword: 1, // content: 1, desc: 1, img_url: 1, tags: 1, category: 1, // state: 1, // type: 1, // origin: 1, // comments: 1, // like_User_id: 1, meta: 1, create_time: 1, // update_time: 1, };同样对其他的接口都做了这个处理。后端做了处理之后,所有的接口速度都加快了,特别是文章列表接口,只用了 0.04 - 0.05 秒左右,相比之前的 4.3 秒,速度提高了 100 倍,简直不要太爽, 效果如下:此刻心情如下:2.3 服务器优化你以为前后端都优化一下,本文就完了 ?小兄弟,你太天真了,重头戏在后头 !笔者服务器用了 nginx 代理。做的优化如下:隐藏 nginx 版本号一般来说,软件的漏洞都和版本相关,所以我们要隐藏或消除 web 服务对访问用户显示的各种敏感信息。如何查看 nginx 版本号? 直接看 network 的接口或者静态文件请求的 Response Headers 即可。没有设置之前,可以看到版本号,比如我网站的版本号如下:Server: nginx/1.6.2设置之后,直接显示 nginx 了,没有了版本号,如下:Server: nginx开启 gzip 压缩nginx 对于处理静态文件的效率要远高于 Web 框架,因为可以使用 gzip 压缩协议,减小静态文件的体积加快静态文件的加载速度、开启缓存和超时时间减少请求静态文件次数。笔者开启 gzip 压缩之后,请求的静态文件大小大约减少了 2 / 3 呢。gzip on;#该指令用于开启或关闭gzip模块(on/off)gzip_buffers 16 8k;#设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。16 8k代表以8k为单位,安装原始数据大小以8k为单位的16倍申请内存gzip_comp_level 6;#gzip压缩比,数值范围是1-9,1压缩比最小但处理速度最快,9压缩比最大但处理速度最慢gzip_http_version 1.1;#识别http的协议版本gzip_min_length 256;#设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。默认值是0,不管页面多大都压缩。这里我设置了为256gzip_proxied any;#这里设置无论header头是怎么样,都是无条件启用压缩gzip_vary on;#在http header中添加Vary: Accept-Encoding ,给代理服务器用的gzip_types text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml text/javascript application/javascript application/x-javascript text/x-json application/json application/x-web-app-manifest+json text/css text/plain text/x-component font/opentype font/ttf application/x-font-ttf application/vnd.ms-fontobject image/x-icon;#进行压缩的文件类型,这里特别添加了对字体的文件类型gzip_disable “MSIE [1-6].(?!.*SV1)”;#禁用IE 6 gzip把上面的内容加在 nginx 的配置文件 ngixn.conf 里面的 http 模块里面即可。是否设置成功,看文件请求的 Content-Encoding 是不是 gzip 即可。设置 expires,设置缓存 server { listen 80; server_name localhost; location / { root /home/blog/blog-react/build/; index index.html; try_files $uri $uri/ @router; autoindex on; expires 7d; # 缓存 7 天 } }我重新刷新请求的时候是 2019 年 3 月 16 号,是否设置成功看如下几个字段就知道了:Staus Code 里面的 form memory cache 看出,文件是直接从本地浏览器本地请求到的,没有请求服务器。Cache-Control 的 max-age= 604800 看出,过期时间为 7 天。Express 是 2019 年 3 月 23 号过期,也是 7 天过期。注意:上面最上面的用红色圈中的 Disable cache 是否是打上了勾,打了勾表示:浏览器每次的请求都是请求服务器,无论本地的文件是否过期。所以要把这个勾去掉才能看到缓存的效果。终极大招:服务端渲染 SSR,也是笔者接下来的方向。3.1 测试场景一切优化测试的结果脱离了实际的场景都是在耍流氓,而且不同时间的网速对测试结果的影响也是很大的。所以笔者的测试场景如下:a. 笔者的服务器是阿里的,配置是入门级的学生套餐配置,如下:b. 测试网络为 10 M 光纤宽带。3.2 优化结果优化之后的首屏速度是 2.07 秒。最后加了缓存的结果为 0.388 秒。再来看下 Lighthouse 的测试结果:比起优化之前,各项指标都提升了很大的空间。4. 最后优化之路漫漫,永无止境,天下武功,唯快不破。本次优化的前端与后端项目,都已经开源在 github 上了,欢迎围观。前端:https://github.com/biaochenxuying/blog-react后端:https://github.com/biaochenxuying/blog-nodegithub 博客地址:https://github.com/biaochenxuying/blog如果您觉得这篇文章不错或者对你有所帮助,请给个赞或者星呗,你的点赞就是我继续创作的最大动力。关注公众号并回复 福利 可领取免费学习资料,福利详情请猛戳: 免费资源获取–Python、Java、Linux、Go、node、vue、react、javaScript ...

March 17, 2019 · 3 min · jiezi

TodoList:适合初学者的vue+node小项目

TodoList一个简单的vue + nodejs项目,前端由vue实现,后端由nodejs(express),数据库采用mongodb。github: https://github.com/xiechengbo/netease_todos在线效果展示: http://www.chengbo.xyz前端使用vue-cli脚手架, vue+axio实现的功能(1) 单条添加todo(2) 单条删除todo(3) 双击编辑todo(4) 单条todo已完成相应样式状态改变(5) 全部todo是已完成相应样式状态改变(6) 清除全部已完成todos(7) 待办todos数量显示(8) 所有todos,已完成todos,未完成todos筛选接口展示 import axios from ‘axios’ const baseUrl = process.env.NODE_ENV === “development” ? “https://nei.netease.com/api/apimock/f3e5d93d5eaceda5a624378374ad5cd7" : “http://148.70.150.147:8080” export const getAllTask = params => { return axios.get(${baseUrl}/api/all, {params: params}) } export const addTask = params => { return axios.post(${baseUrl}/api/add, params).then(res => res.data) } export const deleteTask = params => { return axios.post(${baseUrl}/api/deletes, params).then(res => res.data) } export const updateTask = params => { return axios.post(${baseUrl}/api/update, params).then(res => res.data) } export const updateManyTask = params => { return axios.post(${baseUrl}/api/updateMany, params).then(res => res.data) } export const deleteCompletedTask = params => { return axios.post(${baseUrl}/api/deletemany, params) }后端1.后台由express + mongoodb构建。2.路由 module.exports = function(app) { app.get(’/api/all’, TodoController.getAll); app.post(’/api/add’, TodoController.newTodo); app.post(’/api/deletes’, TodoController.deleteOne); app.post(’/api/deletemany’, TodoController.deleteAllCompleted); app.post(’/api/update’, TodoController.updateOne); app.post(’/api/updateMany’, TodoController.updateMany); ….项目启动$ npm install// or$ yarn install开发环境$ npm run dev完整代码点我, 记得star哦!! ...

March 16, 2019 · 1 min · jiezi

Express与Koa中间件机制分析(一)

提到 Node.js 开发,不得不提目前炙手可热的2大框架 Express 和 Koa。 Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。目前使用人数众多。Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。相信对这两大框架有一些了解的人都或多或少的会了解其中间件机制,Express 为线型模型,而 Koa 则为洋葱型模型。这个系列的博客主要讲解 Express 和 Koa 的中间件机制,本篇将主要讲解 Express 的中间件机制。Express 中间件connect 曾经是 express 3.x 之前的核心,而 express 4.x 已经把 connect 移除,在 express 中自己实现了 connect 的接口,所以我们本篇中的源码解释将直接使用 connect 源码。示例下面将使用 Express 实现一个简单的 demo 来进行中间件机制的讲解var express = require(’express’);var app = express();app.use(function (req, res, next) { console.log(‘第一个中间件start’); setTimeout(() => { next(); }, 1000) console.log(‘第一个中间件end’);});app.use(function (req, res, next) { console.log(‘第二个中间件start’); setTimeout(() => { next(); }, 1000) console.log(‘第二个中间件end’);});app.use(’/foo’, function (req, res, next) { console.log(‘接口逻辑start’); next(); console.log(‘接口逻辑end’);});app.listen(4000);此时的输出比较符合我们对 Express 线性的理解,其输出为第一个中间件start第一个中间件end第二个中间件start第二个中间件end接口逻辑start接口逻辑end但是,如果我们取消掉中间件内部的异步处理直接调用 next()var express = require(’express’);var app = express();app.use(function (req, res, next) { console.log(‘第一个中间件start’); next() console.log(‘第一个中间件end’);});app.use(function (req, res, next) { console.log(‘第二个中间件start’); next() console.log(‘第二个中间件end’);});app.use(’/foo’, function (req, res, next) { console.log(‘接口逻辑start’); next(); console.log(‘接口逻辑end’);});app.listen(4000);输出结果为第一个中间件start第二个中间件start接口逻辑start接口逻辑end第二个中间件end第一个中间件end这种结果不是和 Koa 的输出很相似吗?是的,但是它和剥洋葱模型还是不一样的,其实这种输出的结果是由于代码的同步运行导致的,并不是说 Express 不是线性的模型。当我们的中间件内没有进行异步操作时,其实我们的代码最后是以下面这种方式运行的app.use(function middleware1(req, res, next) { console.log(‘第一个中间件start’) // next() (function (req, res, next) { console.log(‘第二个中间件start’) // next() (function (req, res, next) { console.log(‘接口逻辑start’) // next() (function handler(req, res, next) { // do something })() console.log(‘接口逻辑end’) })() console.log(‘第二个中间件end’) })() console.log(‘第一个中间件end’)})导致这种运行方式的其实就是 connect 的实现方式,接下来我们进行其源码的解析connect 源码解析connect的源码仅有200多行,但是这里讲解只选择其中部分核心代码,并非完整代码,查看全部代码请移步 github中间件的挂载主要依赖 proto.use 和 proto.handle,这里我们删掉部分 if 判断以使我们更专注于其内部原理的实现proto.use = function use(route, fn) { var handle = fn; var path = route; // 这里是对直接填入回调函数的进行容错处理 // default route to ‘/’ if (typeof route !== ‘string’) { handle = route; path = ‘/’; } . . . this.stack.push({ route: path, handle: handle }); return this;};proto.use 主要将我们需要挂载的中间件存储在其自身 stack 属性上,同时进行部分兼容处理,这一块比较容易理解。其中间件机制的核心为 proto.handle 内部 next 方法的实现。proto.handle = function handle(req, res, out) { var index = 0; var stack = this.stack; function next(err) { // next callback var layer = stack[index++]; // all done if (!layer) { defer(done, err); return; } // route data var path = parseUrl(req).pathname || ‘/’; var route = layer.route; // skip this layer if the route doesn’t match if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) { return next(err); } // call the layer handle call(layer.handle, route, err, req, res, next); } next();};在删除到部分非核心代码后,可以清晰的看到,proto.handle 的核心就是 next 方法的实现和递归调用,对存在于 stack 中的中间件取出、执行。这里便可以解释上文中异步和非异步过程中所输出的结果的差异了。当有异步代码时,将会直接跳过继续执行,此时的 next 方法并未执行,需要等待当前队列中的事件全部执行完毕,所以此时我们输出的数据是线性的。当 next 方法直接执行时,本质上所有的代码都已经为同步,所以层层嵌套,最外层的肯定会在最后,输出了类似剥洋葱模型的结果。总结connect 的实现其基本原理是维护一个 stack 数组,将所需要挂载的中间件处理后全部 push 到数组内,之后在数组内循环执行 next 方法,直至所有中间件挂载完毕,当然这过程中会做一些异常、兼容等的处理。 ...

March 14, 2019 · 2 min · jiezi

个推微服务网关架构实践

作者:个推应用平台基础架构高级研发工程师 阿飞在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。因此,在客户端和服务端之间增加一个API网关成为多数微服务架构的必然选择。在个推的微服务实践中,API网关也起着至关重要的作用。一方面,API网关是个推微服务体系对外的唯一入口;另一方面,API网关中实现了很多后端服务的共性需求,避免了重复建设。个推微服务网关的设计与实现个推微服务主要是基于Docker和Kubernetes进行实践的。在整个微服务架构中,最底层的是个推私有部署的Kubernetes集群,在集群之上,部署了应用服务。个推的应用服务体系共分为三层,最上一层是网关层,接着是业务层,最下面是基础层服务。在部署应用服务时,我们使用了Kubernetes的命名空间对不同产品线的产品进行隔离。除了应用服务外, Kubernetes集群上还部署了Consul来实现配置的管理、Kube-DNS实现服务注册与发现,以及一些辅助系统来进行应用和集群的管理。下图是个推微服务体系的架构图。个推对API网关的功能需求主要有以下几方面:要支持配置多个产品,为不同的产品提供不同的端口;动态路由;URI的重写;服务的注册与发现;负载均衡;安全相关的需求,如session校验等;流量控制;链路追踪;A/B Testing。在对市面上已有的网关产品进行调研后,我们的技术团队发现,它们并不太适合应用于个推的微服务体系。第一,个推配置的管理都是基于Consul实现的,而大部分网关产品都需要基于一些DB存储,来进行配置的管理;第二,大部分的网关产品提供的功能比较通用,也比较完善,这同时也降低了配置的复杂度以及灵活性;第三,大部分的网关产品很难直接融入到个推的微服务架构体系中。最终,个推选择使用了OperResty和Lua进行自研网关,在自研的过程中,我们也借鉴了其他网关产品的一些设计,如Kong和Orange的插件机制等。个推的API网关的插件设计如下图所示。OpenResty对请求的处理分为多个阶段。个推API网关的插件主要是在Set、Rewrite、Access、Header_filter、Body_filter、Log这六个阶段做相应的处理,其中,每一个插件都可以在一个或多个阶段起到相应的作用。在一个请求到达API网关之后,网关会根据配置为该请求选择插件,然后根据每个插件的规则,进一步过滤出匹配规则的插件,最后对插件进行实例化,对流量进行相应的处理。我们可以通过举例来理解这个过程,如上图所示,localhost:8080/api/demo/test/hello这个请求到达网关后,网关会根据host和端口确定产品信息,并提取出URI(/api/demo/test/hello),然后根据产品的具体配置,筛选出需要使用的插件——Rewrite_URI、Dyups和Auth,接下来根据每个插件的规则配置进行过滤,过滤后,只有Rewrite_URI和Dyups两个插件被选中。之后实例化这两个插件,在各个阶段对请求进行处理。请求被转发到后端服务时,URI就被rewrite为“/demo/test/hello”,upstream也被设置为“prod1-svc1”。请求由后端服务处理之后,响应会经网关返回给客户端,这就是整个插件的设计和工作的流程。为了优化性能,我们将插件的实例化延缓到了请求真正开始处理时,在此之前,网关会通过产品配置和规则,过滤掉不需要执行的插件。从图中也可以看出,每个插件的规则配置都很简单,并且没有统一的格式,这也确保了插件配置的简单灵活。网关的配置均为热更新,通过Consul和Consul-Template来实现,配置在Consul上进行更新后,Consul-Template会将其实时地拉取下来,然后通过以下两种方式进行更新。(1)通过调用Update API,将配置更新到shared-dict中。(2)更新配置文件,利用Reload OpenResty实现配置文件的更新。个推微服务网关提供的主要功能1.动态路由动态路由主要涉及到三个方面:服务注册、服务发现和请求转发。如下图所示,服务的注册和发现是基于Kubernetes的Service和Kube-DNS实现的,在Consul中,会维持一个服务的映射表,应用的每一个微服务都对应Kubernetes上的一个Service,每创建一个Service都会在Consul上的服务映射表中添加一项(会被实时更新到网关的共享内存中)。网关每收到一个请求都会从服务映射表中查询到具体的后端服务(即Kubernetes中的Service名),并进行动态路由。Kube-DNS可以将Service的域名解析成Kubernetes内部的ClusterIP,而Service代理了多个Pod,会将流量均衡地转发到不同的Pod上。2.流量控制流量控制主要是通过一个名为“Counter”的后端服务和网关中的流控插件实现的。Counter负责存储请求的访问次数和限值,并且支持按时间维度进行计数。流控插件负责拦截流量,调用Counter的接口进行超限查询,如果Counter返回请求超限,网关就会直接拒绝访问,实现限次的功能,再结合时间维度就可以实现限频的需求。同时流控插件通过输出日志信息到fluent-bit,由fluent-bit聚合计次来更新Counter中的计数。3.链路追踪整个微服务体系的链路追踪是基于分布式的链路追踪系统Zipkin来实现的。通过在网关安装Zipkin插件和在后端服务中引入Zipkin中间件,实现最终的链路追踪功能。具体架构如下图所示。4. A/B测试在A/B测试的实现中,有以下几个关键点:(1)所有的策略信息都配置在Consul上,并通过Consul-Template实时生效到各个微服务的内存中;(2)每条策略均有指明,调用一个微服务时应调用A还是B(默认为A);(3)网关中实现A/B插件,在请求到达网关时,通过A/B插件配置的规则,即可确定请求适用的A/B策略;(4)网关会将请求适用的A/B策略通过URL参数传递下去;(5)每个微服务通过传递下来的策略,选择正确的服务进行访问。下图给出了两种场景下的调用链路。总结以上就是个推微服务网关的设计和主要功能的实现。之后,个推的技术团队会不断提升API网关的弹性设计,使其能够在故障出现时,缩小故障的影响范围;同时,我们也会继续将网关与DevOps平台做进一步地结合,以确保网关在迭代更新时,能够有更多的自动化测试来保证质量,实现更快速地部署。

March 5, 2019 · 1 min · jiezi

nodejs+express+mongodb+react+layui完整的小说阅读系统--悦读

一、起源本人是一个前端攻城狮,本着对全栈工程师的向往,学习了nodejs搭建web服务器,根据所学知识自己设计制作了一个简易的小说阅读系统——悦读。这套系统包括:后台服务、数据库存储、后台管理端、客户端APP。后台管理端包括:书籍管理(增删改查)、用户管理(新增、冻结、解冻)客户端包括:注册、登录、添加书架、阅读、分享等二、技术栈服务端:nodejs、express数据库:mongodb后台管理:layui、jquery客户端:react三、开发流程声明:以下安装开发流程均为Windows操作系统下。1.安装nodejsnodejs安装超级简单,前往nodejs官网下载对应版本的nodejs安装包下载完成后点击安装,一直点击next,直到安装完成即可。安装完成后,打开命令行工具(win+r,再输入cmd),在命令行执行node -v命令,若输出版本号则说明安装成功,否则安装失败,自行检查失败原因。2.安装MongoDBnodejs的mongodb模块只是用来连接mongodb数据库的,并不是真正的数据库,下载安装地址[MongoDB][3]2.1下载完成后双击安装,可以选择custom自定义安装目录:2.2点击browser选择安装目录2.3点击next进入下一步,然后取消勾选install mongodb compass,否则可能要很长时间都一直在执行安装,MongoDB Compass 是一个图形界面管理工具,我们可以在后面自己到官网下载安装,下载地址:https://www.mongodb.com/downl…。2.4创建数据目录,MongoDB将数据存储在 db 目录下,但是这个数据目录不会主动创建,我们在安装完成后需要创建data/db目录,请注意,数据目录应该放在根目录下((如: C:datadb 或者 D:datadb 等 )。2.5启动数据库,cd到mongodb安装目录的bin目录中cd c:mongodbbin,执行mongod –dbpath c:datadb,其中c:datadb是你创建的数据存储目录。3.安装expresscd到项目目录下在命令行执行npm install express –save 安装express安装包执行npm install body-parser –save 用于处理 JSON, Raw, Text 和 URL 编码的数据执行npm install cookie-parser –save 解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象执行npm install multer –save 用于处理 enctype=“multipart/form-data”(设置表单的MIME编码)的表单数据4.配置路由和http设置新建文件app.js,内容如下:var express = require(“express”);var bodyParser = require(“body-parser”);var app = express();//设置跨域访问app.all(’’, function(req, res, next) { res.header(“Access-Control-Allow-Origin”, “”); // res.header(“access-control-expose-headers”, “Authorization”); res.header(“Access-Control-Allow-Headers”, “Content-Type,XFILENAME,XFILECATEGORY,XFILESIZE”); next();});// json类型的bodyapp.use(bodyParser.json());//string类型bodyapp.use(bodyParser.urlencoded({ extended: false}));// 静态文件目录app.use(express.static(__dirname + ‘/public’));// 图书馆系统后台管理端路由与业务逻辑app.use(’/baseWeb/book/’, require(’./routes/baseweb_book’));// 图书馆系统app客户端路由与业务逻辑app.use(’/webphone/bookPhone/’, require(’./routes/webPhone_book’));app.listen(8080);4.nodejs连接mongodb数据库服务,执行npm install mongodb安装依赖包const MongoClient = require(‘mongodb’).MongoClient;const ObjectID = require(‘mongodb’).ObjectID;const url = “mongodb://localhost:27017/books”;MongoClient.connect(url, { useNewUrlParser: true }, function(err, db) { if (err) throw err; const DBO = db.db(“books”); // 在books数据库user表中添加数据 // 插入一条 let data = {name: ’lilei’, sex:1}; DBO.collection(“user”).insertOne(data, function(err, result) { if (err) throw err; console.log(“添加成功”); }); // 插入多条 let data = [ {name: ’lilei’, sex:1}, {name: ‘hanmeimei’, sex:0} ]; DBO.collection(“user”).insertMany(data, function(err, res) { if (err) throw err; console.log(“插入了” + res.insertedCount + “条数据”); }); // 删除数据 // 删除一条 var whereStr = {name: ’lilei’}; //查询条件 DBO.collection(“user”).deleteOne(whereStr , function(err, result) { if (err) throw err; console.log(“删除成功”); }); //删除多条 var whereStr = {name: ’lilei’}; //查询条件 DBO.collection(“user”).deleteMany(whereStr , function(err, result) { if (err) throw err; console.log(“删除成功”); }); // 修改 // 修改一条 let whereStr = {“name”:‘hanmeimei’}; // 查询条件 let updateStr = {$set: { “name” : “missDeng” }}; DBO.collection(“user”).updateOne(whereStr, updateStr, function(err, res) { if (err) throw err; console.log(“更新成功”); }); // 修改多条 let whereStr = {“name”:‘hanmeimei’}; // 查询条件 let updateStr = {$set: { “name” : “missDeng” }}; DBO.collection(“user”).updateMany(whereStr, updateStr, function(err, res) { if (err) throw err; console.log(“更新成功”); }); // 查询 let obj = {};//查询条件,空对象为查询所有 DBO.collection(“user”).find(obj).toArray(function(err, result){ if (err) throw err; console.log(result); });});5.新建routes目录,在routes目录下创建webPhone_book.js文件,内容如下:const express = require(“express”);const fs = require(‘fs’);const path = require(‘path’);const crypto = require(‘crypto’); //加载加密文件const router = express.Router();const MongoClient = require(‘mongodb’).MongoClient;const ObjectID = require(‘mongodb’).ObjectID;const url = “mongodb://localhost:27017/books”;// 缓存区const buf = new Buffer.alloc(2048);// 连接数据库MongoClient.connect(url, { useNewUrlParser: true }, function(err, db) { if (err) throw err; const DBO = db.db(“books”); // app端注册 router.post(’/enroll’, function(req, res){ let data = { userName: req.body.userName, sex: req.body.sex, userPhone: req.body.userPhone, userEmail: req.body.userEmail, password: req.body.password, status: 1 } for(let k in data){ if(!data[k]){ res.json({code:4, content:“参数异常”}); return false } } DBO.collection(“user”).find({userPhone: data.userPhone}).count(function(err, num){ if(err) throw err; if(num == 0){ // 密码加密 let pwObj = encrypt(data.password); data.password = pwObj.pw; data.key = pwObj.key; DBO.collection(“user”).insertOne(data, function(err, result) { if (err){ res.json({code:4, content: “服务器异常”}); throw err; } res.json({code:1, content: “添加成功”}); }) }else{ res.json({code:2, content: “此手机号码已注册”}) } }); }); // APP端验证登录 router.post(’/login’, function(req, res){ let userPhone = req.body.userPhone; let password = req.body.password; if(userPhone && password){ DBO.collection(“user”).find({userPhone: userPhone}).toArray(function(err, resArr) { if (err) throw err; if (resArr.length > 0) { password = password + resArr[0].key; let md5 = crypto.createHash(‘md5’); let r = md5.update(password).digest(‘hex’); if (r == resArr[0].password) { res.json({code: 1,content: resArr[0]._id}); } else { res.json({code: 2,content: “密码错误”}); } } else { res.json({code: 2,content: “该手机号暂未注册”}); } }) }else{ res.json({code: 4, content: “参数异常”}); } }); }); module.exports = router; 6、使用layui创建后台管理前端页面并绑定接口7、使用react创建APP客户端项目,并使用hbuilder打包成apk安装包四、总结与收获之前并未系统学习过服务端开发,所以在开发过程中遇到很多问题,比如:跨域问题、文件读写、上传文件、下载文件、数据库设计等。这些问题并没有让我感到挫败,反而越战越勇,想尽办法一一解决,完成之后部署在云服务器,推荐给朋友使用,成就感爆棚。当然这个系统还是一个新生儿,还有很多不足和需要优化的地方,希望各位朋友不吝赐教。完整项目github地址:https://github.com/jaxlix/-安卓安装包下载二维码: ...

March 4, 2019 · 3 min · jiezi

github 授权登录教程与如何设计第三方授权登录的用户表

需求:在网站上想评论一篇文章,而评论文章是要用户注册与登录的,那么怎么免去这麻烦的步骤呢?答案是通过第三方授权登录。本文讲解的就是 github 授权登录的教程。效果体验地址: http://biaochenxuying.cn1. github 第三方授权登录教程先来看下 github 授权的完整流程图 1:或者看下 github 授权的完整流程图 2:1.1 申请一个 OAuth App首先我们必须登录上 github 申请一个 OAuth App,步骤如下:登录 github点击头像下的 Settings -> Developer settings 右侧 New OAuth App填写申请 app 的相关配置,重点配置项有2个Homepage URL 这是后续需要使用授权的 URL ,你可以理解为就是你的项目根目录地址Authorization callback URL 授权成功后的回调地址,这个至关重要,这是拿到授权 code 时给你的回调地址。具体实践如下:首先登录你的 GitHub 账号,然后点击进入Settings。点击 OAuth Apps , Register a new application 或者 New OAuth App 。输入信息。应用信息说明。流程也可看 GitHub 设置的官方文档-Registering OAuth Apps。1.2 授权登录github 文档:building-oauth-apps/authorizing-oauth-apps授权登录的主要 3 个步骤:web 端重定向 http://github.com/login/oauth…根据 code 获取 access_token根据 access_token 获取用户信息笔者这次实践中,项目是采用前后端分离的,所以第 1 步在前端实现,而第 2 步和第 3 步是在后端实现的,因为第 2 个接口里面需要Client_secret 这个参数,而且第 3 步获取的用户信息在后端保存到数据库。1.3. 代码实现1.3.1 前端笔者项目的技术是 react。// config.js// ***** 处请填写你申请的 OAuth App 的真实内容 const config = { ‘oauth_uri’: ‘https://github.com/login/oauth/authorize', ‘redirect_uri’: ‘http://biaochenxuying.cn/', ‘client_id’: ‘’, ‘client_secret’: ‘*’,};// 本地开发环境下if (process.env.NODE_ENV === ‘development’) { config.redirect_uri = “http://localhost:3001/” config.client_id = “” config.client_secret = “"}export default config; 代码参考 config.js redirect_uri 回调地址是分环境的,所以我是新建了两个 OAuth App 的,一个用于线上生产环境,一个用于本地开发环境。一般来说,登录的页面应该是独立的,对应相应的路由 /login , 但是本项目的登录 login 组件是 nav 组件的子组件,nav 是个全局用的组件, 所以回调地址就写了 http://biaochenxuying.cn/。所以点击跳转是写在 login.js 里面;授权完拿到 code 后,是写在 nav.js 里面nav.js 拿到 code 值后去请求后端接口,后端接口返回用户信息。其中后端拿到 code 还要去 github 取 access_token ,再根据 access_token 去取 github 取用户的信息。// login.js// html<Button style={{ width: ‘100%’ }} onClick={this.handleOAuth} > github 授权登录</Button>// jshandleOAuth(){ // 保存授权前的页面链接 window.localStorage.preventHref = window.location.href // window.location.href = ‘https://github.com/login/oauth/authorize?client_id=&redirect_uri=http://biaochenxuying.cn/’ window.location.href = ${config.oauth_uri}?client_id=${config.client_id}&amp;redirect_uri=${config.redirect_uri}}代码参考 login.js // nav.jscomponentDidMount() { // console.log(‘code :’, getQueryStringByName(‘code’)); const code = getQueryStringByName(‘code’) if (code) { this.setState( { code }, () => { if (!this.state.code) { return; } this.getUser(this.state.code); }, ); } }componentWillReceiveProps(nextProps) { const code = getQueryStringByName(‘code’) if (code) { this.setState( { code }, () => { if (!this.state.code) { return; } this.getUser(this.state.code); }, ); } } getUser(code) { https .post( urls.getUser, { code, }, { withCredentials: true }, ) .then(res => { // console.log(‘res :’, res.data); if (res.status === 200 && res.data.code === 0) { this.props.loginSuccess(res.data); let userInfo = { _id: res.data.data._id, name: res.data.data.name, }; window.sessionStorage.userInfo = JSON.stringify(userInfo); message.success(res.data.message, 1); this.handleLoginCancel(); // 跳转到之前授权前的页面 const href = window.localStorage.preventHref if(href){ window.location.href = href } } else { this.props.loginFailure(res.data.message); message.error(res.data.message, 1); } }) .catch(err => { console.log(err); }); }参考 nav.js 1.3.2 后端笔者项目的后端采用的技术是 node.js 和 express。后端拿到前端传来的 code 后,还要去 github 取 access_token ,再根据 access_token 去取 github 取用户的信息。然后把要用到的用户信息通过 注册 的方式保存到数据库,然后返回用户信息给前端。// app.config.jsexports.GITHUB = { oauth_uri: ‘https://github.com/login/oauth/authorize', access_token_url: ‘https://github.com/login/oauth/access_token', // 获取 github 用户信息 url // eg: https://api.github.com/user?access_token=&scope=&token_type=bearer user_url: ‘https://api.github.com/user', // 生产环境 redirect_uri: ‘http://biaochenxuying.cn/', client_id: ‘’, client_secret: ‘’, // // 开发环境 // redirect_uri: “http://localhost:3001/”, // client_id: “”, // client_secret: “***”,};代码参考 app.config.js // 路由文件 user.jsconst fetch = require(’node-fetch’);const CONFIG = require(’../app.config.js’);const User = require(’../models/user’);// 第三方授权登录的用户信息exports.getUser = (req, res) => { let { code } = req.body; if (!code) { responseClient(res, 400, 2, ‘code 缺失’); return; } let path = CONFIG.GITHUB.access_token_url; const params = { client_id: CONFIG.GITHUB.client_id, client_secret: CONFIG.GITHUB.client_secret, code: code, }; // console.log(code); fetch(path, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’, }, body: JSON.stringify(params), }) .then(res1 => { return res1.text(); }) .then(body => { const args = body.split(’&’); let arg = args[0].split(’=’); const access_token = arg[1]; // console.log(“body:",body); console.log(‘access_token:’, access_token); return access_token; }) .then(async token => { const url = CONFIG.GITHUB.user_url + ‘?access_token=’ + token; console.log(‘url:’, url); await fetch(url) .then(res2 => { console.log(‘res2 :’, res2); return res2.json(); }) .then(response => { console.log(‘response ‘, response); if (response.id) { //验证用户是否已经在数据库中 User.findOne({ github_id: response.id }) .then(userInfo => { // console.log(‘userInfo :’, userInfo); if (userInfo) { //登录成功后设置session req.session.userInfo = userInfo; responseClient(res, 200, 0, ‘授权登录成功’, userInfo); } else { let obj = { github_id: response.id, email: response.email, password: response.login, type: 2, avatar: response.avatar_url, name: response.login, location: response.location, }; //注册到数据库 let user = new User(obj); user.save().then(data => { // console.log(‘data :’, data); req.session.userInfo = data; responseClient(res, 200, 0, ‘授权登录成功’, data); }); } }) .catch(err => { responseClient(res); return; }); } else { responseClient(res, 400, 1, ‘授权登录失败’, response); } }); }) .catch(e => { console.log(’e:’, e); });};代码参考 user.js 至于拿到 github 的用户信息后,是注册到 user 表,还是保存到另外一张 oauth 映射表,这个得看自己项目的情况。从 github 拿到的用户信息如下图:最终效果:参与文章:https://www.jianshu.com/p/a9c…https://blog.csdn.net/zhuming...2. 如何设计第三方授权登录的用户表第三方授权登录的时候,第三方的用户信息是存数据库原有的 user 表还是新建一张表呢 ?答案:这得看具体项目了,做法多种,请看下文。第三方授权登录之后,第三方用户信息一般都会返回用户唯一的标志 openid 或者 unionid 或者 id,具体是什么得看第三方,比如 github 的是 id1. 直接通过 注册 的方式保存到数据库第一种:如果网站 没有 注册功能的,直接通过第三方授权登录,授权成功之后,可以直接把第三的用户信息 注册 保存到自己数据库的 user 表里面。典型的例子就是 微信公众号的授权登录。第二种:如果网站 有 注册功能的,也可以通过第三方授权登录,授权成功之后,也可以直接把第三的用户信息 注册 保存到自己数据库的 user 表里面(但是密码是后端自动生成的,用户也不知道,只能用第三方授权登录),这样子的第三方的用户和原生注册的用户信息都在同一张表了,这种情况得看自己项目的具体情况。笔者的博客网站暂时就采用了这种方式。2. 增加映射表现实中很多网站都有多种账户登录方式,比如可以用网站的注册 id 登录,还可以用手机号登录,可以用 QQ 登录等等。数据库中都是有映射关系,QQ、手机号等都是映射在网站的注册 id 上。保证不管用什么方式登录,只要去查映射关系,发现是映射在网站注册的哪个 id 上,就让哪个 id 登录成功。3. 建立一个 oauth 表,一个 id 列,记录对应的用户注册表的 id建立一个 oauth 表,一个 id 列,记录对应的用户注册表的 id,然后你有多少个第三方登陆功能,你就建立多少列,记录第三方登陆接口返回的 openid;第三方登陆的时候,通过这个表的记录的 openid 获取 id 信息,如果存在通过 id 读取注册表然后用 session 记录相关信息。不存在就转向用户登陆/注册界面要用户输入本站注册的账户进行 openid 绑定或者新注册账户信息进行绑定。具体代码实践请参考文章:1. 第三方登录用户信息表设计2. 浅谈数据库用户表结构设计,第三方登录4. 最后笔者的 github 博客地址:https://github.com/biaochenxuying/blog如果您觉得这篇文章不错或者对你有所帮助,请给个赞或者星呗,你的点赞就是我继续创作的最大动力。微信公众号:BiaoChenXuYing分享 前端、后端开发等相关的技术文章,热点资源,随想随感,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源获取–Python、Java、Linux、Go、node、vue、react、javaScript ...

March 4, 2019 · 4 min · jiezi

深入理解express框架的匹配路由

现在node的web框架有很多,除express 还有koa egg等等。 但它们本质上还是基于原生node框架的http。其实框架都大差不差,主要是观摩和学习。本篇文章主要记录下自己在node爬坑之路上的经历和收获~本文主要实现express的功能之一, 匹配路由匹配简单静态路由匹配动态路由首先我们看一下express:const express = require(’express’);let app = new express();app.get(’/’,(req,res)=>{ res.end(‘home Page.’);});app.get(’/center’,(req,res)=>{ res.end(‘center Page.’);});/** 匹配到动态路由 获取路由参数并返回 / app.get(’/product/:id/:name’,(req,res)=>{ res.end(JSON.stringify(req.params)); });/* 当以上路径都没有匹配成功时 返回404 /app.all(’’,(req,res)=>{ res.end(‘404’);});let port = 3000;app.listen(port,()=>{ console.log(Server is start on port ${port});});ok.代码很简单。引入express,new了个express实例,写了几个路由,最后启了本地服务。代码第二行 我们把引入的express 给new出来,说明express内部返回的是一个function。好.麻雀虽小 五脏俱全,我们今天就来实现express的这些功能。let http = require(‘http’); /** express基于http /let url = require(‘url’); /* 用来解析请求的路径 //* express引入了methods 它的作用是返回各种的请求方法 /let methods = require(‘methods’);function application(){ /* 1 express返回了一个函数 * 这个函数就是http.createServer的监听函数 / let app = (req,res) => { /* 1.1 url模块解析 拿到请求路径 比如 /user / let { pathname } = url.parse(req.url); /* 1.2 拿到请求方法 方法是大写 记得转换为小写 / let requestMethod = req.method.toLowerCase(); /* 1.3 通过拿到的路径和方法 在之前定义好的路由数组routes中 循环去匹配 / for(let i = 0; i < app.routes.length; i++){ /* 1.4 解构 拿到每一个路由的 路径 方法 回调 / let { path, method, cb } = app.routes[i]; if((pathname===path||path===’’) && (requestMethod===method)||method===‘all’){ /** 1.5 如果匹配到 返回回调并执行 / return cb(req,res); } } /* 1.6 没有匹配到任何路由 / res.end(Cannot found ${pathname}/${requestMethod}); } /* 2 定义一个存放所有路由的数组 / app.routes = []; /* 2.1 往methods数组中添加一个方法 all 并循环数组 / […methods,‘all’].forEach((method)=>{ app[method] = function(path,cb){ /* 2.2 先将每个请求的路由地址 方法和回调保存起来 * path:路径 method:方法 cb:回调 / let layer = { path, method, cb }; app.routes.push(layer); } }); /* 3 监听端口 / app.listen = function(…arguments){ /* 3.1 利用http的createServer方法 将app传进去 / let server = http.createServer(app); server.listen(…arguments); } return app;}/* 4 将方法导出出去 /module.exports = application;代码上面都仔细的标注了观看序号,1.2.3… 按照顺序观看即可。我们手写的整个express就是一个函数 函数里面return了一个函数。通过node原生框架http的方法 包装了该函数,最后再将整个函数module.exports导出出去。最后我们启动项目,通过浏览器或者postman调用接口,发现确实能实现部分的express功能,但是有一点,此时我们能实现的仅仅是静态的路由,如果有路由参数的情况下,比如/product/:id/:name。结果就不符合预期。 改造:代码上面都仔细的标注了观看序号,1.2.3… 按照顺序观看即可。let http = require(‘http’);let url = require(‘url’);let methods = require(‘methods’);function application(){ let app = (req,res) => { let { pathname } = url.parse(req.url); let requestMethod = req.method.toLowerCase(); for(let i = 0; i < app.routes.length; i++){ let { path, method, cb } = app.routes[i]; /* 7 如果请求路径path中 就说明该路由是动态的 / if(path.params){ /* 8 匹配该动态路由后面的动态参数 匹配成功返回true / if(path.test(pathname)){ /* 9 解构赋值 拿到动态路由的参数 / let [, …otherParams] = pathname.match(path); /* 10 通过reduce()方法 将路由参数转换为对象形式 * 并放到req.params中 / req.params = path.params.reduce( (memo,key,index)=>( memo[key]=otherParams[index],memo ),{} ); /* 11 返回匹配到的动态路由 / return cb(req,res); } } if((pathname===path||path===’’) && (requestMethod===method)||method===‘all’){ return cb(req,res); } } res.end(Cannot found ${pathname}/${requestMethod}); } app.routes = []; […methods,‘all’].forEach((method)=>{ app[method] = function(path,cb){ let layer = { path, method, cb }; /** 1 定义一个空数组 来存放动态路由的参数 / let params = []; /* 2 如果路径中包含: 说明该路由是动态路由 / if(path.includes(’:’)){ /* 3 更改该动态路由的路径path为一个正则表达式 * 目的是为了等真正请求到来时 匹配到该动态路由 并拿到路由参数 / layer.path = new RegExp(path.replace(/:([^/])/g,function(){ /** 4 将动态路由参数的key 放入params数组中 / params.push(arguments[1]); /* 5 返回了一个正则来匹配真正的动态路由参数 注意此处没有: / return ‘([^/])’; })); /** 6 把解析到的动态路由放到该路由路径path的params上 */ layer.path.params = params; } app.routes.push(layer); } }); app.listen = function(…arguments){ let server = http.createServer(app); server.listen(…arguments); } return app;}module.exports = application;先通过正则匹配到该动态路由,并把该动态路由的path替换为一个正则,放到数组中,等待真正的动态路由到来时,从路由数组中拿到该动态路由的路径,也就是刚才替换的正则,来匹配该动态路由后的参数即可。通过以上就能实现获取动态路由的参数 上图:代码在git mock-express ...

February 28, 2019 · 2 min · jiezi

近期前端发展计划

February 24, 2019 · 0 min · jiezi

vueSSR: 从0到1构建vueSSR项目 --- node以及vue-cli3的配置

前言上一次做了路由的相关配置,原本计划今天要做vuex部分,但是想了想,发现vuex单独的客户端部分穿插解释起来很麻烦,所以今天改做服务端部分。服务端部分做完,再去做vuex的部分,这样就会很清晰。vue ssr是分两个端,一个是客户端,一个是服务端。所以要做两个cli3的配置。那么下面就直接开始做吧。修改package.json的命令//package.json :client代表客户端 :server代表服务端//使用VUE_NODE来作为运行环境是node的标识//cli3内置 命令 –no-clean 不会清除dist文件 “scripts”: { “serve:client”: " vue-cli-service serve", “build”:“npm run build:server – –silent && npm run build:client – –no-clean –silent”, “build:client”: “vue-cli-service build”, “build:server”: “cross-env VUE_NODE=node vue-cli-service build”, “start:server”: “cross-env NODE_ENV=production nodemon nodeScript/index” }修改vue.config.js配置添加完相关脚本命令之后,我们开始改造cli3配置。首先要require(‘vue-server-renderer’)然后再根据VUE_NODE环境变量来决定编译的走向以及生成不同的环境清单先做cli3服务端的入口文件// src/entry/server.jsimport { createApp} from ‘../main.js’export default context => { return new Promise((resolve, reject) => { const { app, router } = createApp(context.data) //根据node传过来的路由 来调用router路由的指向 router.push(context.url) router.onReady(() => { //获取当前路由匹配的组件数组。 const matchedComponents = router.getMatchedComponents() //长度为0就是没找到该路由所匹配的组件 //可以路由设置重定向或者传回node node来操作也可以 if (!matchedComponents.length) { return reject({ code: 404 }) } resolve(app) }, reject) })}这里是cli3的配置//vue.config.jsconst ServerPlugin = require(‘vue-server-renderer/server-plugin’),//生成服务端清单 ClientPlugin = require(‘vue-server-renderer/client-plugin’),//生成客户端清单 nodeExternals = require(‘webpack-node-externals’),//忽略node_modules文件夹中的所有模块 VUE_NODE = process.env.VUE_NODE === ’node’, entry = VUE_NODE ? ‘server’ : ‘client’;//根据环境变量来指向入口module.exports = { css: { extract: false//关闭提取css,不关闭 node渲染会报错 }, configureWebpack: () => ({ entry: ./src/entry/${entry}, output: { filename: ‘js/[name].js’, chunkFilename: ‘js/[name].js’, libraryTarget: VUE_NODE ? ‘commonjs2’ : undefined }, target: VUE_NODE ? ’node’ : ‘web’, externals: VUE_NODE ? nodeExternals({ //设置白名单 whitelist: /.css$/ }) : undefined, plugins: [//根据环境来生成不同的清单。 VUE_NODE ? new ServerPlugin() : new ClientPlugin() ] }), chainWebpack: config => { config.resolve .alias .set(‘vue$’, ‘vue/dist/vue.esm.js’) config.module .rule(‘vue’) .use(‘vue-loader’) .tap(options => { options.optimizeSSR = false; return options; }); config.module .rule(‘images’) .use(‘url-loader’) .tap(options => { options = { limit: 1024, fallback:‘file-loader?name=img/[path][name].[ext]’ } return options; }); }}node相关配置用于node渲染 必然要拦截get请求的。然后根据get请求地址来进行要渲染的页面。官方提供了vue-server-renderer插件大概的方式就是 node拦截所有的get请求,然后将获取到的路由地址,传给前台,然后使用router实例进行push再往下面看之前 先看一下官方文档创建BundleRenderercreateBundleRenderer将 Vue 实例渲染为字符串。renderToString渲染应用程序的模板template生成所需要的客户端或服务端清单clientManifest先创建 服务端所需要的模板//public/index.nodeTempalte.html<!DOCTYPE html><html> <head> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <meta name=“viewport” content=“width=device-width,initial-scale=1.0”> <link rel=“icon” href="/favicon.ico"> <meta charset=“utf-8”> <title>vuessr</title> </head> <body> <!–vue-ssr-outlet–> </body></html>node部分先创建三个文件index.js //入口proxy.js //代理server.js //主要配置//server.jsconst fs = require(‘fs’);const { resolve } = require(‘path’);const express = require(’express’);const app = express();const proxy = require(’./proxy’);const { createBundleRenderer } = require(‘vue-server-renderer’)//模板地址const templatePath = resolve(__dirname, ‘../public/index.nodeTempalte.html’)//客户端渲染清单const clientManifest = require(’../dist/vue-ssr-client-manifest.json’)//服务端渲染清单const bundle = require(’../dist/vue-ssr-server-bundle.json’)//读取模板const template = fs.readFileSync(templatePath, ‘utf-8’)const renderer = createBundleRenderer(bundle,{ template, clientManifest, runInNewContext: false})//代理相关proxy(app);//请求静态资源相关配置app.use(’/js’, express.static(resolve(__dirname, ‘../dist/js’)))app.use(’/css’, express.static(resolve(__dirname, ‘../dist/css’)))app.use(’/font’, express.static(resolve(__dirname, ‘../dist/font’)))app.use(’/img’, express.static(resolve(__dirname, ‘../dist/img’)))app.use(’.ico’, express.static(resolve(__dirname, ‘../dist’)))//路由请求app.get(’’, (req, res) => { res.setHeader(“Content-Type”, “text/html”) //传入路由 src/entry/server.js会接收到 使用vueRouter实例进行push const context = { url: req.url } renderer.renderToString(context, (err, html) => { if (err) { if (err.url) { res.redirect(err.url) } else { res.status(500).end(‘500 | 服务器错误’); console.error(${req.url}: 渲染错误 ); console.error(err.stack) } } res.status(context.HTTPStatus || 200) res.send(html) })})module.exports = app;//proxy.jsconst proxy = require(‘http-proxy-middleware’);function proxyConfig(obj){ return { target:’localhost:8081’, changeOrigin:true, …obj }}module.exports = (app) => { //代理开发环境 if (process.env.NODE_ENV !== ‘production’) { app.use(’/js/main*’, proxy(proxyConfig())); app.use(’/hot-update’,proxy(proxyConfig())); app.use(’/sockjs-node’,proxy(proxyConfig({ws:true}))); }}//index.jsconst app = require(’./server’)app.listen(8080, () => { console.log(’\033[42;37m DONE \033[40;33m localhost:8080 服务已启动\033[0m’)})做完这一步之后,就可以预览基本的服务渲染了。后面就只差开发环境的配置,以及到node数据的传递(vuex) npm run build npm run start:server 打开localhost:8080 F12 - Network - Doc 就可以看到内容最终目录结构|– vuessr |– .gitignore |– babel.config.js |– package-lock.json |– package.json |– README.md |– vue.config.js |– nodeScript //node 渲染配置 | |– index.js | |– proxy.js | |– server.js |– public//模板文件 | |– favicon.ico | |– index.html | |– index.nodeTempalte.html |– src |– App.vue |– main.js |– router.config.js//路由集合 |– store.config.js//vuex 集合 |– assets//全局静态资源源码 | |– 备注.txt | |– img | |– logo.png |– components//全局组件 | |– Head | |– index.js | |– index.scss | |– index.vue | |– img | |– logo.png |– entry//cli3入口 | |– client.js | |– server.js | |– 备注.txt |– methods//公共方法 | |– 备注.txt | |– mixin | |– index.js |– pages//源码目录 | |– home | | |– index.js | | |– index.scss | | |– index.vue | | |– img | | | |– flow.png | | | |– head_portrait.jpg | | | |– logo.png | | | |– vuessr.png | | |– vue | | | |– index.js | | | |– index.scss | | | |– index.vue | | |– vueCli3 | | | |– index.js | | | |– index.scss | | | |– index.vue | | |– vueSSR | | | |– index.js | | | |– index.scss | | | |– index.vue | | |– vuex | | |– index.js | | |– index.scss | | |– index.vue | |– router//路由配置 | | |– index.js | |– store//vuex配置 | |– all.js | |– gather.js | |– index.js |– static//cdn资源 |– 备注.txtgithub欢迎watch ...

January 28, 2019 · 3 min · jiezi

vueSSR: 从0到1构建vueSSR项目 --- 初始化

开始初始化 npm install -g @vue/cli nodemon nodemon 检测目录文件更改时,来重启基于node开发的程序 vue create vuessr 我选的附带 babel,eslint cd vuessr 创建文件以及文件夹 type null > vue.config.js //node相关配置文件 mkdir nodeScript cd nodeScript type null > index.js type null > proxy.js type null > server.js 进入src目录 //目录初始化 cd ../src type null > router.config.js //路由配置 mkdir pages //项目展示页面主要目录 cd pages mkdir router mkdir entry //vue-cli3 entry的相关配置入口 vueSSR需要。 mkdir static/js //gulp提取框架,插件等几年不动的源码整合后存放于cdn服务器 mkdir static/css //gulp提取整合初始化的样式表,存放的位置 mkdie methods //vue等全局代码的存放比如拦截器 use mixin 兼容函数 //安装依赖 npm install –save-dev sass-loader npm-run-all npm运行多个命令 -s 是顺序 -p是并行 cross-env 可以修改node环境变量 webpack-node-externals 忽略node_modules文件夹中的所有模块 vue-server-renderer 不解释修改eslint配置package.json eslintConfig rules 这个对象下面添加,cli的eslint附带以下的配置 所以手动关闭下。 “no-console”: 0, “no-unused-vars”: 0, “no-undef”: 0如果你觉得eslint警告很烦,那么可以 vue.config.js module.exports = { …, lintOnSave:false, … }明天开始配置 vue-router vuex entry 相关github欢迎watch ...

January 24, 2019 · 1 min · jiezi

NodeJs+Express+Mysql + Angular(Vue/React) 项目实战 - 大纲

NodeJs+Express+Mysql + Angular(Vue/React) 项目实战最近准备写一系列文章,全面讲述如何基于NodeJs + Express + Mysql + Angular(Vue/React)从零开发前后端完全分离项目;文笔及技术可能在某些方面欠佳,请您指正,共同学习进步前端:AngularJs(后面再使用Vue、React重构)后端:NodeJS + Express (提供Api服务,无Session)数据端:Mysql (我发现目前网络上关于 Nodejs和Mysql搭配的教程少之又少,感觉写出来对大伙应该很有帮助,如果您喜欢用MongoDB:请移步https://cnodejs.org/topic/581…)项目场景:简单博客系统(博客发布、列表、分类等)开发环境:Mac + Sublime Text + iTerm+ Chrome + Navicat + PostMan正式环境:CentOS + SecureCRT大纲:1、开发环境搭建1.1 环境搭建-基础配置-NodeJs+Express+Mysql实战1.2 Node.js 安装与使用-基础配置-NodeJs+Express+Mysql实战1.3 Mysql 安装与使用-基础配置-NodeJs+Express+Mysql实战2、Node.js 知识点讲解 by nswbmwNode.js 新手入门:http://cnodejs.org/getstartrequireexports 和 module.exportsPromise环境变量packge.jsonsemvernpm 使用注意事项npm initnpm installnpm scriptsnpm shrinkwrap3、Express by nswbmwExpress新手入门: http://www.expressjs.com.cn/初始化一个 Express 项目supervisor路由express.Router模板引擎ejsincludesExpress 浅析中间件与 next错误处理4、博客后端Api4.1 开发环境-博客后端Api-NodeJs+Express+Mysql实战4.2 目录结构-博客后端Api-NodeJs+Express+Mysql实战4.3 配置文件-博客后端Api-NodeJs+Express+Mysql实战4.4 功能梳理-博客后端Api-NodeJs+Express+Mysql实战4.5 数据库表-博客后端Api-NodeJs+Express+Mysql实战4.6 路由设计/ResetFul Api4.7 权限控制4.8 Sequelize Mysql4.9 Co yield promise4.10 Token jwt4.11 邮箱NodeMailer4.12 文件上传multer4.13 国际化i18n4.14 定时任务schedule4.15 跨域cors5、博客前端开发5.1 开发环境5.2 目录结构5.3 了解Angular1.x5.4 Bootstrap Scss5.5 字体图标iconfont5.5 基础配置5.5 路由设计5.5 缓存机制5.6 AOP切面拦截(token机制、全局request/response Error、loading)5.7 表单验证(指令)5.8 文件上传5.9 列表循环(过滤器)5.10 构建产出6、部署6.1 服务器购买6.2 基础环境配置6.3 node安装6.4 pm26.5 nginx技多不压身。多一个技能多一条出路,祝你在自学道路上越走越好,掌握自己的核心技能,不只是优秀,还要成为不可替代的人! ...

January 24, 2019 · 1 min · jiezi

express+request实现-图夫在线爬取网页图片

先奉上图夫地址:https://tufu.xkboke.comGIT开源地址:git地址(欢迎star)懒惰驱动Idea有时候在站酷或者UI中国看到很好的图片和作品都会想收藏下来学习一下,但是每次右击另存为都很麻烦,而且有的还要放大后才有原图可以下载;作为一个伪全栈怎么能忍呢,然后就想着扒扒他们网站的源码看,这一看发现图片原图存放的位置都有着规律,这就很高兴啦,哈哈!雏形诞生浪起来!!很快完成了第一个小脚本,顺利把需要的图片下载了下来,但只是最简单的爬取图片,后来优化了一下,把每次下载的图片都放到不同文件夹。但是转头一想,独乐乐不如众乐乐,就想着干脆把这个脚本工具化,做一个可以兼容多个网站的爬虫工具,并且可以批量下载原图,想法很快被付诸实践,经过不断的改版后,终于我的图夫出来了第一版。迭代优化一开始只支持站酷和UI中国,而且对其他的网站都兼容的不是很好,没关系,先做出第一版,接着慢慢迭代,后来根据反馈又添加了涂鸦王国,设计癖,视觉ME等网站,最近几天在逛贴吧,发现贴吧的一些图片也是很漂亮,有很多可以当壁纸的图片,但是要下到原图需要三四次步骤,而且一次只能下载一个,所以呢,我又把它加入我的图夫工具中了,哈哈!我的图夫又慢慢壮大起来了!先放一张图展示一下我的图夫技术栈其实原理大家都知道的,就是爬虫,只是我把爬虫可视化做成了一个工具,方便日常使用而已,这里主要使用的是express,库的话用的是request,compressing用来压缩文件夹,node-uuid用来生成随机hash;放一张目录结构部分代码index.js 主要请求文件,其他文件就移步git查看吧const path = require(‘path’);const fs = require(‘fs’);const analyze = require(’./analyze’);const tarTool = require(’./tarTool’)const uuid =require(’node-uuid’)/** * 根据hash值创建文件夹 * @param path /function write(path) { fs.exists(path, function (exists) { //path为文件夹路径 if (!exists) { fs.mkdir(path, function (err) { if (err) { console.log(‘创建失败’); return false; } else { console.log(‘创建成功’); } }) } })}/* * 请求图片地址 * @param response * @param req * @param next /function start(req,response,next) { const hash = uuid.v1().replace(/-/g, “”) const imgDir = path.join(path.resolve(__dirname, ‘..’), ‘output/img/’+hash); write(imgDir) // 发起请求获取 DOM console.log(‘请求地址’,req.url); request(req.url, function(err, res, body) { if (!err && res) { console.log(‘start’); // 将 downLoad 函数作为参数传递给 analyze 模块的 findImg 方法 analyze.findImg(body,req.type,imgDir,downLoad,req.url); response.json({head: {code: 0, msg: ‘ok’}, data: hash}) }else { response.json({head: {code: 1000, msg: err}, data: ‘’}) } });}/* * 获取到 findImg 函数返回的图片地址后,利用 request 再次发起请求,将数据写入本地。 * * @param {} imgUrl * @param {} i * @param {} imgDir /function downLoad(imgUrl, i,imgDir) { console.log(‘图片地址’,imgUrl); let ext = imgUrl.split(’.’).pop(); // 再次发起请求,写文件 request(imgUrl).pipe(fs.createWriteStream(path.join(imgDir, i + ‘.’ + ext), { ’encoding’: ‘utf8’, }));}/* * 下载图片到本地后,利用tar压缩成一个压缩包,并返回路径 * @param {} req * @param {} response * @param {} next */function tarFile(req,response,next) { console.log(‘接收’,req); tarTool.tarTool(req.path,response)}module.exports= { getImg:start, tarTool:tarFile}使用方法当然既然是工具,就必须非常简单啦,你只需复制你要下载页面的URL链接,然后粘贴到我的输入框中就可以,然后选择网站类型(当然悄悄告诉你,不选也没关系,我做了一个校验),然后就是点击搜索了,接下来就是耐心的等待…loading….(因为服务器的带宽只有1M,所以会下载有点慢,如果你愿意打赏一下,我也是不介意的,哈哈),执行完毕后,会出现下载按钮,你只需要点击下载即可下载打包完毕的文件了。已支持网站站酷UI中国涂鸦王国设计癖视觉ME百度贴吧…(等待你的意见)声明本工具仅作为技术交流工具,不得用于任何商业用途或获利。本网站不存储任何图片,所有内容均通过爬虫工具爬取网页上存在的内容。通过本网站下载的任何图片不代表你拥有商用的权利或者授权,如需授权或商用请联系原网站作者或平台,谢谢理解!最后,奉上我的开源GIT地址:https://github.com/gengchen52…图夫网站地址:https://tufu.xkboke.com如果喜欢的话,请给个star,如果有什么想法的话可以提issues,也可以微信联系我,欢迎交流,也可在评论中留下你想采集的网站链接,我会不定期更新图夫完全支持的网站往期文章mpvue小程序《校友足迹》成长记(一)使用node脚本全自动删除豆瓣评论与帖子(这个最近正在更新,也会上线可视化操作,敬请关注)[基于mongodb+express+vue+axios+bootstrap的掘金最热文章收藏评论分析](https://juejin.im/post/5a1293…个人博客:www.xkboke.com ...

January 22, 2019 · 1 min · jiezi

nodemon+cross-env+config实现支持热更新的能根据不同环境加载不同配置的nodejs环境

nodejs项目中我们经常会用到nodemon启动项目以使我们的项目在开发时支持热更新,修改了代码后不需要手动重启服务器;使用npm 的config模块实现不同的环境(一般是develop,production,test);nodemon和config的使用方法这里不做详细介绍。cross-env的作用是不需要全局配置NODE_ENV在scripts脚本中修改NODE_ENV的值从而实现不同环境中proccess.env.NODE_ENV的不同,而config的工作原理就是基于NODE_ENV这个值的,所以推荐两者结合使用。先上三个工具结合使用后的配置文件。/package.json"scripts": { “dev”: “nodemon ./bin/www –exec babel-node –presets es2015,stage-2”, “start”: “cross-env NODE_ENV=production babel-node ./bin/www –presets es2015,stage-2” }, “dependencies”: { // … other dependencies “config”: “^3.0.1”, “cross-env”: “^5.2.0”, // … other dependencies }, “devDependencies”: { // … other devDependencies “nodemon”/nodemon.json{ “restartable”: “rs”, “ignore”: [ “.git”, “f2e”, “node_modules/**/node_modules” ], “verbose”: true, “execMap”: { “js”: “node –harmony” }, “events”: { “restart”: “osascript -e ‘display notification "App restarted due to:\n’$FILENAME’" with title "nodemon"’” }, “env”: { “NODE_ENV”: “develop” }, “ext”: “js,json”}nodemon的配置文档介绍的可以在scripts中一一配置,也可以在上面的配置文件中配置,我们建议在配置文件中配置,清晰明了还好管理。nodemon.json中跟本文相关的配置就是env->NODE_ENV配置项,他的值就对应设置了node环境中proccess.env.NODE_ENV的值,当执行npm run dev 时,proccess.env.NODE_ENV对应的是nodemon的配置文件中的值当执行npm run start 时, proccess.env.NODE_ENV对应的是cross-env设置的参数的值 ...

January 18, 2019 · 1 min · jiezi

记一次翻译站经历

做这个记录之前,刚完成使用drone作为公司前端项目的持续交付工具的实践,打算写的教程前先把官方文档扒下来做个翻译站。在实践一番后,卡在不能频密调取google翻译这块上,项目无法进行下去。最后觉得经历的过程涉及的内容挺多的所以记录一下同时分享给大家。这次经历涉及以下知识:wget抓取网站使用基于python的翻译工具使用nodejs调取命令行使用nodejs读写文件为express添加jwt基于express实现上传文件在nodejs环境下读取并编辑html文件我们一点点来说。wget抓取网站最初是寻找有什么带可视化的工具来达到目的的,后来在寻找的过程中看到原来wget能实现整站抓取,所以怎样简单怎样来!# 抓取整站wget -r -p -np -k -E http://www.xxx.com# w抓取第一级wget -l 1 -p -np -k http://www.xxx.com-r 递归抓取-k 抓取后修正链接,适合本地浏览-e robots=off 忽略robots协议,强制抓取(流氓抓取)-E 将text/html类型的文档保存为.html的文件使用基于python的翻译工具这个在github上找了几个工具,同时也考虑过使用官方提供的API(微软和google均有提供),最后得出使用soimort/translate-shell(并不想花钱和花时间再看文档上了>w<)这个trans shell工具提供几个翻译源(google, bing, yandex, apertium),不知道为何能用的只有google (!゚゚)。google也很有保证了,问题不大。安装并不复杂,只需要安装gawk,其他在ubuntu系统下默认都有包含的:GNU Awkgawk安装$ sudo apt-get install gawk尝试:$ gawk -f <(curl -Ls git.io/translate) – -shell安装trans本体,官方文档提供三种方式,方式1不知道为何有bug,方式2并不太熟悉,最后选择方式3:$ git clone https://github.com/soimort/translate-shell$ cd translate-shell/$ make$ [sudo] make install使用起来也是简单:$ trans ‘Saluton, Mondo!‘Saluton, Mondo!Hello, World!Translations of Saluton, Mondo![ Esperanto -> English ]Saluton , Hello,Mondo ! World!简短输出方式:$ trans -brief ‘Saluton, Mondo!‘Hello, World!翻译文件:$ trans -b en:zh -i input.txt -o output.txt使用trans调取google translate进行翻译不能频频调用,频频调用之后会令后续请求503,被google限制请求!!使用nodejs调取命令行完成翻译的调研后,感觉本地实现翻译需要安装各种东西,不如做成web应用好了。用express快速建立网站应用,关于在nodejs下调用命令其实是没啥头绪的,搜索得出结果发现可以使用Child Process模块实现:const util = require(‘util’)const exec = util.promisify(require(‘child_process’).exec)exec(trans -V) .then(({stdout, stderr}) => { if(stdout.indexOf(“not installed”) > -1) return Error(stdout) }) .then(()=>exec(trans -b ${language} -i ${input} -o ${output})) .then(({stdout, stderr})=>{ return { input, output, message: stdout } })使用nodejs读写文件这个就不详细说明了,简单列一下用到的函数:fs.readFileSync(path)同步读取文件例子:const data = fs.readFileSync(’./test.txt’)console.log(data.toString())// testing!fs.writeFileSync(path, data) 同步写入文件例子:try{ fs.writeFileSync(’./test.txt’, ’testing!!’)}catch(e){ console.error(e)}fs.unlinkSync(path) 同步删除文件例子:try{ fs.unlinkSync(’./test.txt’)}catch(e){ console.error(e)}为express添加jwt先说一下jwt,全名叫JSON Web Tokens,是一种开放的,行业标准的RFC 7519方法,用于表示两端之间的应用安全。RFC是由Internet Society(ISOC)赞助发行的互联网通信协议规范,包含各种各样的协议,同时包含互联网新开发的协议及发展中所有的记录。jwt这种实现已经成为互联网通讯安全标准,那么在express怎样实现?首先安装下面两个包:auth0/node-jsonwebtokenauth0/express-jwtnpm i -S express-jwt jsonwebtoken使用:const { router } = require(’express’)const decode_jwt = require(’express-jwt’)const jwt = require(‘jsonwebtoken’)const secret = “your-secret” //盐// 登录router.get(’/login’, function(req, res, next) { /+[登录逻辑]…/ const token = jwt.sign(user, secret) res.status(200).send({ user, token })})//受限的接口router.get(’/user/star’, decode_jwt({secret: secret}), (req, res)=>{ const { user } = req const stars = [] /+[获取用户star列表]/ res.status(200).send(stars)})解释一下,jsonwebtoken包为加密作用, secret作为盐用来混淆内容(出于安全是不能对客户端公开),然后经过express-jwt解密处理http header里带有的authorization: Bearer [token]中的token来获得user信息。这样在/user/star接口中就能获取到用户资料做后续的业务处理了。基于express实现上传文件忘了说明这里提及的express版本为4,那么在新版的express 4文档中提及了这么一段关于上传文件的处理说明:In Express 4, req.files is no longer available on the req object by default. To access uploaded files on the req.files object, use multipart-handling middleware like busboy, multer, formidable, multiparty, connect-multiparty, or pez.意思是:express 4 版本req.files字段不在有效,需要使用上面提及的中间件提供支持才能实现读取上传来的文件。看了一番文档,最后选择了multer。下面讲一下如何使用:安装npm i -S multer使用const multer = require(‘multer’)const limits = { fieldSize: 102410243 }const extname = ‘html’//创建本地储存const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, ‘./uploads’); }, //储存文件时自定义文件名称 filename: function (req, file, cb) { cb(null, file.fieldname + ‘-’ + Date.now()); }})//创建上传处理const uploader = require(‘multer’)({ storage, limits, fileFilter(req, file, cb){ if(path.extname(file.originalname) === .${extname}) cb(null, true) else cb(new Error(upload file extname must be ${extname})) }})/** * 上传接口 * 只接受http头是content-type: multipart/form-data的数据 * 这里设定获取字段是file的内容储存成文件来完成文件上传工作 /router.post(’/trans_on_upload’, uploader.single(‘file’), (req, res)=>{ const { file } = req const fileData = fs.readFileSync(file.path) console.log(fileData.toString()) res.status(200)})multer接受多种文件上传方式:uploader.single(fieldname) 接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.fileuploader.array(fieldname[, maxCount]) 接受一个以 fieldname 命名的文件数组。可以配置 maxCount 来限制上传的最大数量。这些文件的信息保存在 req.files。uploader.fields(fields) 接受指定 fields 的混合文件。这些文件的信息保存在 req.files。fields 应该是一个对象数组,应该具有 name 和可选的 maxCount 属性。例子:[ { name: ‘avatar’, maxCount: 1 }, { name: ‘gallery’, maxCount: 8 }]uploader.none() 只接受文本域。如果任何文件上传到这个模式,将发生 “LIMIT_UNEXPECTED_FILE” 错误。这和 upload.fields([]) 的效果一样。uploader.any() 接受一切上传的文件。文件数组将保存在 req.files。警告: 确保你总是处理了用户的文件上传。 永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用。multer使用起来有一定限制,并不是所有项目合适使用,所以不做深入说明。在nodejs环境下读取并编辑html文件这里处理的流程是使用fs.readFileSync(path)读取html文件内容后,希望能以dom方式编辑内容,使用jsdom/jsdom能像在浏览器一样的方式处理DOM,是非常好用的工具。比如我的需求是获取所有TextNode提取内容进行翻译并替换原来内容,最后导出html内容:const { JSDOM } = require(“jsdom”)const { minify } = require(“html-minifier”)//递归获得所有TextNodeconst getAllTextNode = (node)=>{ var all = []; for (node=node.firstChild;node;node=node.nextSibling){ const { parentNode } = node if (node.nodeType==3){ all.push(node) } else all = all.concat(getAllTextNode(node)); } return all;}const html = “”/+[获取html内容]**/const vbrows = new JSDOM(minify(html, { collapseWhitespace: true}))const { document } = vbrows.windowconst textNodes = getAllTextNode(document.body)textNodes.forEach(textNodes=>{ const transStr = textNodes.textContent /翻译处理/ textNodes.textContent = transStr})//翻译结果console.log(’trans result’, vbrows.serialize())总结完成一个应用涉及的范围其实挺广的,内容如果再深入讨论应该能写更多内容吧。由于手上还有其他工作,只能大致记录关键词和使用的方式,原理方向等有时间再深入研究。 ...

January 17, 2019 · 2 min · jiezi

mpvue小程序《校友来了》成长记 | 给2018画下圆满句号

1、前言很久没有进行更新文章了,2018已经结束,2019已经开启,为了给2018画下圆满的句号,决定在新年来临前写一篇总结。如果有看过我文章的朋友或许知道还有一个小程序《校友足迹》,而《校友来了》正是《校友足迹》的升级版,我在原有的基础上添加了校友圈子功能,通过《校友来了》不仅能看到校友的分布情况,同时还可以与同城校友交流,查看校友名片等。让《校友来了》不仅仅是一个工具,更是一个同城校友发现与交流的圈子。初心《校友来了》旨在帮助更多同城校友交流,在这里你能发现新的机会、新的朋友、甚至可以帮你找到你的另一半!《校友来了》更是一个帮你拓展社交圈的好工具,在家靠父母,出门靠朋友,朋友又是同校校友,你在这个城市就会多一份异乡的温暖。体验2、校友足迹1.0关于《校友足迹》1.0可以查看我以前的另一篇文章mpvue小程序《校友足迹》成长记(一)3、升级原因《校友足迹》发布一段时候后,一直有计划想要升级一些新的功能,让其不再那么单一,但是由于工作上比较忙的原因,一直搁置了下来。直到有一次,有一位在公众号看了我文章的朋友,留言说想要聊一下我的《校友足迹》,他给了我重新升级《校友足迹》的想法。刚好这段时间工作上也不是很忙,那就利用业余时间说干就干,一口气升级一下。4、新增功能4.1 校友圈子校友圈子是这次最大的升级,基于《校友足迹》的思考,同城校友圈是我最想做的功能,先把全国校友按照省市划分,再根据学校划分,每个城市的校友都是不同的圈子。只有同城校友才能看到自己发的话题,这样也符合圈子的定义,既然是圈子就不能太大,要细化一些。同时在顶部Banner部分也做了公有与私有化划分,根据院校与城市会显示不同的轮播图。这也是为了帮助各大高校做宣传使用,因为在举行校友聚会的时候并不是所有的校友都能看到消息,更有一些老校友断了联系,通过这个宣传入口可以让更多的同城校友看到消息。4.2、校友名片校友名片可以通过点击昵称或者头像查看,此页面展示了同城校友的基本信息。由于小程序没有开放直接添加微信好友的接口,所以这里需要校友完善自己的个人信息后,通过点击复制的方式回到微信界面添加。虽然在操作方式上较为繁杂,但目前只能采用此方式。不过手机号是可以直接保存到通讯录中4.3 个人中心个人中心这里主要就是个人信息的基本展示与修改,同时还有自己发布过的话题,与话题相关的评论和点赞消息通知。由于话题功能需要用户基本信息才可,所以这里需要授权得到您的昵称和头像信息,不用担心隐私问题,因为小程序的这个授权也只是基本的头像和昵称,并没有隐私信息。4.4 消息中心消息中心主要分为个人消息与系统公告通知,个人消息可以接收到自己发布话题的点赞与评论消息。4.5 校友足迹页升级改版原有足迹页面比较单一乏味,新版在经过无数次的设计改版后,提升了整体逼格(O(∩_∩)O哈哈~),在分享出去后也是逼格满满,目前只显示排名前三的城市。下载按钮可以生成此页面的图片,方便你分享到朋友圈,聊聊则是直接通往校友圈子的入口。关于技术做的时候远比初想的难,从mysql数据库表的设计,到node,express业务逻辑的拆分和数据的封装与接口统一,一直到mpvue前端页面展示,以及mysql数据库查询优化,redis缓存的使用,还有JWT接口权限的验证,还有小程序的采坑,更有界面设计的优化,在这段时间里都一一经历了。学了不少新的东西,也回顾了以前的不少知识,算是一个伪node全栈的项目了,关于技术的详解我会另开一篇文章进行详细介绍,不仅作为一个分享交流,也作为这个项目的技术总结。如果有兴趣的话可以持续关注一下,在这里先放一下张目录结构前端mpvue + flyio + vuex + stylus + echartsmpvue mpvue 是一个使用 Vue.js 开发小程序的前端框架。框架基于 Vue.js 核心,mpvue 修改了 Vue.js 的 runtime 和 compiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验。vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。flyio 一个支持所有JavaScript运行环境的基于Promise的、支持请求转发、强大的http请求库。可以让您在多个端上尽可能大限度的实现代码复用。mpvue-echarts ECharts 的 Mpvue 小程序版本。开发者可以通过熟悉的 ECharts 配置方式及 Vue 语法,快速开发图表,满足各种可视化需求。 stylus CSS 的预处理框架,stylus 给 CSS 添加了可编程的特性,也就是说,在 stylus 中可以使用变量、函数、判断、循环一系列 CSS 没有的东西来编写样式文件,执行这一套骚操作之后,这个文件可编译成 CSS 文件后端服务mysql + redis + node (express + superagent + jsonwebtoken +crypto + ioredis + mysql + pm2)mysql 系型数据库管理系统 redis Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。 express Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。 superagent superagent 是一个轻量的,渐进式的ajax api,可读性好,学习曲线低,内部依赖nodejs原生的请求api,适用于nodejs环境下. jsonwebtoken JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案 crypto crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。在这里用做微信小程序加密解密 ioredis ioredis是一个功能强大,功能齐全的Redis客户端 mysql mysql的node.js驱动程序pm2 PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单 最后如果你对本小程序感兴趣的话可以分享到朋友圈,让更多的校友了解到《校友来了》,让同城校友的圈子越来越壮大。同时如果你有更好的想法或者idea,欢迎下方留言交流,如果你的院校有校友会相关的宣传需求,也欢迎联系我,我将免费为贵校提供宣传。 ...

January 16, 2019 · 1 min · jiezi

区块链 | ETH投票项目

区块链投票项目项目地址GITHUB项目说明区块链投票小项目,主要使用了Nuxt和web3.项目设计1. 服务结构虽然中心化服务器使得合约操作有了中心化风险,但是在当前智能合约性能较弱、功能不完善的时候中心化服务器有利于集中管理、提高服务性能、提供附加服务。2. 服务器结构设计除了基本的用户注册、用户账户信息存储、合约基本信息存储、附加服务如请求加入合约,其余操作均由服务器在区块链上进行操作。数据库中只存储基本信息。3. 用户页面设计提供合约操作的图形化接口,尽可能使用户直观、方便、快捷地进行合约操作,免去不方便地命令行等直接操作。4. 数据库设计数据库只用于存储基本信息,如用户信息、合约信息、请求信息、参与表。其余信息和操作都通过区块链来进行。使用说明1. 登录与注册账号密码按要求填写即可,但是注册时会直接用有资产的账户给新建账户转 1ETH,所以需要修改VoteSystem/server/contractOP/contractOP.js中showMeTheMoney函数中的测试账户。2. 创建投票项目由于使用 String 或 bytes 太多会导致 gas 不足,所以存储的数据都限定为 bytes32,也就是只能有限个字符。3. 创建的合约管理点开每一行可以看到所有的提议4. 首页加入项目所有未申请以及未参与的项目都会显示在主页。5. 请求参加项目所有请求参加的项目都在我的请求项中,可以在请求被批准前取消。6. 请求管理所有申请参加属于该用户创建的合约的请求都会显示在管理请求中。7. 参与的投票我参与的显示所有被批准参与的投票。8. 投票完成点击提议可以给合约投票,投票完成后刷新页面重新获取票数。测试测试视频使用ganache-cli作为测试用区块链

January 11, 2019 · 1 min · jiezi

2019年如何成为全栈工程师?

2019年到了,每个前端工程师都有一颗全栈的心,我也不例外,但随着前端能做的事情越来越多,前端的知识体系也越来越复杂,面对密集的知识点,我是倍感心里憔悴呀,浸没在术语和工具的浩瀚海洋中一度让我感到沮丧,静下心想想,一口气吃不成胖子,试图一下子学习所有东西我是万万做不到的,制定好一个合理的学习路线是必不可少的了。1. 前端万丈高楼平地起,学习前端也是一样,没有基础知识做地基,我还想做全栈那就是做梦呀。Html/Css/JavaScript自不必说,重中之重,务必得学的扎实。选择框架的确很难以抉择,React用户量遥遥领先,一定是极好的,于是我选择Vue,更快能上手才是我目前最需要的^-^。2018年Vue CLI已经发布了,今年Vue 3.0应该也是不远了,也将有Vue Native做跨平台开发,Vue的也越来越接近React那样的大型生态系统,虽然目前还有一段距离,啥也不说了,学就完了。摆一张Anthony Gore 所作的Vue学习的关键领域图,让我知道,我离把基础打好还有挺远。2. 后端使用node.js和Express.js来创建API服务器是后端学习的第一步,相信2019年,这个组合依旧是主导地位。GraphQL最近被炒的很热,有精力也得看看,至少也得知道它能解决哪些问题,以及如何在RESTful API中使用它做路由优化。HTTPS将无处不在,所以HTTPS Everywhere 或是Gaddy二选一必须一个。我还得知道如何使用HTTP2这个协议来优化内容传输,就怕我还没学会这个HTTP3就出了。Redis也是必学之一,缓存和内存存储是2019年需要学习的重要概念。了解Elasticsearch来做搜索,搜索也是web的重要组成部分啊。3. 测试有人把测试分为三种测试类型:单元测试:给定输入,测试输出,用于测试单个函数或类。集成测试:测试流程或组件是否按预期运行。端对端测试:测试用户的实际行为 ,不仅仅测试功能算了,还是好好学学一体化测试框架Jest吧。另外也得了解一下Cypress、Nightwatch。4. TypeScript圈内对TS铺天盖地的赞美让我知道再不学它我就要落伍了,程序员落伍的后果多么可怕,TS做为JS的超集,拥有静态类,并且Vue对TS支持足够完美,这已经足够理由去学它了。阶段性总结:以上这些还不足以成为一个优秀的全栈工程师,毕竟还有很多领域没覆盖到,但我还是要说路要一步一步走,学习也得一步一步来,学无止境,2019,一起加油。

January 11, 2019 · 1 min · jiezi

StateOfJS: 2018年JavaScript生态圈趋势报告

前言作为前端开发者,及时了解行业动态对我们的工作、学习方向起到至关重要的作用,毕竟知识太多,选择对的学习方向才能既省力又能紧跟技术发展潮流。近期,StateOfJS发布了刚刚过去的2018年JavaScript趋势报告,让我们一起来看看吧。2018 年的报告,总共有来自 153 个不同的国家共 20,000 多名 JavaScript 开发者参与调查(中国占总数的 0.37%,共 75 个开发者)。我们通过这个调查,来确定这些开发者正在使用什么技术,他们喜欢哪些技术,以及他们对哪些技术感兴趣。希望这个报告能帮助你在 JavaScript 生态之中找到适合自己的技术栈。今年的报告和前两年不同,取消了 CSS 部分。因为组织者认为 CSS 是一个庞大的话题,几乎可以作为一个独立的调查去做。所以这一次的报告就专注于 JavaScript。除了没有 CSS 之外,今年的报告主要围绕着 Flavors、前端框架/库、数据层、服务端框架、测试框架、Mobile & Desktop和其他工具这几个方向进行展开调查,我们接下来就分别来看看各个方面的情况是怎么样的。JavaScript Flavors (语言选择)从上图中我们可以看到,除了 ES6 之外,TypeScript 是最受欢迎的,远远超过其他几个同类型的。平均而言,85.9%的受访者使用过ES6,并乐于再次使用它。此比率较高的国家/地区显示为红色,较低的国家/地区显示为蓝色(调查受访者总数少于20的国家/地区将被省略)。46.5%的受访者使用过TypeScript,并乐于再次使用它。Reason 也同样值得大家关注,在2018年逐渐被越来越多人了解使用。因为 Reason 背后有 Facebook 的支持,并且满意度也很高。Front-end Frameworks(前端框架)在我们的日常开发工作中,前端框架/库都是不可或缺的,目前我们经常能够听到的就是 React、Vue、Angular,除此之外还有其他一些框架/库,那他们的情况又是怎么样的呢?我们一起来看看:通过上图,我们可以看出,React 还是最受欢迎的,Vue 排名第二,对比去年数据我们可以得出,框架战场已经由去年“三足鼎立”变成今年的“两强相争”。但是从“使用过并且将再次使用”的比例来看,Vue还是相去甚远,不过接受这个报告的开发者更多是国外的,国内也许这个数据会有所不同。Stateofjs 的数据显示,两年前有 27%的受访者表示从未听过 Vue,现在这个数据已经降到了 1.3%,在 GitHub 上,Vue 的 star 数已经超越了 React,其未来不可限量。而 Angular 虽然拥有庞大的用户群,但逐渐被开发者抛弃,已被 Vue 超越,除非 Google 出大招“相救”,否则 Angular 很难再重返冠亚军宝座。下面这张图表描绘了开发人员对前端库的满意程度,以及它们拥有的用户数量之间的关系:当然还有一些其他库,有兴趣的同学可以自己了解一下:Data Layer(数据层)从上图我们可以看到,虽然 Redux 在原始数量方面占据主导地位,但我们认为这种趋势更有利于 GraphQL 及相关技术。当然,从理论上讲,你可以将 Redux 和 GraphQL 结合使用,但实际上,这种组合最终可能会被特定于 GraphQL 的工具所取代,GraphQL 的客户端选择倾向于 Apollo,值得一提的是,最新版的 Apollo 发布,让 Redux 变得可有可无,所以明年这个排名可能会有大变化。我们再来看看其他工具的表现:Back-end Frameworks(后端框架)我们知道,Express 和 Koa 都是同一个作者 TJ 发起的,但是我们从上图可以看到,“使用它,并将再次使用”的比例来看,Express 还是最受欢迎的,并且远超于其他框架,值得一提的是 Next.js,虽然它仍无法比拟功能齐备的 Node 后端,但它专注于解决 React 应用的服务器渲染问题的特性,使之受到很多开发者的关注。Testing(测试)从上图的报告中我们可以看出,Jest 和 Mocha在受欢迎程度上基本差不多,但是 Jest 还是更受欢迎一些,也许是因为 React 和 Facebook 的原因,因为 Jest 可以说是 React 的标配啦,我们看到有 8.4% 的开发者曾经使用过 Mocha,但是以后将不再使用它。Mobile & Desktop (移动端和桌面应用)“任何可以用 JavaScript 编写的应用程序最终都会用 JavaScript 编写。”JavaScript 不仅仅局限于浏览器了。React Native 和 Electron 是使用 Web 技术构建移动和桌面应用程序的两个主要解决方案。巧合的是,他们在满意度和用户数量上具有相似的数据。Electron 的多功能性(它可以与任何 UI 框架一起使用)可以解释它获得如此超高满意度的原因。Other Tools(其他工具)JavaScript生态系统不仅限于库和框架。我们每天都在使用所有这些小工具,包括用于代码打包的工具和用于编写代码的文本编辑器。此外,我们还想知道哪些新浏览器API正在获得吸引力; 是的,听起来很疯狂,事实证明我们中的一些人也使用不是 JavaScript的语言!Other Languages (其他语言)Browser APIs (浏览器API)Build Tools (构建工具)Utility Libraries (实用程序库)Text Editors (编辑器)最后我们再来看看受访者们的看法结论2018年大部分是2017年已经观察到的趋势的延续。这对我们来说是个好消息,因为这意味着我们可以花更少的时间来担心使用什么,然后用更多时间实际使用它!但今年可能会再次变化。虽然前端的所有目前都很平静,但是如何从数据库获取数据到客户端的问题还远未解决,GraphQL肯定会开始在该领域制造越来越大的波浪。随着针对后端和状态管理层的GraphQL定制解决方案的出现,我们可能很快就会感觉到JavaScript再次在我们的脚下移动。但就目前而言,没有必要恐慌。现在是成为JavaScript开发人员的最佳时机,而且我们愿意打赌,2019年会更加清晰!链接:https://2018.stateofjs.com/in…技术放肆聊公众号,每日干货,最前沿的技术知识,扫描下方二维码关注: ...

January 11, 2019 · 1 min · jiezi

node和express搭建代理服务器(源码)

实例git 地址:https://github.com/xubaodian/… 本例用node和express搭建的代理服务器。,期望目标如下:1、开启某服务A,该服务可实现若干功能,例如普通的restful请求,文件上传,静态资源访问等等。2、开启node代理服务B,指向服务A,访问代理服务B,可访问服务A的任意功能。就如下图所示:图中上半部分是直接访问服务,下班部分是通过代理服务器访问服务。使用代理服务器时,浏览器向代理服务器请求数据,代理服务器转发请求,并将接收到的数据返回给浏览器,即所有的数据都通过代理服务器转发。带着这个目标,我们就讲述下如何实现该功能。既然是请求和响应转发,那我们就了解一下,什么是请求。请求和响应简述http请求和响应主要右报文头部、空行和报文主体三个部分组成。空行我们不用关心,其实对我们来说,只要完成报文头部和报文主体的转发,就可以说实现了代理功能。请求和响应通过代理的整个过程如下:1、代理服务器接收请求后,在将目标服务数据返回给浏览器前要保持请求。2、提取请求路径、请求头、请求主体等数据。3、以2中提取的数据为参数,向目标服务器发送请求。4、接收目标服务器返回数据,提取响应头,响应主体等数据。5、将4中的提取出来的数据返回给客户端(浏览器)。6、断开连接。经过这几个步骤,就实现了代理。代码实现下面直接上代码,然后做一些讲解。代理函数如下:const http = require(‘http’);const querystring = require(‘querystring’);//获取请求的cookie和query等let getHeader = (reqClient) => { let headers = reqClient.headers; headers.path = reqClient.path; headers.query = reqClient.query; headers.cookie = reqClient.get(‘cookie’) || ‘’; return headers;}//代理函数,options是代理设置,包括目标服务器ip,port等let proxy = (options) => { let reqOptions = { hostname: options.host, port: options.port } //返回请求处理函数,reqClient浏览器的请求,resClient是响应浏览器的对象 return function (reqClient, resClient) { //设置目标服务器的请求参数,头中的各项参数 let headers = getHeader(reqClient); reqOptions.headers = reqClient.headers; let query = []; if (headers.query) { Object.keys(headers.query).map(key => { query.push(key + ‘=’ + headers.query[key]); }); reqOptions.path = headers.path + (query.length === 0 ? ’’ : (’?’ + query.join(’&’))); } reqOptions.cookie = headers.cookie; reqOptions.method = reqClient.method; //向目标服务器发送请求,reqProxy是向目标服务器的请求,resProxy是目标服务器的响应。 let reqProxy = http.request(reqOptions, (resProxy) => { resProxy.setEncoding(‘utf8’); //设置返回http头 resClient.set(resProxy.headers); resClient.status(resProxy.statusCode); //接收从目标服务器返回的数据 resProxy.on(‘data’, (chunk) => { //接收目标服务器数据后,以流的方式向浏览器返回数据 resClient.write(chunk); }); //接收目标服务器数据结束 resProxy.on(’end’, () => { //向浏览器写数据结束。 resClient.end(); }); //目标服务器响应错误 resProxy.on(’error’, () => { //响应错误,结束向浏览器返回数据 resClient.end(); }); }); //接收浏览器数据 reqClient.on(‘data’, (chunk) => { //以流的方式向目标服务器发送数据 reqProxy.write(chunk); }); //接收数据结束 reqClient.on(’end’, () => { //向目标服务器写数据结束 reqProxy.end(); }); //普通JSON数据代理 if (Object.keys(reqClient.body).length) { reqProxy.write(querystring.stringify(reqClient.body)); reqProxy.end(); } }}module.exports = proxy;上面就是node代理的核心代码。支持普通的请求,静态资源代理,文件上传下载代理等功能。git 地址:https://github.com/xubaodian/… demo中,核心代码在common/proxy.js里,我还实现了两个测试服务。在server文件下的app.js和app2.js是两个服务的入口文件。app2.js是目标服务器,有三个测试页面1、http://localhost:20000/json.html post请求测试,对应’/json’接口,可发送数据,f12查看请求是否成功 2、http://localhost:20000/upload.html 文件上传测试,对应接口’/upload’接口,上传文件,f12查看请求是否成功,同时在服务器upload文件夹下会有文件。3、http://localhost:20000/get.html get请求测试,对应接口’/get’,同样f12查看app2为目标服务器,有3个接口。1、’/upload’接口,测试文件上传功能,上传文件将放在uploads文件夹下,上传的文件,文件名是一个uuid,没有后缀,添加后缀即可查看文件是完整。测试过,传1G的文件没问题,再大的文件没试过,有需要的可以试下2、’/json’,测试POST请求。3、’/get’,测试GET请求。app.js为代理服务为器,监听端口为18000,将所有请求转发至app2,即所有app2的接口静态资源,app中访问时一致的。测试步骤:1、可开启目标服务器,通过三个页面测试功能。2、开启代理服务器,访问下面三个页面:http://localhost:18000/json.html http://localhost:18000/upload.htmlhttp://localhost:18000/get.html 测试同样的功能。若和步骤1实现同样功能,则代理服务功能已经实现了。经过测试,代理功能是没问题的。如果问题欢迎留言,或发送邮件至472784995@qq.com。至于性能,我没测过,因为我自己这边的应用场景,访问量都不大,可以使用。 ...

January 10, 2019 · 1 min · jiezi

GraphQL 入门详解

简介定义一种用于API调用的数据查询语言核心思想传统的api调用一般获取到的是后端组装好的一个完整对象,该对象的结构相对固定,而且前端可能只需要用其中的某些字段,大部分数据的查询和传输工作都浪费了。graphQL提供一种全新数据查询方式,可以只获取需要的数据,使api调用更灵活、高效和低成本。特点需要什么就获取什么数据支持关系数据的查询API无需定义各种路由,完全数据驱动无需管理API版本,一个版本持续演进支持大部分主流开发语言和平台强大的配套开发工具使用方法下面我们通过搭建一个SpaceX的新闻网站来直观学习graphQL的基本使用方法,所有数据由 官方API 获得。服务端服务端采用node + express。新建一个node项目,安装如下依赖:$ npm i graphql express-graphql express axios创建入口文件 server.js,里面创建express服务。使用graphQL我们只需要设置一个路由,所有的请求都由这个graphQL的request handler处理:const express = require(’express’);const graphqlHTTP = require(’express-graphql’);const schema = require(’./schema’);const app = express();app.use(’/graphql’, graphqlHTTP({ schema, graphiql: true}));const PORT = process.env.PORT || 5000;app.listen(PORT,()=>console.log(Server started on port ${PORT}));graphqlHTTP是grapql的http服务,用于处理graphql的查询请求,它接收一个options参数,其中schema是一个 GraphQLSchema实例,我们待会儿定义。graphiql设置为true可以在浏览器中使用GraphiQL直接进行调试,之后会看到。更多express-graphql的用法请参考 Github express-graphql。schema接着我们定义schema,schema意为‘模式’,其中定义了数据模型的结构、字段的类型、模型间的关系,是graphQL的核心。新建schema.js文件,定义两个数据模型:LaunchType(发射)和 RocketType(火箭)。注意字段的数据类型需要使用GraphQL定义的,不能使用js中的基本数据类型。const { GraphQLObjectType, GraphQLInt, GraphQLString, GraphQLBoolean, GraphQLList, GraphQLSchema } = require(‘graphql’);const LaunchType = new GraphQLObjectType({ name: ‘Launch’, fields: () => ({ flight_number: { type: GraphQLInt }, mission_name: { type: GraphQLString }, launch_date_local: { type: GraphQLString }, launch_success: { type: GraphQLBoolean }, rocket: { type: RocketType }, })});const LaunchType = new GraphQLObjectType({ name: ‘Rocket’, fields: () => ({ rocket_id: { type: GraphQLString }, rocket_name: { type: GraphQLString }, rocket_type: { type: GraphQLString } })});有了数据模型之后,我们需要从数据库或者第三方API获取数据,在此我们从spacex的官方API获取。我们需要定义一个root query,root query将做为所有查询的入口,处理并返回数据,更多请参考 GraphQL Root fields & resolvers。在 schema.js中增加代码:const axios = require(‘axios’);…const RootQuery = new GraphQLObjectType({ name: ‘RootQueryType’, fields: { launches: { type: new GraphQLList(LaunchType), resolve(parent, args) { return axios.get(‘https://api.spacexdata.com/v3/launches').then(res => res.data); } } }});module.exports = new GraphQLSchema({ query: RootQuery});查询列表完成这一步,api服务基本搭建完成!我们看一下效果,在浏览器中输入 http://localhost:5000/graphql 将打开 Graphiql(生产环境建议禁用):我们可以只查询所有的 flight_number:或者更多的属性:是不是很简单很神奇!单个查询我们也可以通过传入参数查询单条信息:const RootQuery = new GraphQLObjectType({ name: ‘RootQueryType’, fields: { … launch: { type: LaunchType, args: { flight_number: { type: GraphQLInt } }, resolve(parent, args) { return axios.get(https://api.spacexdata.com/v3/launches/${args.flight_number}) .then(res => res.data); } } }});结果:前端刚刚我们都是用GraphiQL在浏览器调用接口,接下来我们看一下在前端页面中怎么调用graphql服务。前端我们使用react。在项目根目录初始化react项目:$ npx create-react-app client为了便于调试,在package.json中增加scripts:“start”: “node server.js”,“server”: “nodemon server.js”,“client”: “npm start –prefix client”,“dev”:“concurrently "npm run server" "npm run client" “样式我们使用bootswatch中的一款主题:GraphQL的客户端有多种实现,本次项目使用 Apollo,最流行的GraphQL Client。更多client请参考 GraphQL Clients。安装依赖安装如下依赖:$ cd client$ npm i apollo-boost react-apollo graphql其中 apollo-boost 是apollo client本身,react-apollo 是react视图层的集成,graphql 用于解析graphql的查询语句。设置client修改App.js内容如下:import React, { Component } from ‘react’;import ApolloClient from ‘apollo-boost’;import { ApolloProvider } from ‘react-apollo’;import ‘./theme.css’;import ‘./App.css’;import logo from ‘./spacex-logo-light.png’const client = new ApolloClient({ uri: ‘http://localhost:5000/graphql’});class App extends Component { render() { return ( <ApolloProvider client={client}> <div className=“container”> <img src={logo} id=“logo” /> </div> </ApolloProvider> ); }}export default App;和redux使用<Provider>传递store类似,react-apollo 通过 <ApolloProvider>将apollo client向下传递。实现query接着我们来实现显示launches的component,新增文件 components/Launches.js:import React, { Component, Fragment } from ‘react’;import gql from ‘graphql-tag’;import { Query } from ‘react-apollo’;import LaunchItem from ‘./LaunchItem’;const LAUNCHES_QUERY = gql query LaunchesQuery { launches { flight_number, mission_name, launch_date_local,, launch_success, } };export class Launches extends Component { render() { return ( <Fragment> <h1 className=“display-4 my-3”>Launches</h1> <Query query={LAUNCHES_QUERY}> { ({ loading, error, data }) => { if (loading) return <h4>Loading…</h4> if (error) console.log(error); return ( <Fragment> { data.launches.map(launch => <LaunchItem key={launch.flight_number} launch={launch}/>) } </Fragment> ) } } </Query> </Fragment> ) }}export default Launchesquery语句通过 graphql-tag 定义,传入 <Query> 执行获取数据并传入 LaunchItem 显示。components/LaunchItem.js:import React from ‘react’export default function LaunchItem({ launch: { flight_number, mission_name, launch_date_local, launch_success } }) { return ( <div className=“card card-body mb-3”> <div className=“col-md-9”> <h4>Mission: {mission_name}</h4> <p>Date: {launch_date_local,}</p> </div> <div className=“col-md-3”> <button className=“btn btn-secondary”>Launch Details</button> </div> </div> )}运行由于本地调试,client和server分别运行在不同的端口,所以需要先进行跨域处理,使用 cors。server.jsconst cors = require(‘cors’);…app.use(cors());效果好了,大功告成,我们来看一下效果:结语今天就主要介绍GraphQL工程的搭建和GraphQL Query的使用,更多关于GraphQL的内容比如 Mudtation下次有空会跟大家逐步讲解。本文灵感来源:Youtube@Traversy Media,感谢本文完整demo(增加部分功能):Github@MudOnTire ...

January 4, 2019 · 2 min · jiezi

NodeJS Express 中创建html5的server-sent event服务端

1、客户端代码<!doctype html><html><head><meta charset=“utf-8”><title>Server-Sent</title></head><body><div id=“result”></div><!–js–><script>var source = new EventSource(’/source.interface’); //数据接口source.onmessage = function(event){ document.getElementById(‘result’).innerHTML += event.data + ‘<br>’; }</script></body></html>2、Nodejs后台代码var express = require(’express’);var router = express.Router();router.get(‘source.interface’, function(req, res, next){ res.setHeader(‘Content-Type’, ’text/event-stream’); res.setHeader(‘Cache-Control’, ’no-cache’); res.send(‘data:’ + new Date() + ‘\n\n’); //后面必须带有’\n\n’,否则不会触发 });

January 3, 2019 · 1 min · jiezi

express.js的介绍及使用

Node.js 《Node.js 官网(中文)》Node.js 《Node.js 官网(英文)》<br/><br/>Node.js 是什么Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.1. JavaScript 运行时2. 既不是语言,也不是框架,它是一个平台Node.js 中的 JavaScript1. 没有 BOM、DOM2. 在 Node 中为 JavaScript 提供了一些服务器级别的 API 2.1 fs 文件操作模块 2.2 http 网络服务构建模块 2.3 os 操作系统信息模块 2.4 path 路径处理模块 2.5 …..<!– markdown-to-slides share1.md -o index.html -s slide.css –>I. express简介基于 Node.js 平台,快速、开放、极简的 Web 开发框架简单来说,封装了node中http核心模块,专注于业务逻辑的开发。安装方法npm install express –savekoaKoa - next generation web framework for node.jsKoa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。(project:koa-1.js)const Koa = require(‘koa’);const app = new Koa();app.use(async ctx => { ctx.body = ‘Hello World’;});app.listen(3000);<img src=“http://img0.ph.126.net/77JLQd...; />Hello XXX (node)var http = require(‘http’)http.createServer(function (req, res) { // 主页 if (req.url == “/”) { res.end(“Holle hemo!”); } // About页 else if (req.url == “/about”) { res.end(“Hello about!”); }}).listen(‘3009’, ’localhost’, function () { console.log(’listen 3009 ….’)})project:demo-1.js Hello XXX 测试结果:<img src=“http://img2.ph.126.net/oE0IH7...; height=“200” width=“600” />Hello XXX (express)var express = require(’express’)var app = express()app.get(’/’, function (req, res) { res.send(‘hello home …’)})app.get(’/about’, function (req, res) { res.send(‘hello about …’)})app.listen(3000, function () { console.log(’express app is runing …..’)})project:demo-2.js Hello XXX 测试结果:<img src=“http://img1.ph.126.net/py2gHZ...; height=“200” width=“600” />express 路由Routing refers to how an application’s endpoints (URIs) respond to client requests. -请求方法-请求路径-请求处理函数公开静态资源// 访问路径 http://127.0.0.1:3000/a.jsapp.use(express.static(’./public/’))// 访问路径 http://127.0.0.1:3000/public/a.jsapp.use(’/public/’,express.static(’./public/’))require 方法的加载规则-深入浅出 node.jsnode 中存在的模块主要有:核心模块 path fs http …自己定义的模块 (路径形式的模块)第三方模块 art-template express(使用npm安装的模块)// 加载核心模块const path = require(‘path’);// 加载自定义模块const foo = require(’./fooo.js’)// 加载第三方模块 node_modulesconst express = require(’express’)node 中require加载规则:优先缓存加载判断模块标识2.1 是否是核心模块 http 、fs 加载 缓存 export2.2 是否是文件模块 ./foo.js 加载 缓存 export2.3 是否是第三方模块 (第三方模块需要 npm install 安装)- node_modules/art-template/- node_modules/art-template/package.json- node_modules/art-template/package.json 中找main 作为文件加载入口- index.js 备选项- 进入上一级目录找 node_modules- 按照此规则依次向上查找,直到磁盘根目录找不到 报错 Can not find moudle XXXnode 中require加载规则:<img src=“http://img1.ph.126.net/rbUZEF...; height=“540” width=“300” /><img src=“http://img2.ph.126.net/h8Kc7e...; height=“540” width=“400” style=“margin-left:60px”/>nodemonnodemon《nodemon使用方法》nodemon reload, automaticallynodemon用来监视node.js应用程序中的任何更改并自动重启服务,非常适合用在开发环境中// 全局安装nodemonnpm install -g nodemonnodemon app.js中间件 body-parserexpress中没有内置的获取表单请求体的API,所以需要第三方包解析HTTP请求体This module provides the following parsers:JSON body parserRaw body parserText body parserURL-encoded form body parser使用方法:const bodyParser = require(‘body-parser’);const app = express();app.use(bodyParser.urlencoded({ extended: false }));app.use(bodyParser.json());app.use(function(req, res, next) { res.header(“Access-Control-Allow-Origin”, “”); res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested-With, Content-Type, Accept”); next();});router.post(’/students/new’, function (req, res) { console.log(req.body) Student.save(req.body, function (err) { if (err) { return res.status(500).send(‘Server error.’) } res.redirect(’/students’) })})post 提交方式测试:<img src=“http://img1.ph.126.net/82CmNm...; width=“350” /><img src=“http://img1.ph.126.net/wSMCvy...; width=“350” style=“margin-left:40px”/>package-lock.jsonnpm 5以前没有 package-lock.json 文件安装包时不需要加 –save参数 会自动保存依赖信息在安装包时,会自动更新package-lock.json文件package-lock.json文件保存node_molules中所有包信息(记录版本号和下载地址等信息)path 路径操作模块path.join(__dirname, ‘../node_modules’)path.basename(path[, ext])<img src=“http://img2.ph.126.net/MgfYCw...; />node 中的其他成员在每个模块中,除了require、export 等模块相关的API之外,还有两个特殊的成员__dirname 可以用来获取当前文件模块所属目录的绝对路径 动态获取__filename 可以用来获取当前文件的绝对路径 动态获取1. 在文件操作路径中,相对路径设计的是相对于执行node命令所在路径2. 模块中的路径标识就是相对于当前文件模块,不受执行node命令所处路径影响const fs = require(‘fs’)const path = require(‘path’)// 文件操作中的相对路径fs.readFile(‘c:/a/b/a.txt’, ‘utf-8’, function (err, data) { if (err) throw err console.log(data)})// 文件操作中的相对路径转化为动态获取的绝对路径fs.readFile(path.join(__dirname,’./a.txt’), ‘utf-8’, function (err, data) {})// 模块中的路径标识require(’./b’)II. Express 中间件中间件(middleware) 在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。常规的中间件模式<img src=“https://upload-images.jianshu…; />express 中间件express 中间件Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. 中间件的本质就是请求处理方法,把用户从请求到响应的整个过程分发到多个中间件中去处理,提高代码灵活性,动态可扩展<img src=“https://timgsa.baidu.com/timg...;quality=80&size=b9999_10000&sec=1542863886302&di=8c2650a2389ce7e21e5afcf63739266a&imgtype=0&src=http%3A%2F%2Fresupload.xueda.com%2Fupload%2F55ee1c9f-971f-4e88-a708-666a1459c388%2FkX159dhLPTXl.gif" />中间件的使用var express = require(’express’)var app = express()var myLogger = function (req, res, next) { console.log(‘LOGGED’) next() console.log(‘After LOGGED’)}var myLogger2 = function (req, res, next) { console.log(‘LOGGED2’) next(); console.log(‘After LOGGED2’)}app.use(myLogger)app.use(myLogger2)app.listen(3000, function () { console.log(’express app is runing …..’)})project:demo-3.js 运行结果如下:<img src=“http://img0.ph.126.net/RJXU-z...; />实现中间件机制function express() { var taskArrray = [] var app = function (req, res) { var i = 0 function next() { var task = taskArrray[i++] if (!task) { return; } task(req, res, next); } next(); } // 将中间件存入数组中 app.use = function (task) { taskArrray.push(task) } return app;}project:demo-4.js实现中间件机制测试结果var http = require(‘http’)var app = express();http.createServer(app).listen(‘3000’, function () { console.log(’listening 3000….’);});var myLogger = function (req, res, next) { console.log(‘LOGGED’) next() console.log(‘After LOGGED’)}var myLogger2 = function (req, res, next) { console.log(‘LOGGED2’) next(); console.log(‘After LOGGED2’)}app.use(myLogger)app.use(myLogger2)<img src=“http://img0.ph.126.net/RJXU-z...; />express 中间件分类应用层级别中间件不关心请求路径和请求方法的中间件,任何请求都会执行关心请求路径的中间件路由级别中间件不关心请求路径和请求方法的中间件,任何请求都会执行严格匹配请求方法和请求路径的中间件错误处理中间件404页面 全局错误页面内置中间件express.static第三方中间件body-parsercookie-session使用express 中间件project:demo-5.js// 不关心请求路径和请求方法的中间件app.use(function (req, res, next) { console.log(‘all request must execute!!’) next()})app.use(function (req, res, next) { console.log(‘all request must execute 1 !!’)})// 以/XXX 开头的路径的中间件app.use(’/user/:id’, function (req, res, next) { console.log(‘Request URL:’, req.originalUrl) next()}, function (req, res, next) { console.log(‘Request Type:’, req.method) next()})// 严格匹配请求方法和请求路径的中间件app.get(’/aa/bb’, function (req, res, next) { console.log(’/aa/bb’) next()})使用express 中间件// 内置中间件app.use(’/public/’, express.static(’./public/’))// 所有都匹配不到时 404 (放在最后)app.use(’/’, router)app.use(function (req, res, next) { res.send(‘This is 404 !!!!!’)})// 配置全局错误统一处理中间件app.get(’/aa/bb’, function (req, res, next) { fs.readFile(‘c:/a/b/index.js’, ‘utf-8’, function (err) { if (err) return next(err) })})app.use(function (err, req, res, next) { res.status(500).json({ err_code: 500, err_msg: err.message })})// 第三方级别中间件const bodyParser = require(‘body-parser’);app.use(bodyParser.urlencoded({ extended: false }));app.use(bodyParser.json());III. Express Generator通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。npm install express-generator -gexpress myapp –view=pugcd myappnpm installnpm run startproject:myapp 访问http://localhost:3000/ <img src=“http://img2.ph.126.net/Hdg8CX...; />目录结构与代码1.查看myapp目录结构2.结合中间件分析代码project: myapp<img src=“http://img1.ph.126.net/KyJ0a3...; height=“580px” style=“margin-left:400px;margin-top:-160px”/>相关中间件morganHTTP request logger middleware for node.jspugPug is a high performance template engine heavily influenced by Haml and implemented with JavaScript for Node.js and browsers. For bug reports, feature requests and questions, open an issue. For discussion join the chat room.h1 Pug - node template engine<h1>Pug - node template engine</h1>// compilevar fn = pug.compile(‘string of pug’, options);var html = fn(locals); // rendervar html = pug.render(‘string of pug’, merge(options, locals));IV. express 在vue项目中模拟接口结合ccs-operation-web中 模拟接口 ./api/server.js<img src=“http://img1.ph.126.net/WcCZiU...; />project:app.js<img src=“http://img2.ph.126.net/ODW9rX...; /><img src=“http://img1.ph.126.net/BPnIoG...; />运行express服务器"scripts”: { “server”: “nodemon api/server.js”, “dev”: “webpack-dev-server –inline –progress –open –config build/webpack.dev.conf.js”, // 影响ccs-operation-web/config/proxyConfig.js http://localhost:3002/api/listContracts?pin=X&all=X “devlocal”: “shell-exec –colored-output "npm run dev –local" "npm run server"”, }shell-executorA small nodejs module to execute shell commands in parallelnpm i -g shell-executor// –colored-output Use colored output in logsshell-exec –colored-output ’npm run lint’ ’npm run test’ ’npm run watch’ccs-operation-web ./api/server.jsconst express = require(’express’);const bodyParser = require(‘body-parser’);const request = require(‘request’);const path = require(‘path’);const walk = require(‘klaw-sync’);const { origin_proxy_url, local_proxy_port, local_proxy_url} = require(’../config/proxyConfig’);const app = express();app.use(bodyParser.urlencoded({ extended: false }));app.use(bodyParser.json());app.use(function(req, res, next) { res.header(“Access-Control-Allow-Origin”, “”); res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested-With, Content-Type, Accept”); next();});let _existRoutes = [];app.use( (req, res, next)=>{ const {url, body, method} = req; if (!~_existRoutes.indexOf(req.path)) { const rurl = origin_proxy_url.replace(//$/, ‘’) + url; let r = method === ‘POST’ ? request.post({url: rurl, form: body}, (err, httpRes, reqBody)=>{ console.log(err, reqBody, body) }) : request(rurl); console.log(本地未定义的请求,跳转到 ${method} ${rurl}); req.pipe(r).pipe(res); return; } next();});//遍历本目录下的 *.api.jswalk(path.resolve(’./’)) .filter(p=>/.api.js$/.test(p.path)) .map(p=>p.path) .forEach(part=>require(part)(app));//记录注册过的路由_existRoutes = app._router.stack.filter(s=>s.route).map(s=>s.route.path);app.listen(local_proxy_port, ()=>{ console.log(\n\n local server running at ${local_proxy_url} \n\n);});klaw-syncklaw-sync is a Node.js recursive and fast file system walker// 用法const klawSync = require(‘klaw-sync’)const paths = klawSync(’/some/dir’)// paths = [{path: ‘/some/dir/dir1’, stats: {}}, {path: ‘/some/dir/file1’, stats: {}}]<img src=“http://img1.ph.126.net/O9u0y_...; />requestRequest - Simplified HTTP client// 用法npm install requestvar request = require(‘request’);request(‘http://www.google.com’, function (error, response, body) { console.log(’error:’, error); console.log(‘statusCode:’, response && response.statusCode); console.log(‘body:’, body);});req.pipe(request(‘http://mysite.com/doodle.png')).pipe(resp)<img src=“http://img1.ph.126.net/B5PvG3...; />VI. 总结express 基于 Node.js 平台,快速、开放、极简的 Web 开发框架简单来说,封装了node中http核心模块,专注于业务逻辑的开发。express 核心内容 : 理解、使用中间件express 源码学习 路由express 中间件原理 ...

December 29, 2018 · 5 min · jiezi

Express 文档(express( ))

express()创建一个Express应用程序,express()函数是express模块导出的顶级函数。var express = require(’express’);var app = express();方法express.json([options])此中间件在Express v4.16.0及更高版本中可用。这是Express中的内置中间件函数,它使用JSON有效负载解析传入的请求,并基于body-parser。返回仅解析JSON的中间件,并仅查看Content-Type header与type选项匹配的请求,此解析器接受body的任何Unicode编码,并支持gzip的自动解压和deflate编码。在中间件(即req.body)之后的request对象上填充包含已解析数据的新body对象,或如果没有要解析的body则为空对象({})、Content-Type不匹配、或发生错误。由于req.body的形状基于用户控制的输入,因此该对象中的所有属性和值都是不可信的,应该在信任之前进行验证。例如,req.body.foo.toString()可能以多种方式失败,例如foo可能不存在或者可能不是字符串,而toString可能不是函数,而是字符串或其他用户输入。下表描述了可选options对象的属性。属性描述类型默认inflate启用或禁用处理压缩的body,禁用时,压缩的body会被拒绝。Booleantruelimit控制最大请求体大小,如果这是一个数字,则该值指定字节数;如果是字符串,则将值传递给bytes库以进行解析;混合"100kb"reviverreviver选项作为第二个参数直接传递给JSON.parse;你可以在有关JSON.parse的MDN文档中找到有关此参数的更多信息;函数nullstrict启用或禁用仅接受数组和对象;禁用时将接受JSON.parse接受的任何内容;Booleantruetype这用于确定中间件将解析的媒体类型;此选项可以是字符串、字符串数组或函数;如果不是函数,则将type选项直接传递给type-is库,这可以是扩展名(如json)、mime类型(如application/json),或带有通配符的mime类型(如*/或/json);如果是函数,则将type选项作为fn(req)调用,如果返回truthy值,则解析请求混合"application/json"verify此选项(如果提供)称为verify(req, res, buf, encoding);其中buf是原始请求体的Buffer;encoding是请求的编码,可以通过抛出错误来中止解析。函数undefinedexpress.static(root, [options])这是Express中的内置中间件函数,它提供静态文件,基于serve-static。注意:为获得最佳结果,请使用反向代理缓存来提高服务静态资源的性能。root参数指定从中提供静态资源的根目录,该函数通过将req.url与提供的root目录相结合来确定要提供的文件。当找不到文件时,它不是发送404响应,而是调用next()继续下一个中间件,允许堆叠和回退。下表描述了options对象的属性,另请参见下面的示例。属性描述类型默认dotfiles确定如何处理dotfiles(以点“.”开头的文件或目录);请参阅下面的dotfilesString“ignore”etag启用或禁用etag生成,注意:express.static总是发送弱ETagBooleantrueextensions设置文件扩展名回退:如果找不到文件,搜索具有指定扩展名的文件并提供找到的第一个文件;例如:[‘html’, ‘htm’]混合false

December 28, 2018 · 1 min · jiezi

Express 文档(在代理后面运行Express)

在代理后面运行Express在代理后面运行Express应用程序时,将(通过使用app.set())应用程序变量trust proxy设置为下面列出的值之一。虽然如果未设置应用程序变量trust proxy,应用程序不会运行失败,但它将错误地将代理的IP地址注册为客户端IP地址,除非配置了trust proxy。类型:Boolean如果为true,则客户端的IP地址被理解为X-Forwarded-*header中最左侧的条目。如果为false,则应用程序理解为直接面向互联网,客户端的IP地址来自req.connection.remoteAddress,这是默认设置。类型:IP地址要信任的IP地址、子网或IP地址和子网的数组,以下列表显示了预配置的子网名称:loopback — 127.0.0.1/8、::1/128linklocal — 169.254.0.0/16、fe80::/10uniquelocal — 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、fc00::/7你可以通过以下任何方式设置IP地址:app.set(’trust proxy’, ’loopback’) // specify a single subnetapp.set(’trust proxy’, ’loopback, 123.123.123.123’) // specify a subnet and an addressapp.set(’trust proxy’, ’loopback, linklocal, uniquelocal’) // specify multiple subnets as CSVapp.set(’trust proxy’, [’loopback’, ’linklocal’, ‘uniquelocal’]) // specify multiple subnets as an array指定后,IP地址或子网将从地址确定过程中排除,并将离应用服务器最近的不可信IP地址确定为客户机的IP地址。类型:Number信任来自前置代理服务器的第n跳作为客户端。类型:函数定制信任实现,只有在你知道自己在做什么的情况下才能使用它。app.set(’trust proxy’, function (ip) { if (ip === ‘127.0.0.1’ || ip === ‘123.123.123.123’) return true // trusted IPs else return false})启用trust proxy会产生以下影响:req.hostname的值派生自X-Forwarded-Host header中设置的值,该值可由客户端或代理设置。可以通过反向代理设置X-Forwarded-Proto来告诉应用程序它是https还是http甚至是无效的名称,该值由req.protocol反映。req.ip和req.ips值使用X-Forwarded-For的地址列表填充。trust proxy设置使用proxy-addr包实现,有关更多信息,请参阅其文档。上一篇:调试 ...

December 27, 2018 · 1 min · jiezi

Express 文档(数据库集成)

数据库集成添加将数据库连接到Express应用程序的功能只需在应用程序中为数据库加载适当的Node.js驱动程序,本文档简要介绍了如何在Express应用程序中为数据库系统添加和使用一些最流行的Node.js模块。这些数据库驱动程序是众多可用的驱动程序,对于其他选项,请在npm网站上搜索。Cassandra模块:cassandra-driver安装$ npm install cassandra-driver示例var cassandra = require(‘cassandra-driver’)var client = new cassandra.Client({ contactPoints: [’localhost’] })client.execute(‘select key from system.local’, function (err, result) { if (err) throw err console.log(result.rows[0])})Couchbase模块:couchnode安装$ npm install couchbase示例var couchbase = require(‘couchbase’)var bucket = (new couchbase.Cluster(‘http://localhost:8091’)).openBucket(‘bucketName’)// add a document to a bucketbucket.insert(‘document-key’, { name: ‘Matt’, shoeSize: 13 }, function (err, result) { if (err) { console.log(err) } else { console.log(result) }})// get all documents with shoe size 13var n1ql = ‘SELECT d.* FROM bucketName d WHERE shoeSize = $1’var query = N1qlQuery.fromString(n1ql)bucket.query(query, [13], function (err, result) { if (err) { console.log(err) } else { console.log(result) }})CouchDB模块:nano安装$ npm install nano示例var nano = require(’nano’)(‘http://localhost:5984’)nano.db.create(‘books’)var books = nano.db.use(‘books’)// Insert a book document in the books databasebooks.insert({ name: ‘The Art of war’ }, null, function (err, body) { if (err) { console.log(err) } else { console.log(body) }})// Get a list of all booksbooks.list(function (err, body) { if (err) { console.log(err) } else { console.log(body.rows) }})LevelDB模块:levelup安装$ npm install level levelup leveldown示例var levelup = require(’levelup’)var db = levelup(’./mydb’)db.put(’name’, ‘LevelUP’, function (err) { if (err) return console.log(‘Ooops!’, err) db.get(’name’, function (err, value) { if (err) return console.log(‘Ooops!’, err) console.log(’name=’ + value) })})MySQL模块:mysql安装$ npm install mysql示例var mysql = require(‘mysql’)var connection = mysql.createConnection({ host : ’localhost’, user : ‘dbuser’, password : ‘s3kreee7’, database : ‘my_db’});connection.connect()connection.query(‘SELECT 1 + 1 AS solution’, function (err, rows, fields) { if (err) throw err console.log(‘The solution is: ‘, rows[0].solution)})connection.end()MongoDB模块:mongodb安装$ npm install mongodb示例(v2.)var MongoClient = require(‘mongodb’).MongoClientMongoClient.connect(‘mongodb://localhost:27017/animals’, function (err, db) { if (err) throw err db.collection(‘mammals’).find().toArray(function (err, result) { if (err) throw err console.log(result) })})示例(v3.)var MongoClient = require(‘mongodb’).MongoClientMongoClient.connect(‘mongodb://localhost:27017/animals’, function (err, client) { if (err) throw err var db = client.db(‘animals’) db.collection(‘mammals’).find().toArray(function (err, result) { if (err) throw err console.log(result) })})如果你想要MongoDB的对象模型驱动程序,请查看Mongoose。Neo4j模块:apoc安装$ npm install apoc示例var apoc = require(‘apoc’)apoc.query(‘match (n) return n’).exec().then( function (response) { console.log(response) }, function (fail) { console.log(fail) })Oracle模块:oracledb安装注意:请参阅安装前提条件。$ npm install oracledb示例const oracledb = require(‘oracledb’);const config = { user: ‘<your db user>’, // Update me password: ‘<your db password>’, // Update me connectString: ’localhost:1521/orcl’ // Update me};async function getEmployee(empId) { let conn; try { conn = await oracledb.getConnection(config); const result = await conn.execute( ‘select * from employees where employee_id = :id’, [empId] ); console.log(result.rows[0]); } catch (err) { console.log(‘Ouch!’, err); } finally { if (conn) { // conn assignment worked, need to close await conn.close(); } }}getEmployee(101);PostgreSQL模块:pg-promise安装$ npm install pg-promise示例var pgp = require(‘pg-promise’)(/options/)var db = pgp(‘postgres://username:password@host:port/database’)db.one(‘SELECT $1 AS value’, 123) .then(function (data) { console.log(‘DATA:’, data.value) }) .catch(function (error) { console.log(‘ERROR:’, error) })Redis模块:redis安装$ npm install redis示例var redis = require(‘redis’)var client = redis.createClient()client.on(’error’, function (err) { console.log(‘Error ’ + err)})client.set(‘string key’, ‘string val’, redis.print)client.hset(‘hash key’, ‘hashtest 1’, ‘some value’, redis.print)client.hset([‘hash key’, ‘hashtest 2’, ‘some other value’], redis.print)client.hkeys(‘hash key’, function (err, replies) { console.log(replies.length + ’ replies:’) replies.forEach(function (reply, i) { console.log(’ ’ + i + ‘: ’ + reply) }) client.quit()})SQL Server模块:tedious安装$ npm install tedious示例var Connection = require(’tedious’).Connection;var Request = require(’tedious’).Request;var config = { userName: ‘your_username’, // update me password: ‘your_password’, // update me server: ’localhost’}var connection = new Connection(config);connection.on(‘connect’, function(err) { if (err) { console.log(err); } else { executeStatement(); }});function executeStatement() { request = new Request(“select 123, ‘hello world’”, function(err, rowCount) { if (err) { console.log(err); } else { console.log(rowCount + ’ rows’); } connection.close(); }); request.on(‘row’, function(columns) { columns.forEach(function(column) { if (column.value === null) { console.log(‘NULL’); } else { console.log(column.value); } }); }); connection.execSql(request);}SQLite模块:sqlite3安装$ npm install sqlite3示例var sqlite3 = require(‘sqlite3’).verbose()var db = new sqlite3.Database(’:memory:’)db.serialize(function () { db.run(‘CREATE TABLE lorem (info TEXT)’) var stmt = db.prepare(‘INSERT INTO lorem VALUES (?)’) for (var i = 0; i < 10; i++) { stmt.run(‘Ipsum ’ + i) } stmt.finalize() db.each(‘SELECT rowid AS id, info FROM lorem’, function (err, row) { console.log(row.id + ‘: ’ + row.info) })})db.close()ElasticSearch模块:elasticsearch安装$ npm install elasticsearch示例var elasticsearch = require(’elasticsearch’)var client = elasticsearch.Client({ host: ’localhost:9200’})client.search({ index: ‘books’, type: ‘book’, body: { query: { multi_match: { query: ’express js’, fields: [’title’, ‘description’] } } }}).then(function (response) { var hits = response.hits.hits}, function (error) { console.trace(error.message)}) ...

December 27, 2018 · 3 min · jiezi

Express 文档(调试)

调试ExpressExpress在内部使用debug模块来记录有关路由匹配、正在使用的中间件函数、应用程序模式以及请求—响应周期流的信息。debug就像是console.log的增强版本,但与console.log不同,你不必在生产代码中注释掉debug日志,默认情况下,日志记录处于关闭状态,可以使用DEBUG环境变量有条件地打开日志记录。要查看Express中使用的所有内部日志,请在启动应用程序时将DEBUG环境变量设置为express:。$ DEBUG=express: node index.js在Windows上,使用相应的命令。> set DEBUG=express:* & node index.js在express生成器生成的默认应用程序上运行此命令将打印以下输出:$ DEBUG=express:* node ./bin/www express:router:route new / +0ms express:router:layer new / +1ms express:router:route get / +1ms express:router:layer new / +0ms express:router:route new / +1ms express:router:layer new / +0ms express:router:route get / +0ms express:router:layer new / +0ms express:application compile etag weak +1ms express:application compile query parser extended +0ms express:application compile trust proxy false +0ms express:application booting in development mode +1ms express:router use / query +0ms express:router:layer new / +0ms express:router use / expressInit +0ms express:router:layer new / +0ms express:router use / favicon +1ms express:router:layer new / +0ms express:router use / logger +0ms express:router:layer new / +0ms express:router use / jsonParser +0ms express:router:layer new / +1ms express:router use / urlencodedParser +0ms express:router:layer new / +0ms express:router use / cookieParser +0ms express:router:layer new / +0ms express:router use / stylus +90ms express:router:layer new / +0ms express:router use / serveStatic +0ms express:router:layer new / +0ms express:router use / router +0ms express:router:layer new / +1ms express:router use /users router +0ms express:router:layer new /users +0ms express:router use / &lt;anonymous&gt; +0ms express:router:layer new / +0ms express:router use / &lt;anonymous&gt; +0ms express:router:layer new / +0ms express:router use / &lt;anonymous&gt; +0ms express:router:layer new / +0ms当对应用程序发出请求时,你将看到Express代码中指定的日志: express:router dispatching GET / +4h express:router query : / +2ms express:router expressInit : / +0ms express:router favicon : / +0ms express:router logger : / +1ms express:router jsonParser : / +0ms express:router urlencodedParser : / +1ms express:router cookieParser : / +0ms express:router stylus : / +0ms express:router serveStatic : / +2ms express:router router : / +2ms express:router dispatching GET / +1ms express:view lookup “index.pug” +338ms express:view stat “/projects/example/views/index.pug” +0ms express:view render “/projects/example/views/index.pug” +1ms要仅从路由器实现中查看日志,请将DEBUG的值设置为express:router,同样,要仅查看来自应用程序实现的日志,请将DEBUG的值设置为express:application,依此类推。express生成的应用程序express命令生成的应用程序也使用debug模块,其debug的命名空间的范围限定为应用程序的名称。例如,如果你使用$ express sample-app生成应用程序,则可以使用以下命令启用调试语句:$ DEBUG=sample-app:* node ./bin/www你可以通过分配以逗号分隔的名称列表来指定多个调试命名空间:$ DEBUG=http,mail,express:* node index.js有关调试的更多信息,请参阅debug。上一篇:错误处理 ...

December 27, 2018 · 2 min · jiezi

Express 文档(错误处理)

错误处理错误处理是指Express如何捕获和处理同步和异步发生的错误,Express附带一个默认的错误处理程序,因此你无需编写自己的错误处理程序即可开始使用。捕捉错误确保Express捕获运行路由处理程序和中间件时发生的所有错误非常重要。路由处理程序和中间件内的同步代码中发生的错误不需要额外的工作,如果同步代码抛出错误,则Express将捕获并处理它,例如:app.get("/", function (req, res) { throw new Error(“BROKEN”); // Express will catch this on its own.});对于由路由处理程序和中间件调用的异步函数返回的错误,必须将它们传递给next()函数,Express将捕获并处理它们,例如:app.get("/", function (req, res, next) { fs.readFile("/file-does-not-exist", function (err, data) { if (err) { next(err); // Pass errors to Express. } else { res.send(data); } });});如果将任何内容传递给next()函数(字符串’route’除外),则Express将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。如果序列中的回调不提供数据,只提供错误,则可以按如下方式简化此代码:app.get("/", [ function (req, res, next) { fs.writeFile("/inaccessible-path", “data”, next); }, function (req, res) { res.send(“OK”); }]);在上面的示例中,next作为fs.writeFile的回调提供,调用时有或没有错误,如果没有错误,则执行第二个处理程序,否则Express会捕获并处理错误。你必须捕获由路由处理程序或中间件调用的异步代码中发生的错误,并将它们传递给Express进行处理,例如:app.get("/", function (req, res, next) { setTimeout(function () { try { throw new Error(“BROKEN”); } catch (err) { next(err); } }, 100);});上面的示例使用try…catch块来捕获异步代码中的错误并将它们传递给Express,如果省略try…catch块,Express将不会捕获错误,因为它不是同步处理程序代码的一部分。使用promises可以避免try…catch块的开销或者使用返回promises的函数,例如:app.get("/", function (req, res, next) { Promise.resolve().then(function () { throw new Error(“BROKEN”); }).catch(next); // Errors will be passed to Express.});由于promises会自动捕获同步错误和拒绝promises,你可以简单地提供next作为最终的catch处理程序,Express将捕获错误,因为catch处理程序被赋予错误作为第一个参数。你还可以使用处理程序链来依赖同步错误捕获,通过将异步代码减少为一些简单的代码,例如:app.get("/", [ function (req, res, next) { fs.readFile("/maybe-valid-file", “utf8”, function (err, data) { res.locals.data = data; next(err); }); }, function (req, res) { res.locals.data = res.locals.data.split(",")[1]; res.send(res.locals.data); }]);上面的例子有一些来自readFile调用的简单语句,如果readFile导致错误,那么它将错误传递给Express,否则你将快速返回到链中下一个处理程序中的同步错误处理的世界。然后,上面的示例尝试处理数据,如果失败,则同步错误处理程序将捕获它,如果你在readFile回调中完成了此处理,则应用程序可能会退出,并且Express错误处理程序将无法运行。无论使用哪种方法,如果要调用Express错误处理程序并使应用程序存活,你必须确保Express收到错误。默认错误处理程序Express附带了一个内置的错误处理程序,可以处理应用程序中可能遇到的任何错误,此默认错误处理中间件函数添加在中间件函数堆栈的末尾。如果你将错误传递给next()并且你没有在自定义错误处理程序中处理它,它将由内置错误处理程序处理,错误将堆栈跟踪写入客户端,堆栈跟踪不包含在生产环境中。将环境变量NODE_ENV设置为production,以在生产模式下运行应用程序。如果在开始写入响应后调用next()并出现错误(例如,如果在将响应流式传输到客户端时遇到错误),则Express默认错误处理程序将关闭连接并使请求失败。因此,当你添加自定义错误处理程序时,必须在headers已发送到客户端时委托给默认的Express错误处理程序:function errorHandler (err, req, res, next) { if (res.headersSent) { return next(err) } res.status(500) res.render(’error’, { error: err })}请注意,如果你在你的代码调用next()出现错误多次,则会触发默认错误处理程序,即使自定义错误处理中间件已就绪也是如此。编写错误处理程序以与其他中间件函数相同的方式定义错误处理中间件函数,除了错误处理函数有四个参数而不是三个:(err, req, res, next),例如:app.use(function (err, req, res, next) { console.error(err.stack) res.status(500).send(‘Something broke!’)})你可以在其他app.use()和路由调用之后定义错误处理中间件,例如:var bodyParser = require(‘body-parser’)var methodOverride = require(‘method-override’)app.use(bodyParser.urlencoded({ extended: true}))app.use(bodyParser.json())app.use(methodOverride())app.use(function (err, req, res, next) { // logic})中间件函数内的响应可以是任何格式,例如HTML错误页面、简单消息或JSON字符串。对于组织(和更高级别的框架)目的,你可以定义多个错误处理中间件函数,就像使用常规中间件函数一样,例如,为使用XHR和不使用XHR的请求定义错误处理程序:var bodyParser = require(‘body-parser’)var methodOverride = require(‘method-override’)app.use(bodyParser.urlencoded({ extended: true}))app.use(bodyParser.json())app.use(methodOverride())app.use(logErrors)app.use(clientErrorHandler)app.use(errorHandler)在此示例中,通用logErrors可能会将请求和错误信息写入stderr,例如:function logErrors (err, req, res, next) { console.error(err.stack) next(err)}同样在此示例中,clientErrorHandler定义如下,在这种情况下,错误会明确传递给下一个错误。请注意,在错误处理函数中不调用“next”时,你负责编写(和结束)响应,否则这些请求将“挂起”,并且不符合垃圾回收的条件。function clientErrorHandler (err, req, res, next) { if (req.xhr) { res.status(500).send({ error: ‘Something failed!’ }) } else { next(err) }}实现“catch-all”的errorHandler函数,如下所示(例如):function errorHandler (err, req, res, next) { res.status(500) res.render(’error’, { error: err })}如果你有一个具有多个回调函数的路由处理程序,则可以使用route参数跳转到下一个路由处理程序,例如:app.get(’/a_route_behind_paywall’, function checkIfPaidSubscriber (req, res, next) { if (!req.user.hasPaid) { // continue handling this request next(‘route’) } else{ next(); } }, function getPaidContent (req, res, next) { PaidContent.find(function (err, doc) { if (err) return next(err) res.json(doc) }) })在此示例中,将跳过getPaidContent处理程序,但app中的/a_route_behind_paywall中的任何剩余处理程序将继续执行。对next()和next(err)的调用表明当前处理程序已完成并处于什么状态,next(err)将跳过链中的所有剩余处理程序,除了那些设置为处理上述错误的处理程序。上一篇:使用模板引擎下一篇:调试 ...

December 27, 2018 · 2 min · jiezi