Code Monkey home page Code Monkey logo

knowledge-base's Introduction

Vue.js        12 hrs 59 mins  █████████████░░░░░░░░░░░░   51.84 %
JavaScript    6 hrs 14 mins   ██████▒░░░░░░░░░░░░░░░░░░   24.92 %
TypeScript    2 hrs 54 mins   ███░░░░░░░░░░░░░░░░░░░░░░   11.62 %
JSON          1 hr 14 mins    █▒░░░░░░░░░░░░░░░░░░░░░░░   04.96 %
Markdown      59 mins         █░░░░░░░░░░░░░░░░░░░░░░░░   03.93 %

knowledge-base's People

Contributors

dream4ever avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

knowledge-base's Issues

让 macOS 终端也能用上 SSR

需求描述

虽然在 macOS 上安装了 SSR 客户端,但是在终端中还是没法使用 SSR 的。因此即使设置了 npm 的源用的国内淘宝的源,有时候下载依赖还是会从国外的服务器下载,然后就下载不过来。

应用过程

Google iterm use socks proxy,在 V2EX 上看到了一个有效的方法:
有什么方法可以让 Terminal (iTerm) 走 socks5
。同时结合另一篇文章中的思路:Mac OSX终端走shadowsocks代理,整理出如下的操作过程:

先在终端中用 curl ip.cn 检查本机 IP,确认终端目前没有用到代理。

$ curl ip.cn
当前 IP:*.*.*.* 来自:山东省青岛市 移动

由于已在 SSR 客户端软件中设置代理地址为 socks5://127.0.0.1:1080,因此在终端中用如下命令,让 HTTP 协议走 SOCKS5:

$ export http_proxy=socks5://127.0.0.1:1080

然后再检查本机 IP,确保代理设置生效:

$ curl ip.cn
当前 IP:*.*.*.* 来自:日本

这个时候,就可以放心地下载 npm 依赖了。下载完成后,在终端中再执行命令 unset http_proxy,恢复 HTTP 代理为默认设置。

Windows 远程桌面连接客户端无法记住账户

需求描述

似乎是在 Windows 10 升级到 1803 版本之后,每次远程桌面连接到同样是 Windows 系统的阿里云服务器上的时候,就总是提示需要输入账户密码。

之前在本机设置过远程桌面连接客户端,保存了账户信息,是可以直接登录到服务器上的。这么一看,很可能是 Windows 10 版本升级之后,改了系统中的某些默认设置,因为之前使用远程桌面连接时也没有修改过系统设置。

解决过程

最开始是用中文关键字搜索的,发现尝试了几个结果都不起作用,于是换英文关键字 windows remote desktop remember my credentials 进行搜索。

第一个链接虽然是微软官网的,但是感觉讲的不够详细,那就再往后看。

第二个链接是 Super User 上的(Super User 相当于运维界的 Stack Overflow),这个链接应该比较靠谱。照着 最高票回答 做了一番设置,发现不起作用。

于是接着看 Google 的搜索结果,第四个链接 How to Fix RDP not Saving Password / Credentials 讲了一些和第二个链接不太一样的东西,照着设置了一番,这下果然好了!

要点总结

  • 如果发现通过 Windows 远程桌面连接来登录服务器时,服务器总是需要输入账号和密码才能登录,那说明客户端这边的配置有问题,需要进行修改。
  • 客户端需要设置两个地方,在组策略编辑器中(按快捷键 Win+R 弹出运行窗口,然后输入 gpedit.msc 即可打开该编辑器),在 计算机配置 => 管理模板 => Windows 组件 => 远程桌面服务 => 远程桌面连接客户端 这个选项中,不允许保存密码在客户端计算机上提示提供凭据 这两项都要设置为 未配置 状态。
  • 计算机配置 => 管理模板 => 系统 => 凭据分配 这个选项中,允许分配默认凭据用于仅 NTLM 服务器身份验证允许分配默认凭据允许分配保存的凭据允许分配保存的凭据用于仅 NTLM 服务器身份验证 这四个选项,都要设置为 已启用 状态,勾选 将 OS 默认值与上述输入连接起来 选项,并点击 将服务器添加到列表 右侧的 显示 按钮,将 TERMSRV/* 这个值添加进来,并点击确定。

检查页面中视频是否处于播放状态

需求描述

自己写代码需要实现视频的播放、暂停功能,要实现暂停功能,就需要检查视频当前是否处于播放状态。

方案调研

调研过程

Google:js check if video playing,第一个结果就是 Stack Overflow 的,亲测靠谱!

入选方案

以列表形式记录入选的方案。

应用过程

关键的就是下面代码中 if 语句的判断条件:

var video = document.querySelector('video');
if (video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2) {
  video.pause();
}

要点总结

无。

GitHub 项目用代理推送至服务器

需求描述

在本机编写的项目,时不时地就因为众所周知的原因无法推送到 GitHub 上了。既然阳关大道走不通,那就只好走独木桥了。

方案调研

调研过程

本机的项目往 GitHub 推送失败时,会报下面的错误:

ssh: connect to host github.com port 22: Connection timed out

用上面的报错信息 Google,看到 Stack Overflow 上有解决方法:git - ssh: connect to host github.com port 22: Connection timed out | Stack Overflow

上面的修改只是让本机项目走 http 协议,还要让项目走本地代理才行,设置方式参考:laispace/git 设置和取消代理

入选方案

应用过程

在终端先用指令 ssh -T [email protected] 测试是否能通过 ssh 连接到 GitHub 上,如果提示连接超时,说明 ssh 的确连不到 GitHub 上。

ssh 不行,那就换 http 方式。在项目所在目录中,执行 git config --local -e,会显示当前项目的 git 设置,把 ssh 方式的 URL:url = [email protected]:username/repo.git 改成 http 方式的 URL:url = https://github.com/username/repo.git

然后!不要忘了然后,还要让 git 走本地代理。当然了,http.proxyhttps.proxy 后面究竟是用 socks5 还是 http,都要看代理软件的设置,根据自己的实际情况来就行。

$ git config --global https.proxy 'socks5://127.0.0.1:1080'
$ git config --global http.proxy 'socks5://127.0.0.1:1080'

要点总结

Google + Stack Overflow 解决程序员所有问题 😄

跨平台开发源码文件换行问题

需求描述

执行 git add 时,提示 git warning: LF will be replaced by CRLF

这个问题是 Windows 和 Mac 下换行方式不同所导致的。

方案调研

调研过程

Google 上面的提示文字,有两个有价值的链接:

入选方案

应用过程

参考上面链接里的内容,执行了下面的命令:

$ git config --global core.autocrlf input

先这样用着,后面再看看结果如何。

要点总结

无。

IIS 代理的 vue-cli 页面无法稳定访问

需求描述

用 IIS 架设的网站,某个路径下的静态页面无法稳定访问,网页是用 vue-cli 打包生成的。

另外两个同样通过 URL 重写映射的路径就没问题,不过那两个路径不是用 vue-cli 打包的,一个是简单的静态页面,另一个则是 Node.js 项目。

有时候重启网站后,该静态页面又能够恢复一段时间的正常访问,有时候重启也不管用。

方案调研

既然 IIS 有问题,而且也找不到原因,那就试试 Nginx。去 Nginx 官网看了看,Windows 版本和非 Windows 版本的版本号是一样的,说明 Windows 版本不是老古董,那就放心用吧。

应用过程

用 Nginx 来代理打包后的静态页面,在本机可以用 localhost:9999 进行测试访问。

server {
  listen 9999;
  server_name localhost;

  location / {
    root e:/upcweb/mingpian/dist/;
    try_files $uri $uri/ /index.html;
  }
}

然后再通过 IIS 的 URL 重写功能,将网址映射到 Nginx 所代理的地址上。

IIS 重写规则如下:

  • 模式: ^(mingpian/)(.*)$
  • 条件(全部匹配): {REQUEST_FILENAME} 不是文件 且 不是目录
  • 操作:重写为 URL http://localhost:9999/{R:2}
  • 附加选项:附加查询字符串,停止处理后续规则

要点总结

  • 条条大路通罗马,IIS 不行,还有 Nginx 嘛!真是技多不压身,哈哈。

Mongoose 异步执行

需求描述

最开始使用 Mongoose 的时候,对各种方法的返回结果不清楚,于是代码返回的不是期望中的结果。

因此就研究了一下 Mongoose 的几个常用 API,以及合理的使用方法。

方案调研

调研过程

在 Mongoose 中,不同的方法执行后的返回结果是不一样的。

.find() 方法本身返回的是一个 Query 类型的对象, .find().exec() 返回的才是 Promise 对象。

Mongoose find 方法的说明:Model.find()

Google mongoose async await,在 Stack Overflow 上有篇问答可以参考:Using mongoose promises with async/await

入选方案

应用过程

理论阐释

重点理解Using mongoose promises with async/await

由于 .find().exec() 返回的是 Promise 对象,要使用其中的值,方法本身要用 return 语句将 Promise 对象返回给调用它的方法。

而调用它的方法中,还要用 await 关键字执行这个被调用的方法获取最终执行结果。并且主调方法也要加上 async 关键字才行。

实际代码

// 主调方法还需要加上 async 关键字
async function getToken() {
  try {
    // 异步执行的方法,所以加上 await 关键字才能写成同步执行的形式
    let token = await getLocalToken()
    if (!token.access_token) {
      console.log(token)
      return token
    } else {
    //   let token = await getNewToken()
    //   let result= await saveToken()
    }
    return token
  } catch (error) {
    console.log(error)
  }
}

function getLocalToken () {
  // 返回 Promise 对象记得加 reutrn
  return TokenModel
    .find({})
    .exec() // 调用 exec() 才会得到 Promise 对象
    // 下面都是废代码!想想为什么?
    // .then(token => {
    //   return token
    // })
    // .catch(err => {
    //   return 0
    // })
}

后续优化

待完成:

要点总结

暂无

微信 JS-SDK 接口调用

需求描述

随着业务需求的不断增加,微信 JS-SDK 接口的调用就成了必然。因为只有合法调用了微信 JS-SDK 接口,才能使用相关功能,比如自定义微信内转发链接的样式等等。

方案调研

整体流程

接口权限

公众号接口权限说明:在开发业务之前,一定要先来这里看看,当前用于开发的公众号是否有对应的权限!

  • 接口配置信息:这里设置的接口 URL 用于返回 echostr ,所以应当填写 abc.com/wx 这样的接口地址而不只是域名。
  • JS接口安全域名:用来调用后端接口的域名,所以应当只填写域名,以便网站下的所有服务均可使用接口。

设置安全域名

用于调用后端服务的前端页面,所属域名必须在公众号的 JS接口安全域名 的列表之中。

设置 ⇒ 公众号设置 ⇒ 功能设置 ⇒ JS接口安全域名,在这里将前端页面所属域名添加进来。

另外在设置界面提供下载的 txt 文件,必须能够通过指定的域名直接访问到,否则也会设置失败。

接入平台

先接入微信公众平台:接入指南

重点是第二步,在自己的服务器上要编写后台服务,将微信服务器所发送的 GET 请求中的 echostr 原样返回,这样微信服务器才能验证成功,平台就正式接入了。

注意:即使所有设置都不变,每次提交服务器配置时,微信服务器所发送的 GET 测试请求中的各项参数也会变化。

再注意:本次提交服务器配置的时间,如果和上一次提交隔了几分钟以上的话,初次提交会超时,再次提交就 OK 了,不知道是不是自己的代码写得还不够完善。

示例代码:

router.get('/wx', function (req, res, next) {
  res.send(req.query.echostr);
});

设置后端服务 IP 的白名单

设置了 IP 白名单之后,自己的服务器才能获取到 access_tokenjsapi_ticket

开发 ⇒ 基本配置 ⇒ 公众号开发信息 ⇒ IP 白名单 ⇒ 查看 ⇒ 修改

接口调试工具

微信公众平台接口调试工具:用于验证指定的 appIdappSecret 能否成功获取 access_token

微信 JS 接口签名校验工具:用于验证签名算法是否正确

获取 access_token

参考链接:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

let APPID = ""
let APPSECRET = ""
let accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET

获取 jsapi_ticket

let ACCESS_TOKEN = ""
let jsApi_Ticket = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + ACCESS_TOKEN + "&type=jsapi"

注意:确认一下发送到后端的 URL 是否需要先用 encodeURIComponent() 编码。

签名

通过接口获取到 access_token 再获取到 jsapi_ticket 之后,要用 jsapi_ticket 进行签名。这时签名所用的 timestampnoncestr ,必须和前面第一步接入平台验证 echostr 时所用的 timestampnoncestr 相同。也就是说每次在公众号的“开发 ⇒ 基本配置 ⇒ 服务器配置”中更新了 timestampnoncestr 之后,也要在后台的签名代码中对应更新。

这么说的话,在每次验证服务器配置的时候,把 timestampnoncestr 写入数据库或者缓存不就好了?

前端调用

测试账号

提供给开发者的测试账号,似乎可以不受限制地进行转发?上网查了查,测试帐号好像也没什么特殊的限制,写代码测试了一下,的确可用。不过为了保险起见,还是用正式的账号吧。

配置信息

  • 设置 ⇒ 公众号设置 ⇒ 功能设置 ⇒ JS 接口安全域名
  • 开发 ⇒ 基本配置 ⇒ 全部设置

应用过程

此处将实际的业务代码补充进来。

要点总结

研究这个微信 JS-SDK 的调用真是个大工程,印象中搞了两个星期才弄好?要踩的坑实在是太多了。

注意事项

开发者工具控制台输出结果

用微信开发者工具测试微信 JS-SDK 接口的调用情况时,启动开发者工具之后,首次打开调用了接口的页面时,控制台显示的“当前页面通过 wx.config 获取到的 JSSDK 权限如下”的结果为空,需在不关闭开发者工具的前提下,刷新一下页面,这里显示的结果才有对应的值。

经过测试,发现这个问题在 Windows 和 Mac 上的微信开发者工具中均会出现。

Windows 下 Nginx 的安装、配置、持久化运行

精简流程

下载最新版 Nginx

What’s the difference between the “mainline” and “stable” branches of nginx? 这篇文章可知,尽量下载最新版的 mainline 版本,而不是 stable 版本。

测试运行

  • 将 nginx 配置文件拷贝至新版 nginx 的 conf 目录下
  • 在命令行中定位至 nginx 所在目录,执行 .\nginx.exe -t 测试配置文件是否可用
  • 配置文件测试无误后,执行 start .\nginx.exe 启动 nginx,并查看 nginx 代理的页面是否可正常访问
  • 页面检查完成后,执行 .\nginx.exe -s stop,停止 nginx 程序

NSSM 持久化运行

参考资料:run nginx as windows service

  • 命令行中定位至 64 位 NSSM 程序所在目录
  • 执行 .\nssm.exe install,会出现 NSSM 图形化安装界面
  • Path 参数指向 nginx 最新版程序的位置,如 D:\Software\nginx-1.17.9\nginx.exe
  • Startup directory 参数指向 nginx 所在文件夹,如 D:\Software\nginx-1.17.9
  • I/O 标签下的 Input,设置为 start .\nginx.exe
  • I/O 标签下的 OutputError,设置为 nginx 访问成功和访问失败的日志文件,如 d:\Software\nginx-1.17.9\logs\access.logd:\Software\nginx-1.17.9\logs\error.log
  • 点击 Install service,安装服务,成功后会有提示
  • 最后在命令行中再手动执行 .\nssm.exe start nginx,将 nginx 服务运行起来,然后访问 nginx 所代理的页面,如果正常显示,说明 nginx 服务已配置成功,done!

需求描述

vue-cli 打包后的页面,在用 IIS 代理的情况下,会随机出现页面请求无法到达服务器的情况。因为请求无法到达服务器,所以在日志中也看不出什么问题。

为了保证页面的可用性,只好临时安装配置上 Nginx,让 Nginx 代理页面,IIS 负责将请求转发给 Nginx。

方案调研

调研过程

查看 Nginx 官网的 Windows 安装程序版本,和其它系统的版本是一样的,那就可以放心使用了。

入选方案

应用过程

Windows 下运行

  • 启动程序: cd 至 exe 所在目录之后,执行 start .\nginx.exe 。一个新的命令行窗口会一闪而过,访问 localhost:80 能够显示 Nginx 相关页面,说明已经配置成功。
  • 日志文件:在 exe 所在目录的 logs 子文件夹中,需要在配置文件中开启记录日志的选项。
  • 重启程序: .\nginx.exe -s reload
  • 测试配置文件是否有错误: .\nginx.exe -t

参考资料

Windows 下保活

  • 需求:Nginx 进程在 Windows 服务器中经常会莫名其妙地消失,查看系统日志,也看不到程序异常退出的记录。要让它在后台稳定运行,就需要以服务的形式启动。
  • Google:nginx auto start windows
  • 推荐方案:run nginx as windows service,通过 NSSM 这款绿色软件,把 Nginx 配置为服务,让它随系统启动。

路径匹配不上的问题

服务器上有几个页面在 Nginx 及 IIS 中配置之后,通过域名访问总是有问题。后来直接访问 Nginx 代理的页面,发现同样有问题,这个时候才知道并不是 Nginx 没有匹配上路径,而是代码本身就不完善,后面还需要进一步调整。

参考资料

打包版本不能附加路径

vue-router 开启了 history 模式时,就会有这个问题;如果不开启,就没有这个问题了。

vue-router 不开启 history 模式时,URL 中 # 后面的部分不会发往服务端,Nginx 官方文档也有说明,location 指令只能匹配 URL 中域名之后的第一个斜杠 / 与第一个问号 ? 或第一个井号 # 之间的部分。

vue-router 为组件设置路径为 path: '/msg/:msg?',组件中通过 beforeRouteUpdate() 方法监听路由的变化。Nginx 中的代理如下设置:

location /msg/ {
  alias e:/upcweb/vue-router/dist/;
  try_files $uri $uri/ /index.html;
}

在浏览器中访问 http://localhost:9999/msg/ 时,会自动转为访问 http://localhost:9999/msg/#/ 这个页面,对应于上面项目的根组件。需要以 http://localhost:9999/msg/#/msg/123/ 这种形式,才能访问到指定组件设置的路径。

vue-router 开启了 history 模式之后,照着下面的方式配置过,访问 http://localhost:9999/msg/123/ 这样的路径都会匹配到 Nginx 中设置的 location /,而不是按照期望中的匹配到路由中的动态部分:


参考资料

要点总结

以列表形式记录本次需求实现过程中的要点:

  • IIS 出了问题,就尝试 Nginx,千万不要在一棵树上吊死。
  • 所访问的网址先是通过 IIS 重定向到 Nginx 定义的 location,然后 location 又对应具体的物理路径。最开始访问网址有问题,还以为是 Nginx 的配置没写对,后来直接访问 Nginx 定义的 location,才发现是 vue-router 相关的代码没写对,调试功力还是不够好啊,有待提升。

限制 IP 访问指定路径

需求描述

部分页面只想让指定 IP 段的用户访问。

方案调研

调研过程

第三次研究

  • Google:IIS 限制访问 IP
  • 根据网上的资料,查到可以直接在 IIS 中只允许指定 IP 访问,但是!为子目录设置的 IP 访问限制不管用!禁止未指定的客户端访问之后,添加了允许的 IP 段,这些 IP 也无法访问,怎么设置都不行。

第二次研究

  • Google:validate ip:用于验证是否为合法的 IP 格式,关键字方向不对。
  • Google:check ip:检查 IP 是否在指定的 IP 段之类。
  • danielcompton/ip-range-check

第一次研究

如何使 Express 只允许指定的 IP 段访问特定的路由?在下面的文章中给出了方法。

Express ip filter for specific routes?

入选方案

  • danielcompton/ip-range-check:可以有效地限制访问者的 IP,可用于整个项目,也可应用于指定的路径,很方便。

排除方案

  • IIS 的 IP 限制功能:无法让指定页面只允许部分 IP 访问。

应用过程

// 以 Express 为例
if (req.ip && req.ip.split(':').length > 0) {
  const ip = req.ip.split(':').pop();
  const isIpAllowed = ipRangeCheck(ip, config.allowedIpList);

  if (isIpAllowed) {
    // do something
  } else {
    res.sendStatus(404);
  }
}

要点总结

暂无。

微信公众号文章展示 HTML 页面

需求描述

像网上很常见电子相册之类的 HTML 页面,能否在微信中展示?

方案调研

不用想了,不可能的。

应用过程

要点总结

一个 vue-cli 项目编译生成多个子项目

资料汇总

搜索关键词:vue-cli multiple pages application


以下为旧版方案。

需求描述

用 vue-cli 生成多个页面,要放在各个不同的域名下,该如何实现呢?

方案调研

调研过程

Google vue-cli multiple dist folder,第一个链接 How to direct vue-cli to put built project files in different directories? 和自己的需求并不相关,讲的是如何把编译生成的 html 放到一个目录下,js、css 等静态资源文件放到另一个目录下。

再看第二个链接 How to create multiple vue apps in same folder,一看提问的内容,完全就是自己想要实现的需求嘛!很好。

一个回答说可以在 webpack 中设置 entryCopyWebpackPlugin 来实现需求:

module.exports = {
    entry: {
        App1: './App1/main.js',
        App2: './App2/main.js'
    },
    plugins: [
        new CleanWebpackPlugin(['dist'])
        , new CopyWebpackPlugin([
            { from: './App1/*.css' },
            { from: './App1/*.html' },
            { from: './App2/*.css' },
            { from: './App2/*.html' },
        ])
    ],

提问者在这个回答下面留言,说是在 Multiple entry points -> multiple html outputs? #218 中找到了解决办法。

同时提问者自己也单独补充了一个回答,给出了自己在用的方法,设置 webpack.prod.conf.js 中的 HtmlWebpackPlugin 插件:

new HtmlWebpackPlugin({
    filename: 'app1.html',
    template: 'app1_template.html',
    inject: true,
    chunks: ['app1', "vendor", "manifest"],
    chunksSortMode: 'dependency'
}),
new HtmlWebpackPlugin({
    filename: 'app2.html',
    template: 'app2_template.html',
    inject: true,
    chunks: ['app2', "vendor", "manifest"],
    chunksSortMode: 'dependency'
}),

入选方案

应用过程

为了保证能够按自己的需求生成多个 index.html,那么就需要先研究研究哪些地方会用到这个文件。

搜索新建的 vue-cli 项目的 buildconfig 文件夹,研究了一下用到 index.html 的文件,相关代码及功能整理如下:

// config\index.js
// 设置发布后的 `index.html` 的路径
module.exports = {
   ...
  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
  }
}
// build\webpack.dev.conf.js

  // 设置开发模式下 historyApiFallback 的重写规则
  devServer: {
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },

  // 设置什么?
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
// build\webpack.prod.conf.js

  // 设置什么?
  // 这里 config.build.index 的值就是第一处用到 `index.html` 的代码
  plugins: [
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
    }),

此外,生成的页面都是放在网站的子目录下的,所以还需要设置静态资源相对于 index.html 的路径。

Git hook 实现自动部署

需求描述

公司所购阿里云 ECS 服务器的操作系统为 Windows 2012,想要改进项目部署的方式。但是安装了 2012 的 ECS 服务器无法安装 docker,如果启用 SSH 又担心降低服务器的安全性,于是最后考虑研究 git hook 以实现项目的自动部署。

方案调研

先以 git hook 自动部署git hook auto build 作为关键字上网搜索,对整体流程有了基本的了解,知道了 post-receive 或者 post-update是所需的 hook。

然后在此过程中,了解到 GitHub 本身提供 webhook 功能,可以监听项目的各个阶段。比如本机 push 了代码到 GitHub 之后,GitHub 将 POST 指定的内容到用户设定的 Payload URL,这个 URL 可以是用户编写的后台服务的 URL。

URL 接收 GitHub 的 POST 请求之后,可执行对应的动作,比如将最新的项目代码 pull 到服务器上的指定目录并执行编译操作,这样就可以将项目的最新版本自动部署至服务器。

既然如此,那直接用 GitHub 为每个项目提供的 webhook 其实更方便,可以用一个 Payload URL 为各个项目设置好自动发布的代码,便于集中管理。

关键字清单

还是英文的搜索结果更靠谱。

  • Google 关键字: git hook 自动部署
  • Google 关键字: git hook auto build
  • Google 关键字: github webhook 自动部署

入选方案

  • typicode / husky:typicode 的这个项目也蛮不错,可以考虑。
  • rvagg/github-webhook-handler:在 GitHub 上用 webhook 作为关键字搜索,然后只显示 JavaScript 语言为主的项目,按 Star 数排行,第一页的项目筛选了一遍之后,发现只有这个项目是靠谱的。

参考资料

应用过程

GitHub 的 Webhook 还是很省心的,只要填写的 API 能通就可以,自己写了一个 Express 的后端服务,用来自动将指定项目的代码 pull 下来,但是用 nssm 启动为服务之后,不知道为什么不管用,还得调试调试。

要点总结

Vue 组件修改网页标题

需求描述

在部分业务中,网页的标题是在页面获取到用户当前请求的 URL 后动态生成的。

而在 Vue 的组件化开发中,自己最开始不知道如何在组件或路由中动态更改页面的标题,于是在 vue-cli 项目的 index.html 中用原生 JS 修改页面标题。带来的一个问题就是用户会感知到标题的变化。

既然存在这个问题,就需要研究一下如何在 vue-cli 项目中,展示页面之前就动态修改标题,让用户感知不到这个变化。

方案调研

调研过程

先 Google 关键字: vue set html title before,搜索结果页的第一个链接 how to change document.title in vue-router? #914 就是 vue-router 项目中关于这个需求的讨论,值得好好研究研究。

第二个链接 How can I bind the html <title> content in vuejs?
也给出了建议使用的组件 vue-headful

知道了这个组件之后,又用 vue head vue document vue title 作为关键字在 GitHub 上搜索,找到了 Star 数更高的组件 vue-head

入选方案

应用过程

要点总结

跳过微信内置浏览器缓存

Updated at 2020年03月11日:

今天又研究了一下如何跳过缓存,因为一个在线教材的业务在不断更新各个图书的内容,但是有用户反映没有看到更新后的内容。

做了如下设置,等用户反馈:

  1. IIS 中,指定路径的 HTTP 响应头添加如下标志:
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0
  1. HTML 页面中,head 块添加如下代码:
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  <meta http-equiv="Pragma" content="no-cache" />
  <meta http-equiv="Expires" content="0" />

参考链接:

  1. IIS 中,指定路径的“输出缓存”配置中,禁用特定类型文件的缓存:

image

参考链接:


Updated at 2019年8月15日:

今天在调试一个后端程序,发现在 Express.js 里设置了 HTTP 响应头来禁用缓存的话,前端页面调用的 API 就不会被缓存,每次都必定向后端发起请求,并且 HTTP 响应的状态码始终为 200,开心!哈哈。这么说,会被微信缓存的这些页面,可以通过 Express.js 来渲染?然后设置 HTTP 响应头来禁用缓存,这样就不担心微信和 QQ 的缓存问题了?可以试试。

PS:在 IIS 设置同样的 HTTP 响应头,是起不到作用的,必须得在 Express.js 里设置才行。

Updated at 2019年3月25日:

刚才在微信中测试页面,发现页面所加载的 CSS 样式是被缓存过的,也就是说即使设置了 HTTP Header 的 Cache-Control 字段为 no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0,也依然可能不生效?那就闹心了……那还是得用 window.location = xxx.html?v=Math.random() 这种方法来保证始终访问的是最新的页面,当然了,目标页面也需要用 Webpack 之类的工具来打包,给资源文件名加上 hashtag,来保证用户始终看到的是最新的资源。

Updated at 2019年03月09日:

问了问大神,尝试在 Nginx / IIS 中设置 HTTP Header 的 Cache-Control 字段为 no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0,终于实现禁用缓存的需求了,超级开心!而且之前在网页中设置 meta 属性的方法不管用,是因为这样设置之后,只能控制网页所加载的其他静态资源的缓存设置,无法控制网页本身的缓存设置,所以还是得从 Nginx / IIS 上面入手。

Updated at 2018年11月19日:

上网查了一圈,好像还是只能用 window.location = xxx.html?v=Math.random() 的方法,跳转到新页面,来变相地跳过微信内置浏览器的缓存。

需求描述

微信默认会缓存网页及相关资源,网页一旦更新,用户扫码后还需手动刷新一下,才能看到更新后的页面,否则就只能看到旧页面,这个问题如何解决?

方案调研

参考资料:

测试记录

应用层

自己的手机

  • iPhone 7
  • iOS 12.1.4
  • 自用流量
  • IIS 各级目录按照默认设置启用了缓存
  • 未在 HTTP 响应头中禁用缓存
  • 代码层面也未操作缓存
类型 缓存 具体操作
HTML 更改 HTML 页面内容,微信再次扫码,显示的还是旧页面
JS 更改 HTML 所引用的外部 JS 文件的内容,微信再次扫码,就会加载新的 JS
CSS 更改 HTML 所引用的外部 CSS 文件的内容,微信再次扫码,就会加载新的 CSS
IMG img 标签中的 src 不变,实际图片更改之后,微信再次扫码,显示的是旧图片

老刘的手机

  • iPhone 6
  • iOS 9.1
  • WiFi
  • IIS 各级目录按照默认设置启用了缓存
  • 未在 HTTP 响应头中禁用缓存
  • 代码层面也未操作缓存
类型 缓存 具体操作
HTML 更改 HTML 页面内容,微信再次扫码,显示的还是旧页面
JS 更改 HTML 所引用的外部 JS 文件的内容,微信再次扫码,就会加载新的 JS
CSS 更改 HTML 所引用的外部 CSS 文件的内容,微信再次扫码,就会加载新的 CSS
IMG img 标签中的 src 不变,实际图片更改之后,微信再次扫码,显示的是旧图片

朱姐的手机

  • OPPO A57t
  • Android 6.0
  • WiFi
  • IIS 各级目录按照默认设置启用了缓存
  • 在 HTTP 响应头中禁用缓存
类型 缓存 具体操作
HTML 更改 HTML 页面内容,微信再次扫码,显示的还是旧页面
JS 更改 HTML 所引用的外部 JS 文件的内容,微信再次扫码,就会加载新的 JS
CSS 更改 HTML 所引用的外部 CSS 文件的内容,微信再次扫码,就会加载新的 CSS
IMG img 标签中的 src 不变,实际图片更改之后,微信再次扫码,显示的是旧图片

会话层

已经在微信中扫码访问过的页面,用 IIS 的 URL 重写功能,将这个页面的请求重写到百度首页,微信再次扫码访问该页面,依然显示旧页面,说明请求就没有到后端。

应用过程

无效方案

怎样禁止微信内置浏览器的缓存?

上面这个链接里,讲的各种设置 HTML meta 属性的方法,在 iOS 中全部无效。

有效方案

后端(ExpressJS)

参考 How to disable webpage caching in ExpressJS + NodeJS? 这篇文章,在 app.js 中全局设置 HTTP 响应头的 Cache-Control 字段,在全站范围内禁用缓存。

服务端(Nginx / IIS)

在 Nginx / IIS 中设置 HTTP Header 的 Cache-Control 字段为 no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0,就可以禁用微信内置浏览器的缓存功能了。不过这个应当只用在图书配套资源的页面上。

另外还需要研究一下,微信内置浏览器的缓存多久才会过期。反正四五分钟内连续多次扫描同一个二维码,是不会过期的。

设置跳转页

参考资料的后两个链接,讲的都是将用户扫描后所访问的初始页面设置为中转页面,在页面中用 window.location = xxx.html?v=Math.random() 这种方式跳转到目标页面,这样可以保证用户总能看到最新的页面。

然后还需要用 Webpack、Parcel 这样的工具,更新 HTML 页面所引用的静态文件,这样才能保证整个网页都是最新版的。

PS:如果源页面和目标跳转页面在同一个目录中,则 window.location = xxx.html?v=Math.random() 这里,直接写 HTML 页面的文件名即可,不需要再加上路径。

过程记录

MDN搜索header cache no-store
发请求的时候明确告诉服务器,不要缓存
应用层的问题不要飘移到下层
做实验可以,但不是你的解决方案
这是在应用层的东西,不要放到你的会话层来处理
再说明确点,这个问题应该在你的代码里来解决,而不是在你的web server
你的代码就要告诉微信,我不要你缓存
加随机参数也行,一般是对付cdn的,有标准方法,我觉得cache no-store好些
越底层越克制
看看http协议吧
这话我跟你说了好多次了
白瞎了这么好的脑子了
你下回肯定会在header里加很多东西,请克制
RFC
英文好,就看这,别的野鸡书看了也是浪费时间
http协议几乎是最好懂的协议了
都看,先看数字小的
这文档会指引你看相关东西的
你其实可以每天看一篇

CSS 强制惯性滚动

需求描述

忘了改了什么地方,手机查看页面,上下滚动时没有惯性效果了。

方案调研

调研过程

入选方案

应用过程

overflow-y: scroll;
-webkit-overflow-scrolling: touch;

要点总结

问题倒是解决了,但是当初为什么会产生这个问题呢……

vue-cli 的 index.html 中不能用字符串模板

需求描述

报错代码:Template execution failed: ReferenceError: name is not defined

方案调研

调研过程

Google 上面的报错代码,在 ERROR in Template execution failed: ReferenceError: htmlWebpackPlugin is not defined 这个 issue 中,给出了不算解决办法的解决办法:在 index.html 里面不要用字符串模板就没问题了。

入选方案

JS/JavaScript/Node.js 前端/后端文件上传

需求描述

如果是后台管理所需的文件上传功能,就不考虑前端兼容性。但是后端能保证上传功能无问题么?

方案调研

前端上传

在 GitHub 中用关键字 upload 搜索,并筛选编程语言为 JavaScript,按 Star 数排名,第一个是 pqina/filepond,紧接着的几个,要么已经归档了,要么是专用于 Angular 的,还有一个 Plupload 很多年前用过,但是没有好感。再之后的 23/resumable.js 看着挺顺眼,可以考察考察。

后端上传

Node.js 生态

查看 filepond 这个库的 README,发现列出的配套的 Backend 没有 Node.js。

接着往后翻 README,在比较靠后的位置,有一篇 Using FilePond with NodeJS,看来还是有 Node.js 的解决方案的。大致看了一下这篇文章,后端用的是 Express.js 作为底层框架,用 node-formidable / formidable 这个库实现接收上传文件的功能。

又看了看 formidable 这个库,17 年 1 月发布的最后一次 release?21 年和 18 年又发布过 3 次,但在 Releases 界面默认是折叠状态,搞不懂是为啥。

然后用 node.js file upload 作为关键字进行 Google,第一个链接是 express-fileupload,先记下来。

第二个链接是一篇 2020 年 2 月写的文章 Handling File Uploads in Node.js with Express and Multer,里面提到了 Multer、Formidable、Multiparty 这三个库是主流的 Node.js 上传库。Formidable 前面提到过,17 年之后就没更新了,另外俩也没好太多,Multiparty 前面几年诈尸式更新过几次,21 年就没动静了,Multer 好一点点,平均每年更新两三个版本的节奏。

如果要从这三个里面选的话,那就选 Multer 吧,至少比另外两个活跃,虽说也活跃不到哪里去。

另外 node.js file upload 这组关键字可以继续挖掘挖掘,目前只看了搜索结果的前两条。

其他语言

又想起老徐之前说过的 seaweedfs,去看了看这个项目,看到项目的标签里有 object-storageblob-storage,回头可以筛选一下这两个标签里的项目,看看有哪些比较好的项目能用上。

调研心得

文件上传分为前端部分和后端部分,搜索时可以缩小至两部分各自的范围,提高搜索结果的质量。

模板搜索网站包括 Google、GitHub、掘金、思否等国内外主流搜索引擎/网站。

搜索关键字也要不断扩充,比如前端部分的文件上传,底层是把 form 标签的 enctype 字段设置为 multipart/form-data,同时要用 post 方法才行,你看,这就是 upload 之外的又一个关键字。

结果汇总

关键字列表

  • upload
  • multipart/form-data
  • file server
  • node.js
  • object-storage
  • blob-storage

搜索网站

  • Google
  • GitHub
  • Stack Overflow
  • 掘金
  • 思否

前端项目

后端项目

相关文章

flexbox 适配低端设备

需求描述

flexbox 做定位非常方便,但是老旧手机需要额外设置,因此做了简单的研究。

方案调研

调研过程

Google:flexbox ios support

第一个搜索结果是 caniuse 的:CSS Flexible Box Layout Module,从列表中数据可知,iOS 7.1+ 和 Android 4.4+ 都是完全支持的,那就好说了。

第二个搜索结果是 CSS-Tricks 的:Using Flexbox: Mixing Old and New for the Best Browser Support,从文中内容可知,要在老旧手机上正常使用 flexbox,还需要针对不同的设备做不同的设置。

根据搜索结果中的 Flexbox not working on iPad and Safari 可知,可以用 Autoprefixer 为 flexbox 添加对应的浏览器兼容性设置。

当然了,如果用 webpack 来编译项目的话,直接配置好 autoprefixer 就可以了,最终生成的代码会自动添加兼容老旧浏览器的 CSS 代码。

入选方案

具体代码

Autoprefixer 中定义的 Browserlist 参数如下,可保证兼容所需的设备:

>= 0.1%, ie >= 8, ios >= 8, android >= 4.4

应用过程

在使用过程中,发现 iOS7 不支持 justify-content: space-around 这个属性(值?)。

参考资料

网站压力测试

需求描述

因业务需要,需对考试系统进行压力测试。

方案调研

流量分析

压力测试

  • 选出一款用于对网站进行压力测试的工具。
  • 对网站进行压测,查看前端、后端优化前后网站性能差异。

入选方案

以列表形式记录入选的方案。

应用过程

阅读资料

先把 https://github.com/Dream4ever/Coding-Life/blob/master/Front-End/Front-End Resource Collection.md#性能优化 这里收集的资料过一遍。

一系列的文章读完了,下面就该把这些文章综合起来,列出所有可优化的点,然后按照重要性依次优化。

要点汇总

推后。

性能分析

先在电脑上查看服务器往客户端发送数据的情况。

  • 在 Chrome 的隐身模式下,访问指定页面。
  • 访问首个页面之前,要开启开发者工具的“禁用缓存”选项,并清除浏览器缓存,保证首次访问页面的效果。
  • 首个页面加载成功之后,将浏览器完整截图,然后清空请求信息,并开启“保留日志”选项。之后每加载完成一个页面并截图之后,就都要清空请求信息,这样就能完整保留下一个页面的请求。
  • 首个页面加载成功之后,取消“禁用缓存”选项,这样后面的页面就可以正常使用缓存了。

整套流程下来,服务端对浏览器的响应就都记下来了。将服务器返回的文件按类型分类(比如分为 PHP/CSS/JS/图片 这四类),以 PHP 文件为例,表格格式如下所示:

文件名称 体积 优化方案 实施难度 优先级
exercise 219K 前后端分离
exam 91K 同上 同上
result 31K 同上 同上

接着在手机上研究客户端往服务器发送数据的情况。

  • 让手机和电脑连上同一个 WiFi,配置手机 WiFi 的 HTTP 代理为电脑端 Charles 中设置的端口号,IP 当然是用电脑的 IP 了。
  • 手机只需要查看向服务器发送的数据包的尺寸。用各个页面测试,请求的 Header 部分普遍在 600~800B 之间,Body 的话,只在提交答案时才有实际意义,自主练习时,20 道选择题加 20 道判断题,Body 的尺寸为 719 字节;自我检测和模拟考试是 80 道选择题加 40 道判断题,Body 的尺寸为 2.11KB。
    • 每道题提交数据格式为: QuestionA123456=B ,一共 17 字节
    • 多道题提交数据格式为: **&**&** ,N 道题的总字节数为 18 * N - 1

统计完上面两方面的数据之后,又根据前面在 Chrome 中截的图,统计了每个页面加载完毕时,所发送的请求数(不含已缓存资源)以及所加载文件的总体积(同样不含已缓存资源),之后可优先对总体积明显偏大的或请求数过多的页面进行优化。

压力测试

待完成。

要点总结

  • 待补充。

iOS 视频自动播放

需求描述

在部分 iPhone 机型的微信客户端中,视频默认是无法自动播放的。

方案调研

应用过程

先 Google:iOS 微信 视频 preloadiOS 微信 音频 视频自动播放 这篇文章说需要结合微信 JS-SDK 才可以。

经过测试之后,发现不只是微信,iOS 下的 Chrome 也可以自动播放了。

但是按照上面的设置之后,在页面中再次测试,发现页面报错,不显示内容了。

因为一方面给 video 标签开启了 autoplay 属性,另一方面又通过上面的代码让视频自动播放,可能是这样重复的设置导致页面出现了 bug。于是按照下面的流程研究,总算解决了:

Google 关键字:DOMException: Failed to load because no supported source was found
参考链接:DOMException: Failed to load because no supported source was found

现在虽然浏览器中能正常播放视频了,但是控制台中总会报错,这里给出了官方的说明:DOMException: The play() request was interrupted

仔细检查代码,发现在两个地方都执行了 video.play() 方法,因为这个方法返回的是 Promise 对象,所以第二次执行该方法时就会报这个错误(说法可能不准确)。

hls.js 的官方文档 还介绍了如何实现流媒体的自动播放。

经过测试,最后发现上面 hls.js 官方文档提供的自动播放的方法在 iOS 下无效,还需要结合前面 playPromise 相关的方法才能实现真正的 iOS 下的自动播放。

此外,还需要判断指定视频当前是否在播放状态,然后只对未开始播放的视频执行 play() 方法,这样才能避免代码报错。

判断视频是否处于播放状态的方法:How to tell if a

另外还需要设置微信,不让它自动进入全屏模式播放视频:微信内置浏览器 如何小窗不全屏播放视频?

playsinlinex5-playsinlinewebkit-playsinline 这三个标签,共同保证能够在浏览器中以小窗方式播放视频。

参考资料

实际代码

<body>
  <video
      id="video"
      controls
      autoplay="autoplay"
      preload="auto"
      playsinline="true"
      x5-playsinline="true"
      webkit-playsinline="true"
      x-webkit-airplay="true"
      data-setup="{}">
  </video>
</body>
<script src="https://cdn.bootcss.com/hls.js/0.9.1/hls.min.js"></script>
<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>

initVideo () {
  let video = document.getElementById('video');
  if(Hls.isSupported()) {
    let hls = new Hls();
    hls.loadSource(this.videoSrc);
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
    });
  }
  else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = this.videoSrc;
    video.addEventListener('loadedmetadata',function() {
      video.play();
    });
  }
}

autoPlay () {
  let video = document.getElementById('video');
  let isPlaying = video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2;
  if (!isPlaying) {
    let playPromise = video.play();
    if (playPromise !== undefined) {
      playPromise
        .then(function () {})
        .catch(function (error) {
          console.log(error);
        });
    }
  }
  document.addEventListener('WeixinJSBridgeReady', function () {
    if (!isPlaying) {
      let playPromise = video.play();
      if (playPromise !== undefined) {
        playPromise
          .then(function () {})
          .catch(function (error) {
            console.log(error);
          });
      }
    }
  }, false);
}

要点总结

移动端多版本的适配比 PC 还要命呐……

安卓点击表单元素页面变形

需求描述

在安卓的浏览器中(独立 APP 及微信这样内置浏览器的 APP),点击输入框(inputtextarea 等)之后,会弹出输入法界面,同时输入法界面会把页面网上挤得变了形。

方案调研

调研过程

Google:js 安卓 输入法 resize,这篇文章 微信下html页面,键盘弹出,页面变形 就对问题原因和解决方法进行了描述。

解决方案

入选方案

应用过程

相关元素都用绝对尺寸如 px 作为单位,不用相对尺寸,页面就不会变形了。

要点总结

CSS 中的百分比单位虽然好用,但也不是万能的。还是那句话,没有最好的,只有最合适的。

JS 复制页面内文本

需求描述

需要在浏览器中用 JS 实现复制文本的功能,不要求是用户直接能看到的文本。点个按钮然后复制指定的文本到系统剪贴板中也是可以的。

实现过程

2018年06月22日更新:在部分安卓手机上,复制出来的文本为空,QQ、独立浏览器均有此情况。但同事又说她当时测试的时候是没问题的。如果不是代码的问题,那就是自己在测试的时候,没能访问更新后的页面。但是安卓版本的 QQ 是没法 刷新内置浏览器中的页面的,这就很恶心了。


Google js copy text,第一个链接就是 Stack Overflow 上的:How do I copy to the clipboard in JavaScript?,回答也是干货满满。

但是!在点开 Stack Overflow 的链接之前,一眼瞅到第四条搜索结果就是 GitHub 上现成的项目:zenorocha/clipboard.js。有现成的东西,就懒得自己从头实现了,嘿嘿。

安装了这个库之后,想在项目中应用,但是由于子组件用到了 vue-js-modal 模态对话框,如果点击事件没有被触发,对话框不会被初始化,就没办法在父组件中复制对话框内的文本了。(后来想想,数据就在父组件中,子组件直接通过 emit 通知父组件,然后让父组件完成复制的工作就可以了嘛。)

于是再上 GitHub 搜索,找到了 Inndy/vue-clipboard2 这个项目,测试了一下,果然 OK 了。

兼容性统计

参考资料

要点总结

简写 document.querySelector()

需求描述

由于良好的兼容性,在自己目前的业务代码中,到处都会用到 document.querySelector() 以及 document.querySelectorAll() 这两个方法。但是总是要手写这两个方法的话就太长了、太花时间了。那么怎样能够简化输入呢?

方案调研

用关键字 js short for document.querySelector Google,第一个链接是 Stack Overflow 上的回答,就是自己想要的结果。

参考资料

关键代码

var $ = document.querySelector.bind(document);
var $$ = document.querySelectorAll.bind(document);

上面这两行代码,是兼容性最好的版本,直接拿来用即可。

Windows 下使用 Node.js 的注意事项

需求描述

在公司的阿里云服务器上,系统是 Windows 2012,发现通过安装程序安装的 Node.js 被卸载之后,就没法使用 nvm 安装的 Node.js 了。

报错内容:cannot find module npm-cli.js

应用过程

最后还是下载安装了独立的 Node.js 安装包才解决问题。

要点总结

vue-cli CSS 背景图片路径设置

需求描述

在 vue-cli 项目中,需要让 CSS 属性 background-image 属性能正常生效。

方案调研

调研过程

Google:vue-cli css background image,第一个是 SegmentFault 社区的讨论帖:vue-cli build后,组件的css background-image路径错误,看了一下回答,给出来的几个方法感觉都不够好。又看到 cnblogs 上的文章:Vue-cli 构建项目 的.vue组件中, scss中添加背景图路径问题,作者用了 GitHub 上找到的解决方案,很简单,也很好用。

入选方案

排除方案

应用过程

修改 build/utils.js 文件中的 ExtractTextPluginoptions 配置:

if (options.extract) {
  return ExtractTextPlugin.extract({
    use: loaders,
    publicPath: '../../', //注意: 此处根据路径, 自动更改
    fallback: 'vue-style-loader'
  })
} else {
  return ['vue-style-loader'].concat(loaders)
}

照着上面的方法修改之后,在 CSS 中这样写即可:background-image: url("./assets/bg.jpg");。这样不管是在开发环境下还是在生产环境下,背景图都可以正常显示了。

要点总结

解决 res.redirect 部分失败的问题

需求描述

部署在域名A下的 Express.js 程序,要用 res.redirect 跳转到域名B下的一个 URL,结果发现跳转后的 URL 的域名还是A,这是什么原因?

问题研究

仔细看了一遍 Express.js 的官方文档,用法没错啊!这又是什么灵异现象?

注意了,每次遇到这种“明明什么地方都是对的,但结果就是不对”的情况,那就说明一定是别的什么地方有问题。

好,冷静下来再来分析分析,为什么 URL 里的域名应该是域名B的,但跳转过去之后却还是域名A的?代码没问题,那还有别的什么地方可能影响这个域名?

啊,想起来了,IIS 里面还配置过 URL 重写规则,会不会是这里作怪?

于是打开 IIS 中配置的 URL 重写规则,仔细看了一遍,不对啊,这个 URL 并没有在重写规则中,那为什么就是跳转不过去呢?不过既然怀疑到这里了,就试一下吧。

于是添加了一条新的 URL 重写规则,强制将这个 URL 由域名A重写到域名B下面,然后再进行测试,咦,这回虽然终于能显示URL对应的页面了,不过怎么样式都没了?又查看了一下在前端显示的URL,怎么还是域名A?

啊,明白了,原来这条规则设置的是“重写”,而不是“重定向”,所以虽然成功显示了 URL 对应的页面,但是因为设置的是“重写”,所以浏览器接收到的 URL 还是域名A,那自然就会去域名A下寻找这个页面所需的静态资源,当然也就找不到了。这个问题就很好解决了,在 IIS 中把“重写”改成“重定向”,再测试一下,终于好了!

后记:虽然这个问题暂时解决了,但还是没有找到真正的原因,为什么这个 URL 必须手动配置一条重定向规则,才能跳转到正确的域名下?This is a question.

全局安装的 npm 包无法使用

需求描述

在 Mac 下全局安装了 vue-cli,但是没法使用,提示 zsh: command not found: vue

方案调研

调研过程

执行 yarn global list,看到有 vue-cli 这个包。但是执行 vue init webpack project 时,却提示 zsh: command not found: vue

用关键字 vue-cli command not found vue Google,第一个链接 bash: vue: command not found 提到了可以用 npm root -g 查看 npm 的全局路径,在 Mac 下的输出结果是 /Users/samsara/.nvm/versions/node/v9.11.1/lib/node_modules

进入该目录,发现没有 vue-cli 文件夹,但是有 nodemon 和 pm2 这两个全局安装的包,猜测 yarn 的包信息不准确。

再根据第一个链接中提到的 echo $PATH 查看环境变量,有 /Users/samsara/.nvm/versions/node/v9.11.1/bin 这么个路径,进到这个文件夹下,发现依然是有 nodemon 和 pm2,没有 vue-cli,基本验证了自己的猜测。

于是用 yarn 先删除然后再安装 vue-cli,发现调用时依然报错。于是回头再看刚才的 Google 结果,第二个链接 vue: command not found in Mac OS · Issue #732 · vuejs/vue-cli · GitHub 中说用 npm 全局安装 vue-cli:npm i vue-cli -g,照着做了之后,这次果然可以正常调用了,真是奇怪的问题。

入选方案

要点总结

和之前 IIS 代理的静态页面 #15 无法稳定访问的问题一样,尝试替代方案,说不定就解决了呢。

vue-cli 引用 CSS 库

需求描述

需要在 vue-cli 项目中引入 normalize.css 这样的库。

方案调研

调研过程

  • Google 关键字: import css library in vue cli

入选方案

应用过程

实际代码:

// src/main.js
import normalize from 'normalize.css'

new Vue({
  normalize
})

要点总结

Ngrok

需求描述

这个程序可以把本机指定端口上运行的 Web 应用映射到公网 IP 上,很方便。

应用过程

  1. 程序安装运行
    去官网下载后,在cmd中用ngrok http 13437启动,访问映射到外网的地址,提示
**Bad Request - Invalid Hostname**
HTTP Error 400. The request hostname is invalid.
  1. 问题解决

用完整的错误文字搜索,第一个方案就解决了:Exposing localhost to the internet via tunneling (using ngrok): HTTP error 400: bad request; invalid hostname

就是用下面的方式调用即可:

ngrok http 13437 -host-header="localhost:13437"

要点总结

IIS 服务器上的 mp4 在 iOS 上无法播放

需求描述

公司阿里云服务器安装的 Windows 系统,网站均用 IIS 管理。不知道哪次做了什么设置之后,网站上的 mp4 视频(绝大多数)没法在 iOS 上播放了,只有一批似乎是用 Adobe 的 FMS 管理的视频可以正常播放,连在 iOS 7 上都可以播放。放在七牛云上的 mp4,在 iOS 7 上也没法播放,看来就是后端设置的问题。既然如此,就研究一下那批可以播放的视频是如何设置的,以后所有视频资源都应用同样的设置就行了。

研究过程

现象描述

经过仔细测试,发现部分视频可以在各版本 iOS 上播放,剩下视频是无法播放的。

分别检查在 iOS 上能播放的视频和不能播放的视频的网络请求和响应(在 PC 端的 Chrome 浏览器上测试的),对比两者不同的部分,发现两者的响应头是相同的,只有请求头略有不同。

能播放的视频:

请求:
Accept-Encoding: identity;q=1, *;q=0
Range: bytes=0-

不能播放的视频:

请求:
Accept-Encoding: gzip, deflate
Access-Control-Allow-Origin: *

但是只有请求头不同,响应头是相同的,难道又是前端的问题?不对啊,前端代码显然是没变过的,但是突然就出现 mp4 无法访问的问题,这说明还是跟服务器的配置有关。那就集中全力检查服务器上 IIS 和 FMS 的配置。

视频编码研究

将能播放的视频和不能播放的视频的编码格式对比了一下,完全相同!这么说不是视频的因素。

参考链接:Unable to play embedded Mp4 videos on iPad or iPhone in Confluence

FMS 研究

又检查 FMS 软件安装目录内的 mp4 视频和网站目录下 applications 文件夹里的视频(FMS 也有 applications 文件夹),发现都是无法播放的视频,说明 FMS 并没有起作用?

为了验证自己的想法,将 FMS 的三个服务全部停止,果然刚才能播放的视频依然能播放,不能播放的视频依然不能播放。

视频播放插件研究

又想试试换个前端插件能不能解决问题,用了最通用的 video.js,依然不行!

视频重编码

用下面的视频对不能播放的视频重编码,依然不行!对可以播放的视频重编码,依然可以播放……不是视频的原因?

$ .\ffmpeg.exe -i .\input.mp4 -profile:v baseline -level 3.0 -framerate 30 -b:v 850k -b:a 150k output.mp4

参考资料:

HLS 方案研究

第二天又理了理思路:mp4 格式的视频在所有安卓设备上都是可以直接查看的。在 iOS 设备上无法直接访问,hls.js 在新的 iOS 上是可以正常使用的,那么在旧的 iOS 上呢?感觉昨天用 iOS 7 测试时,设置好像不大对,于是今天又研究了研究。发现 Safari 一直在无痕模式下,刷新页面用的好像一直是本地缓存?这就尴尬了,退出了无痕模式并清理 Safari 的缓存后再访问页面,这回果然可以看到最新的页面了!接着在 iOS 7 设备上测试 hls.js 播放流媒体,果然可以了!好开心!哈哈哈哈。

想研究研究 hls.js 究竟能不能在 Android 4.4 上应用,看了看 官方文档的兼容列表,说是支持 Android Chrome 34 及以上版本。

查了查 iOS 7 和 Android 4.4 这两个设备的 UserAgent,发现 iOS 7 的设备是 WebKit 537 的内核,Android 4.4 则是 WebKit 534.3 的内核。

查看 Wikipedia 上的 Google Chrome version history 这篇文档可知,534.3 的内核对应的 Chrome 版本是 6.0.472,这可太老了。

hls.js 是通过 MediaSourceHLS 这两种方式实现流媒体的播放的,上网查了查,这两个功能在 WebKit 534.3 上都是不支持的。

具体来说,支持 MediaSource 的 Android WebView 和 Chrome for Android 最低版本号为 33(参考:MediaSource),而版本号 33 的 Android WebView 和 Chrome for Android 是随着 Android 4.4.3 版本发布的(参考:WebView FAQ - What version of Chrome is it based on?)。Android 测试机的系统版本号是 4.4,对应的 WebView 版本号为 30,是不支持 MediaSource 的,也就是没法使用流媒体播放的功能。

查看 百度流量研究院的统计数据,Android 4.4 大版本在国内还是占到了 10% 左右的用户的,这个比例可不小,那么还是需要照顾这部分用户的。

这样一来,安卓设备播放 mp4 格式视频就可以了,iOS 设备用 hls.js 播放流媒体,爽!

2018年6月22日 更新:

后来综合考虑之后,决定页面只提供流媒体视频资源,对于老旧安卓不提供支持,然后用 4.4.2 的三星 Tab 测试,发现又能播放流媒体了,什么鬼???

iOS 无法播放 mp4 的原因

其它参考资料

Windows 系统 Node 开发环境重置

需求描述

在阿里云服务器 ECS 和办公室电脑上,Windows 下的 Node.js 开发环境不一致,就用周五下午的一点时间重置了一下。

应用过程

卸载全局包

将两边全局安装的包都卸载了,包括用 npm 和 Yarn 安装的:

$ npm uninstall —global xxx
$ yarn global remove xxx

更新 nvm

nvm-windows 的项目网站上 可以看到最新版是 1.1.6,服务器上是最新版,本机则是旧版,先把本机的 nvm 更新成最新版。

配置下载源

参考 nvm 设置下载 node 的镜像地址 一文中的方法,为 Windows 下的 nvm 和 npm 配置下载源为淘宝镜像。nvm 安装用的默认设置的话,配置文件在:c:\Users\HeWei\AppData\Roaming\nvm\settings.txt

在文件中增加下面两行,然后重启终端:

node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/

下载最新版 Node.js 和 npm

这样就把 nvm 的下载源更新了,就可以秒速下载 Node.js 和 npm 了:

$ nvm install latest

下载完成之后,还要用 nvm use x.x.x 切换成最新版的 Node.js 和 nvm。

使用最新版 Node.js 和 npm

分别执行 npm -vnode -v,查看所列出的版本,再和 Node.js 官网 上列出的最新版相比,发现本机用的都是最新版,但是服务器上 npm 用的还是旧版 4.2.0

想着用 nvm use 命令先切换到旧版再切换回新版,发现不管用。

又想起来前面刚看到一篇文章:使用 nvm 管理不同版本的 node 与 npm,仔细看了看,发现文章里没有讲如何使用新版的 npm。

那就继续 Google,用 nvm use npm 作为关键字搜索,第二个回答就是 StackOverflow 上的,嗯,应该就没问题,看了里面的每个回答,选了 其中让人比较放心的一个方法

$ nvm use 9.8.0
$ npm install -g [email protected]

安装完成之后,再执行 npm -v 看一下,啊哈,果然搞定了!

要点总结

vue-js-modal 生成的对话框中元素不能立即使用

需求描述

vue-js-modal 组件生成的模态对话框中包含 video 元素,但是发现用该组件的 show 方法显示对话框之后,其中的 video 元素并不是立即可用的。

方案调研

调研过程

用下面的 countTime() 函数检查 video 元素什么时候能生成,发现是立刻生成的。

countTime() {
  console.log('modal shown\n' + new Date());
  let haveVideo = false;
  let count = 0;
  while (haveVideo) {
    const vdo = document.querySelectorAll('#video');
    haveVideo = vdo.length ? 1 : false;
    count++;
  }
  console.log('video gotten' + new Date() + '\n' + count);
},

但是在浏览器中调试从 this.$modal.show('video-modal') 这行语句开始往后的部分,发现似乎需要了解 Vue 的执行原理,因为调试的时候,是在执行完了 macroTask 之类的函数之后,video 元素才可用的。

this.$modal.show('video-modal') 之后执行了下面的代码,video 还是不可用。

this.$nextTick(() => {
  this.loadVideo();
});

只有用 setTimeout 设置一定的延时,video 标签才可用:

setTimeout(() => {
  this.loadVideo();
}, 100);

入选方案

应用过程

记录应用入选方案的过程中所踩的坑,以及其它值得记录的内容。

要点总结

以列表形式记录本次需求实现过程中的要点:

  • 要点 1
  • 要点 2
  • 要点 3

编写 CNZZ 工具函数

需求描述

自己之前只是以最原始的方式给各个网站的前端页面添加了 CNZZ 统计代码,还有很多可以改进的地方,初步罗列如下:

  • 在页面中隐藏 CNZZ 生成的 HTML 元素
  • 加载完统计代码之后,再通过 JS 往页面中添加内部样式(和上面是同一个需求?)
  • 根据当前域名,加载对应的统计代码
  • 编写成 NPM 包,一次编写,到处使用

关于上面列出的第一项,CNZZ 统计代码用的是下面这种形式,最终会生成文字链接。但是由于 Chrome 的安全设置,会在浏览器控制台中出现第二段的提示代码,也就是说只有统计功能,但并不会显示文字连接。为了安全起见,还是需要在 CSS 中设置好样式,隐藏 CNZZ 统计内容。

<script src="https://s11.cnzz.com/z_stat.php?id=1260367303&web_id=1260367303" language="JavaScript"></script>` 
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.

方案调研

Google Analytics 与国内各家统计服务的对比

CNZZ 统计原理

自己编写 JS 插件 / NPM 包

现成的库/组件

应用过程

参考了 import和export用法总结raychenfj/vue-uweb,把函数写在 addcnzz.js 中:

const AddCnzz = () => {
  const hostName = window.location.hostname;
  const sites = [
    {
      domain: 'abc',
      id: '1234567890',
    },
    {
      domain: 'def',
      id: '9876543210',
    },
  ];
  const site = sites.find(ele => hostName.match(ele.domain)) || null;
  if (site) {
    const script = document.createElement('script');
    script.src = `https://s11.cnzz.com/z_stat.php?id=${site.id}&web_id=${site.id}`;
    document.body.appendChild(script);
  }
}

export default AddCnzz;

然后在 vue-cli 中用 import 指令引用它,最后在需要的地方,调用 AddCnzz() 函数就可以了。

要点总结

写一个自己的工具函数其实很简单嘛!

vue-cli 项目为开发/生产环境引用不同的外部 JS 库

需求描述

前面实现了 vue-cli 项目中引用 CDN 上的 JS/CSS 库,那么另一个问题就随之而来了:开发环境和生产环境,需要引用同一个 JS 库的不同版本,该如何实现?

方案调研

调研过程

之前研究 jantimon/html-webpack-plugin 这个 webpack 插件,看到官方文档中介绍用户可以自定义 index.html 或者页面模板的内容,从而尽量满足用户的个性化需求:

plugins: [
  new HtmlWebpackPlugin({
    title: 'Custom template',
    template: 'index.html'
  })
]
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
  </body>
</html>

既然有这个功能,那么是不是可以编辑 webpack 的配置文件,从而实现在开发/生产环境下引用不同的 JS 库的需求呢?说干就干。

入选方案

应用过程

因为要在开发环境下和生产环境下分别引用不同的 JS 库,那么就需要先了解一下 webpack 是如何为两种环境传入不同的设置的。因为已经用了相当一段时间的 vue-cli 了,所以知道整体的配置流程,先在 config 目录下新建 cdn.js,内容如下:

// config/cdn.js
'use strict';

module.exports = {
  dev: {
+    vue: 'https://cdn.bootcss.com/vue/2.5.16/vue.js',
  },
  build: {
+    vue: 'https://cdn.bootcss.com/vue/2.5.16/vue.min.js',
  }
}

添加了开发环境和生产环境下要用的 vue 的链接之后,再设置这两种环境下的 webpack 编译配置:也就是编辑 build/webpack.dev.conf.jsbuild/webpack.prod.conf.jshtml-webpack-plugin 插件的设置,这样才能在 index.html 中按需引用 JS 库:

// build/webpack.dev.conf.js
const cdn = require('../config/cdn');
...
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
+       vueSrc: config.dev.vue,
      inject: true
    }),
  ]
// build/webpack.prod.conf.js
const cdn = require('../config/cdn');
...
  plugins: [
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
+       vueSrc: config.build.vue,
      inject: true,
    }),
  ]

最后再编辑 index.html,就可以在两种环境下分别引入不同版本的 JS 库了:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>pages</title>
+     <script src="<%= htmlWebpackPlugin.options.vueSrc %>"></script>
  </head>
</html>

这样一来,webpack 就会根据当前设置的环境,自动为 index.html 引入对应的 JS 库,爽!

要点总结

之前总是觉得 webpack 非常复杂,现在再看,其实没那么可怕,把需要解决的问题一个个搞定,最后就会发现还是很简单的。

vue-js-modal 正确用法

需求描述

Vue 组件化开发需要用到模态对话框 Modal,捣鼓了半天才成功调用,真是折腾。

方案调研

调研过程

euvl/vue-js-modal 官网看 README.md 研究组件的正确用法,发现官方文档并不是完全正确的,经过一番折腾,才成功调用组件。

应用过程

Modal 定义

定义 Modal 时,按照如下格式,将代码保存为 VideoModal.vue 文件:

<template>
  <modal
    name="video-modal">
    <div>This is a modal!</div>
  </modal>
</template>
<script>
export default {
  name: 'VideoModal',
};
</script>

template 中的 name 属性值要用短横线分隔命名,script 中的 name 属性值则要用驼峰命名。

Modal 调用

调用 Modal 时,先在调用该组件的 .vue 文件中的 script 代码块内,以下面的方式引入组件:

import VideoModal from './components/VideoModal';

export default {
  name: 'App',
  components: {
    VideoModal,
  }
};

然后在 template 中,以下面的格式插入组件,即用短横线分隔命名的形式调用:

<video-modal/>

最后,在需要显示/隐藏该组件时,用下面的方式实现:

// 在 script 中
this.$modal.show('video-modal');
// 官方文档也列出了下面这种写法,但是实际应用的时候是无效的
this.$modal.show('VideoModal');
// 在 template 中
<div @click="$modal.hide('video-modal')"></div>

要点总结

即使是官方文档,也需要“辩证”地看,有时候的确是文档写错了,就需要自己能够找准出现问题的原因。

vue-cli 项目中引用 CDN 上的 JS/CSS 库

需求描述

目前在 vue-cli 中引入库的方式,是通过 npm 安装,然后 webpack 打包。用起来的确很方便,但是这样一来,打包后的文件体积就会变得很大,vendor.js 的体积普遍在几百 K 上下,而这几百 K 完全可以通过公共 CDN 来分流,毕竟服务器的带宽是有限的。

方案调研

调研过程

vue-cli use cdn 作为关键字搜索,网上给出的建议 How to include a CDN to VueJS CLI without NPM or Webpack?vue项目优化--使用CDN和Gzip,说是可以在 index.html 中静态引入,或者在具体的组件中用 JS 动态引入。这两种方式引入的库都全局可用,自己做的页面都是小项目,不用担心全局变量污染的情况,那就试试吧。

入选方案

应用过程

  • 先用 yarn 把安装好的 vue 删除:yarn remove vue
  • 然后在 index.html 中添加 Vue 的 CDN 链接,这里用的是 bootcdn 提供的服务。
  • 如果启用了 ESLint,会提示没有定义 Vue,就需要编辑 .eslintrc.js,加上 'globals': { 'Vue': false } 这项设置,注意,如果开发环境已启动,需要重启开发环境,这项设置才能生效。
  • 告诉 webpack 不要打包 Vue:编辑 build\webpack.base.conf.js,最后加上 externals: { vue: 'Vue', } 这项设置,属性值 Vue 是模块暴露出来的全局名称。
  • 最后再把 main.js 中对 Vue 的引用删除:import Vue from 'vue' 这句如果不删除,开发环境是一直会报错的。

要点总结

最开始想的是把普通的库通过 CDN 的方式引入,不过最后直接来了个大的,把 Vue 也从外部引入了,测试了一下编译后的 js 文件,体积果然小了很多,赞!

iOS 页面滚动光标不跟随

需求描述

编写的网页出现了一个小 bug,滚动页面时,input 元素中的光标没有随着元素一起移动。

方案调研

调研过程

先是 Google: js after scroll cursor position,没有找到有用的结果。

因为该问题只在 iOS 上出现,所以调整 Google 关键字为:js iOS cursor page scroll。在 Blinking caret/cursor doesn't scroll with it's Input text form element #178 中给出了解决方案:页面滚动前让元素失去焦点,滚动后恢复焦点。

继续搜索,这次尝试中文关键字:iOS 表单元素 页面滚动 光标,给出的结果和英文的一样,都是滚动前失去焦点,滚动后恢复焦点。

入选方案

应用过程

示例代码:

let element = document.querySelector('#app');
element.blur();
element.scrollTop = 100;
element.focus();

要点总结

用对关键字也是很重要的搜索能力。

生成不重复的随机字符串

需求描述

业务上的一个需求,要批量生成不重复的随机字符串。

方案调研

调研过程

先尝试 Google js generate many random strings,第一条结果就是 Stack Overflow 的,点进去看看:

Generate random string/characters in JavaScript

回答里面,有说用 Math.random() 方法的,有说用 Node.js 的 crypto 这个库的,还有说用 uuid 之类的库的。

看了这篇问答拿不定主意,于是再 Google node crypto randombytes duplicate,第一条结果是 node-hat 的 issue,里面有个高分回答也建议用 Node.js 的 crypto 这个库中的 randomBytes 方法。

在 Google 出来的第二条结果 Secure random values (in Node.js) 中,也是既有人推荐用 Node.js 的 crypto 这个库(更加原生),也有人推荐用 uuid 之类的库(更能确保唯一性,但其实没啥区别)。

几个链接看下来,最后自己的结论就是:Node.js 的 crypto,和 uuid 这类的库都可以满足自己的需求。

另外,uuid 这个库只能以一种固定格式 1c572360-faca-11e7-83ee-9d836d45ff41 生成随机字符串,而 nanoid 这个库则可以自定义所生成的字符串的长度,可以自定义字符串中包含哪些字符,在自定义性上更胜一筹。

入选方案

  • nanoid:可以自定义所生成的字符串的长度,可以自定义字符串中包含哪些字符,在自定义性上更胜一筹。

排除方案

  • uuid:只能以一种固定格式 1c572360-faca-11e7-83ee-9d836d45ff41 生成随机字符串。

应用过程

略。

要点总结

重要的事情有三点:

  • Google,不要百度。
  • 可以的话,尽量用英文作为关键字搜索。
  • Stack Overflow 上的讨论质量往往都很高,建议优先参考。

最后还是想说,GitHub 真是个宝库啊。

理解 IIS 的 URL 重写

需求描述

对于 IIS 中的 URL 重写功能,自己一直只是停留在会用的水平,并不了解其中的原理。

目前用 vue-cli 编写的页面,有的是用 URL 重写 + 虚拟目录进行代理,有的是用 URL 重写 + Nginx 进行代理,部分页面时不时地会出现无法访问的情况,查看日志发现网络请求根本就没有到达后端,这个问题就让人很头疼了,找不到病因,没法对症下药。

出于上面的考虑,就想着好好研究一下 IIS 的 URL 重写功能,看看能不能从中摸到上面症状的蛛丝马迹。

方案调研

应用过程

要点总结

前端页面播放流媒体

需求描述

为了实现流媒体播放的效果,不仅需要用 FFmpeg 把视频切割成 ts 文件和对应的 m3u8 播放列表,前端还需要用 hls.js 这个库来播放。这个库的兼容性比 video.js 好很多,PC 和移动端都不报错;video.js 总是在页面第一次刷新出来的时候报错,再刷新一下才能播放视频。

方案调研

调研过程

暂无

入选方案

以列表形式记录入选的方案。

应用过程

vue-cli 集成

vue-cli 中引入 hls.js 的正确姿势:

import hlsjs from 'hls.js';
...
window.Hls = hlsjs;

实际调用

在前端页面中的实际调用代码,使用 hls.js 官方文档提供的代码 即可:

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video"></video>
<script>
  var video = document.getElementById('video');
  if(Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 }
  else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = 'https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8';
    video.addEventListener('loadedmetadata',function() {
      video.play();
    });
  }
</script>

错误排查 1

有时候控制台会报下面的错误:

Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause()

上网查了一下,是 play()pause() 这两个方法之间起了冲突。解决方法在这里:How to prevent “The play() request was interrupted by a call to pause()” error?

关键代码:

var isPlaying = video.currentTime > 0 && !video.paused && !video.ended 
    && video.readyState > 2;

if (!isPlaying) {
  video.play();
}

错误排查 2

控制台又会报下面的错误:

Uncaught DOMException: Failed to read the 'buffered' property from 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
    at e.onSBUpdateEnd

查看报错的地方,是 hls.js,查看对应的 m3u8ts,都是可以在浏览器中直接正常访问的,说明是别的地方的问题。

后来经过排查,发现是视频本身的问题,用格式工厂先预处理一下,然后再用 FFmpeg 切片之后就没问题了。

要点总结

相关 issue:

  • 常规视频处理成流媒体 #22

各版本 Git 安装程序的区别

需求描述

在群里看到有人提到了 msysGit,上网查了查,发现又有 Git for Windows,又有 Git,分不清之间的区别,于是就搜了搜它们有什么不同。

方案调研

调研过程

先以 msysGit 作为关键字搜索,在官方 GitHub Repo 上,看到有说明文字,说 msysGit 是用于在 Windows 下编译 Git 的完整环境,编译之后的安装包就是 Git for Windows

那么这个 Git for Windows 究竟是不是官方的安装程序呢?又用 git vs git for windows 作为关键字搜索,在 Differences between Git-scm, msysGit & Git for Windows 这篇文章中看到了说明,官方的 Git 是在 git-scm 这个网站下载的,而 Git for Windows 则在 https://gitforwindows.org/ 这个网站上下载。这下就清楚了。

最终结果

应用过程

要点总结

网页导出数据至 Excel

需求描述

将一组数据导出到 Excel 中,xlsx 或者 CSV 格式均可,数据中包含数字,保证导出的结果正确即可。

因为 Excel 默认会将数字显示为科学计数法的形式,即数字 10000000000000 会显示为 1E+13 ,所以要想办法解决这个问题。

方案调研

调研过程

先 Google js export to excel ,在第一个链接 JS导出excel插件总结 中列出了三种方案,三个项目 excellentexportexceljsjs-xlsx 的 Star 数量级分别在 600、2500 和 10000,那就用 js-xlsx 这个库吧。

入选方案

  • js-xlsx:Star 数过万,应该是最靠谱的。

排除方案

应用过程

接着用 js-xlsx save array 作为关键字 Google,第一个结果指向 js-xlsx 在 GitHub 的官网,第二个结果则是该项目在 GitHub 上的 issue:How to simply export a Worksheet to xlsx? · Issue #817 · SheetJS/js-xlsx,很可能会看到示例代码,点进去看看。

第一条回复 就给出了导出数组到 Excel 中的方法:

/* external references:
  - https://rawgit.com/SheetJS/js-xlsx/master/dist/xlsx.full.min.js
  - https://rawgit.com/eligrey/FileSaver.js/master/FileSaver.js
*/
/* original data */
var data = [
    {"name":"John", "city": "Seattle"},
    {"name":"Mike", "city": "Los Angeles"},
    {"name":"Zach", "city": "New York"}
];

/* this line is only needed if you are not adding a script tag reference */
if(typeof XLSX == 'undefined') XLSX = require('xlsx');

/* make the worksheet */
var ws = XLSX.utils.json_to_sheet(data);

/* add to workbook */
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "People");

/* write workbook (use type 'binary') */
var wbout = XLSX.write(wb, {bookType:'xlsx', type:'binary'});

/* generate a download */
function s2ab(s) {
  var buf = new ArrayBuffer(s.length);
  var view = new Uint8Array(buf);
  for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
  return buf;
}

saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), "sheetjs.xlsx");

自己根据业务需求修改后的代码如下:

import FileSaver from 'file-saver';
import XLSX from 'xlsx';

// 只取对象中指定的三个属性
const getSerial = R.map(ele => R.pick(['serial', 'user', 'valid'])(ele));
const ws = XLSX.utils.json_to_sheet(getSerial(this.detail));
const wb = XLSX.utils.book_new();

// 最后一个参数定义工作表的名称
XLSX.utils.book_append_sheet(wb, ws, '激活码');

// 写入 xlsx 文件要用 binary 类型
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' });

/* generate a download */
function s2ab(s) {
  const buf = new ArrayBuffer(s.length);
  let view = new Uint8Array(buf);
  for (let i = 0; i !== s.length; i++) {
    view[i] = s.charCodeAt(i) & 0xFF;
  }
  return buf;
}

FileSaver
  .saveAs(
    new Blob([s2ab(wbout)], {
      type: 'application/octet-stream',
    }),
    '激活码.xlsx');

要点总结

  • 常用的 JS 库,在官网或者 StackOverflow 之类的地方往往都能找到实际用例,搜索一下然后照着自己的需求改改就 OK 了,善用搜索能节省很多时间。

前端页面文本搜索及高亮

需求描述

同事之前做的电子书实现了文本搜索的功能,自己能不能实现得更优雅一些呢?之前的搜索方案,是获取后端数据库的查询结果,这样许多人同时搜索的话,服务器的压力就会很大。

自己想了想,这个搜索功能和其它业务没有耦合,那么就完全可以做成纯前端的搜索。

方案调研

两大搜索方向:官方文档 + 实例教程

关键字清单

入选方案

mark.js

排除方案

  • 2018-05-17:又看了一下之前研究的几个方向,中文不需要分词,否则一旦分词的结果和搜索的关键字刚好错位那就麻烦了。那么 lunr.js 和结巴分词就都不考虑了。

  • 2018-05-15:又看了一下 Algolia 的 收费方案,如果要上架一批电子书的话,显然很容易就会超出免费版的用量,那看来还是得找能够架设在自己服务器上的免费方案。

  • search-index:不支持中文搜索,demo 也很潦草,不好用。

  • lunr.js:中文分词没有很好的插件。

  • 结巴分词:纯前端搜索,无需分词。

  • Algolia:免费版的功能有限,无法实际应用。

应用过程

要点总结

常规视频处理成流媒体

需求描述

浏览器加载视频资源时,有时会等待资源文件完全下载完成之后才开始播放。如果在移动端播放体积较大的视频文件(音频文件同理),则可能需要等待较长的时间,这样会严重影响用户体验。

为了改善用户体验,就需要把普通的视频处理成体积很小的多个视频切片及对应的索引文件,用户端选择播放指定时刻的视频,只需要下载很小的一个文件之后即可开始播放。

方案调研

调研过程

用 FFmpeg 编译生成 m3u8 视频列表文件和对应的 ts 视频之后,就可以通过前端的 JS 库播放视频。因为编译生成的每个文件体积很小,用户点击指定时刻的视频,只会下载包含这个时刻的一小段视频,这样就可以实现流畅的点播效果了。

在 Windows 的后端部分,只需在 IIS 中为 m3u8 和 ts 配置 MIME 即可。

参考资料

应用过程

视频预处理

拿到的视频文件,先用格式工厂处理成 MP4 格式的标准的视频资源:在 输出配置 中,选择 AVC 系列的预设配置,保证视频采用 AVC(H264) 编码,比特率使用对应的预设配置即可,比如 AVC 1080p 的比特率设置的就是 3072。音频编码用默认的 AAC 即可,采样率用默认的 44100Hz,比特率用默认的 128

因为拿到的视频文件可能有各种编码,曾经处理过 MPEG2 编码的视频,用 FFmpeg 直接处理后,无法在浏览器中播放,花了很长时间才确定问题的原因处在视频编码上。

MP4 切片

用格式工厂预处理之后,再用 FFmpeg 将视频处理成 ts 切片及对应的 m3u8 索引列表即可。

$ ffmpeg.exe -i 01.mp4 -c:v libx264 -c:a copy -f hls -g 600 -hls_list_size 0 video.m3u8

注意:如果输出文件名在输入文件名之后,有时会导致 hls_list_size 参数无效,fuck!

参数解释:

  • -i :后面跟着需要编译的视频的文件名
  • -hls_list_size :m3u8 播放列表文件中记录的 ts 文件个数,默认为 5,需要改成 0,才能完整保存所有编译出来的文件
  • -g :关键帧的间隔?但是单位并不是秒

批量处理

cls
# 设定 FFmpeg 可执行文件的路径
Set-Location -Path "d:\Software\FFmepg\bin\"
# 设定保存所需处理视频路径的文件所在的位置
$file = "e:\1.txt"
try {
    # 尝试读取文件,失败的话则抛出异常
    $Content = Get-Content $file -ErrorAction Stop
    # 遍历文件中的每一行
    foreach ($Line in $Content) {
        # 每一行用英文半角逗号分隔,读取分隔出来的内容并保存至对应的变量中
        $Path, $Count, $Folder = $Line -split ','
        # 合并变量得到视频文件所在的完整目录
        $VideoFolder = Join-Path $Path $Folder
        # 检查路径是否存在
        if (Test-Path -Path $VideoFolder) {
            # 保存该路径下所有视频文件的文件名
            $VideoNames = Get-ChildItem -Path $VideoFolder -File -Name
            # 遍历每个文件名
            foreach ($VideoName in $VideoNames) {
                # 获取视频文件的完整路径
                $VideoPath = Join-Path $VideoFolder $VideoName
                # 获取视频文件在数组中的索引
                $Index = $VideoNames.IndexOf($VideoName)
                # FFmpeg 处理后生成的文件放在 v1/v2 这样的子文件夹中
                $SubFolder = "v" + ([int]$Index + 1)
                # 设定子文件夹的绝对路径
                $SubFolder = Join-Path $VideoFolder $SubFolder
                # 强制新建文件夹,并且指定不输出结果
                New-Item -ItemType directory -Path $SubFolder -Force | Out-Null
                # 设定 m3u8 文件的绝对路径
                $StreamPath = $SubFolder + "\video.m3u8"
                # 以红色输出文件的绝对路径字符串
                Write-Host -ForegroundColor Red $StreamPath
                # 设定 FFmpeg 执行时所需的参数
                $ArgumentList = '-i "{0}" -c:v libx264 -c:a copy -f hls -g 600 -hls_list_size 0 "{1}"' -f $VideoPath, $StreamPath
                # 以绿色输出执行参数字符串
                Write-Host -ForegroundColor Green -Object $ArgumentList
                # 调用 FFmpeg 开始处理视频,等当前处理进程结束之后,才继续执行后面的代码
                Start-Process -FilePath .\ffmpeg.exe -ArgumentList $ArgumentList -Wait -NoNewWindow
            }
        } else {
            Throw "Path not exist!"
        }
        # 输出换行符,便于查看每次处理的结果
        "`n"
    }
} catch {
    # 输出异常的具体信息
    write-host $_.Exception.Message
    return ""
}

限制单个 ts 文件的体积

Google ffmpeg hls limit ts size,在 [feature request] [hls] hls_flags to limit single_file size 中提到了 -hls_segment_size 这个参数。

再 Google ffmpeg hls_segment_size,在 Ffmpeg hls live streaming - How to generate shorter segments 中提到了应该用 -g 这个参数。把这个参数设置为 30 之后,大部分 ts 文件的时间长度都在 1s 左右。网上还看到 -g 这个参数是设置 keyframe interval 的。

于是再 Google ffmpeg hls keyframe interval value,在 What is the CORRECT way to fix keyframes in FFMPEG for DASH? 这里有详细的讨论。

自己测试 -g 参数不同的值,对于 fps 为 30 的视频,结果如下:

  • -g 设置为 30,视频的时长基本在 1s 左右
  • -g 设置为 90,视频的时长基本在 2s 左右
  • -g 设置为 100 和 120,视频的时长基本在 3s 左右

这么一看,想要设置视频时长,并不能简单地用 fps 乘以时长就作为 -g 的值。

要点总结

关联内容:

  • 前端页面播放流媒体 #24

PDF 图书内容转换

需求描述

现有的图书资源以 PDF 格式为主,要使用这些资源,就需要先把它们转换成可用的格式。

转换成 XML

PDF 电子书可以通过 Adobe Acrobat 导出为 XML 1.0 格式的文档,XML 文档整体结构如下。

<?xml version="1.0" encoding="UTF-8" ?>
<TaggedPDF-doc>
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
  这里是文档元信息,如创建/修改时间,标题、作者等等
<?xpacket end="w"?>
<?xpacket end='r'?>
  这里是文档内容
</TaggedPDF-doc>

但是有个问题:在文档内容中,出现 Ruby 标签对的地方,标签对内的 RB 标签和 RT 标签在 XML 文档里的顺序,和在 PDF 文档里的顺序是相反的。而且所有出现 Ruby 标签的地方,都是汉字在前面的 RB 标签中,单个的左/右双引号在 RT 标签中。

转换成 TXT

也可以直接把 PDF 导出成 TXT 格式的纯文本文档并复制到浏览器中,然后在浏览器中用自定义的编辑器给每一段文字刷上对应的格式,再增加插入图片、视频之类的功能(需要在服务器上搭建文件服务,Nginx 也要做对应的配置,不是个小工程),编辑完成的文档导入 MongoDB 即可。

方案调研

PDF 转换

  • modesty/pdf2json
  • 手动在本地把 PDF 导出成 TXT 格式的纯文本文档

文本编辑

  • GitHub: text editor
    • 更具体一点:markdown 编辑器?带 UI 的更好,方便使用。
    • 继续:还需要考虑如何将页面中设置好格式的富文本传入后端数据库,比如标题、段落、引用等类型的内容,都要设置对应的标签,这个标签是包在文本的外面放在同一个字段中,还是说放在不同的字段中,都是需要好好考虑的事情。
    • 富文本中嵌入的图片、音频、视频等资源,还需要传入下面的 seaweedfs 中,传入之后会生成资源文件的链接,这个链接也要更新到上面编辑的富文本中,这么一看,工作量很大啊。

文件服务

  • chrislusf/seaweedfs
    • 不只是文件服务搭建起来就行,应该还需要 Nginx 进行相关配置才能正常使用。

XML to HTML

应用过程

要点总结

Puppeteer 批量自动生成并保存二维码

需求描述

出于业务需要,经常要批量生成二维码。之前都是上 草料二维码 这个网站,手动生成一堆二维码,效率低,容易出错。每次做这件事的时候,都是一边生成二维码,一边在想:怎样能把这个操作自动化了呢?

方案调研

想起来之前在 GitHub 上看过一个项目,好像是什么无头浏览器(headless browser),查了查自己的 GitHub 收藏列表,果然找到了,就是 GoogleChrome/puppeteer 这个项目。

应用过程

流程梳理

梳理了一下生成二维码的一系列操作,分为以下几个步骤:

  1. 打开 草料网址二维码生成器 这个页面
  2. 等待页面加载完毕
  3. 在文本框中输入需要生成二维码的网址
  4. 点击按钮『生成二维码』
  5. 等待页面自动跳转
  6. 下载页面中显示的二维码图片

这么一看,整个流程是不是很简单?的确如此,不过里面有一个大坑,后面会讲到。

代码实战

打开页面

首先试试用 puppeteer 打开网页并截图,这个在项目官网上是有现成的代码的:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://cli.im/url');
  await page.screenshot({
    path: 'cli.png'
  });

  await browser.close();
})();

执行代码之后,查看生成的截图,虽然把网页截下来了,但是页面排版都错位了,有些内容没有显示出来。

01

参考资料:

查看官方文档,说页面尺寸默认设置为 800px * 600px,如果需要改变这个尺寸,就要用 Page.setViewport() 这个方法。在 Chrome 中打开空白标签页,查看页面尺寸为 1920px * 935px,那就设置成这个尺寸吧。

  ...
  await page.goto('https://cli.im/url');
  await page.setViewport({
    width: 1920,
    height: 935,
  });
  await page.screenshot({
    path: 'cli.png'
  });
  ...

这回再查看生成的截图,嗯,网页内容完整显示了,那就继续下一步。

02

参考资料:

文本框输入内容

页面加载完成之后,就需要在文本框中输入需要生成二维码的网址了。虽然在上一步的操作中,截图显示页面的内容都加载完成了。但是保险起见,还是需要等待文本框确实可用之后,再输入内容。

那么就需要先调用 page.waitForSelector() 这个方法,等待元素加载完毕。然后再调用 page.type() 方法,在文本框中输入网址:

  ...
  // 文本框元素的 id 属性值为 url_content
  await page.waitForSelector('#url_content');
  await page.type(
    '#url_content',
    'http://www.baidu.com/', {
    delay: 50,
  });

执行代码,查看生成的截图,嗯,文本框中成功输入内容了,继续下一步。

03

参考资料:

点击按钮生成二维码

接下来就该模拟点击按钮的操作,让网页生成二维码了。这个操作,用 page.click() 就可以实现了。

这里要和上一步一样,等待按钮确实可用之后,再去点击它。

  ...
  await page.waitForSelector('#click-create');
  await page.click('#click-create', {
    delay: 50,
  });

查看这一步操作完成后的截图,会发现页面停在“正在生成二维码”这里了,本应显示二维码图片的区域,显示的是一个表示“加载中”的图片,这是什么情况?

在浏览器中执行同样的操作,发现点击按钮之后,网站会跳转到新的页面,然后在新的页面上显示二维码。猜测 puppeteer 本身并不会自动跟随页面的跳转,那该怎么办咧?

上网用 puppeteer follow redirectpuppeteer 自动跳转 之类的关键字搜索,都没有找到可用的结果。

于是又去翻官方 API 文档,看了看文档左侧的分类列表,觉得这个功能应该归在 Page 这个类的下面,于是就挨个浏览这个类提供的方法,咦,page.waitForNavigation() 这个方法很可能就是正在找的东西,把它加到代码里试试看。

   ...
  await page.waitForNavigation();

05

啊哈,就是它了!这回可以看到生成的二维码了。其实之前自己在写代码的时候并没有这么顺利,差不多花了半天的时间才定位到问题的原因,真是台上一分钟,台下十年功呐。

参考资料:

下载二维码图片

终于到了最后一步了:下载二维码图片。先用 Chrome 的开发者工具查看图片这个元素的信息,能够看到这个元素的 idqrimage,并且用 document.querySelector('#qrimage') 这条语句确定了当前页面中没有重复 id 的元素。

那么,就根据 SAVING IMAGES FROM A HEADLESS BROWSER 这篇文章中的方法,将图片保存至本地。

  const qrcode = await page.$('#qrimage');
  await qrcode.screenshot({
    path: 'screenshot.png',
  });

在本机查看生成的截图,啊哈,保存成功!

为了确保代码没有问题,再用 草料二维码扫描器 检查刚才生成的二维码,嗯,的确没问题,大功告成!

参考资料:

二维码批量生成及下载

照着前面的方法一步步操作,已经可以把二维码图片下载到本地了。但是,研究前面这些内容,最终是为了实现二维码批量生成及下载。

查看前面『流程梳理』这一小节的内容,再思考二维码批量生成及下载的整个流程,可以确定,每一次生成二维码的过程,会有变化的,只是输入的网址,和保存至本地的图片的文件名。

这样一来,只需要在上面的代码外面套一层循环,每次在循环体中传入网址和图片文件名这两个变量,就可以满足自己的需求了。而且因为需要批量生成的文件名是很规则的,所以这个实现起来也很简单。

未完待续……

vue-cli 引入外部 JS 库的两种方式

需求描述

在业务开发中,需要将各种 JS 库引入 vue-cli 中。有两种方式可以实现需求,一种是在 main.js 中全局引入,另一种则是在指定的组件中局部引入。

方案调研

调研过程

因为需要在 vue-cli 中引入 hls.js 这个库,所以用 vue/vue-cli + hls/hls.js 进行 Google,找到了解决方案。

入选方案

应用过程

全局引用

// main.js
// 先引入这个库
import hlsjs from 'hls.js';

// 然后挂载到 Vue 实例的原型上
Object.defineProperty(Vue.prototype, 'Hls', { value: hlsjs });
// 或
Vue.prototype.Hls = hlsjs;

// 在组件中使用时
// App.vue
if (this.Hls.isSupported()) { ... }

局部引用

// App.vue
import hlsjs from 'hls.js';
window.Hls = hlsjs;

if (Hls.isSupported()) { ... }

注意

所引用的外部库,不要求非得在当前项目所在的目录之内,完全可以是项目之外的某个文件。不过如果是不同分区上的文件(对于 Windows 系统来说),就不知道了。而且把代码放到两个分区,这个习惯不大好吧。

// e:\codebase\utils\addcnzz.js
const AddCnzz = () => {
  const hostName = window.location.hostname;
  const sites = [
    {
      domain: 'abc',
      id: '123',
    },
    {
      domain: 'def',
      id: '456',
    },
  ];
  const site = sites.find(ele => hostName.match(ele.domain)) || null;
  if (site) {
    const script = document.createElement('script');
    script.src = `https://s11.cnzz.com/z_stat.php?id=${site.id}&web_id=${site.id}`;
    document.body.appendChild(script);
  }
}

export default AddCnzz;
// App.vue
import AddCnzz from '../codebase/utils/addcnzz';

created() {
  AddCnzz();
}

要点总结

如果所引入的 JS 库需要在各个组件中都用到,那么建议用全局引用的方式。否则就用局部引用的方式,而且局部引用之后,可以直接调用该 JS 库的实例,不用加 this 关键字。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.