雅荷心语博客
雅荷心语
心之所向便是光
  • 首页
  • 前端之旅
  • 后端之路
  • 软件工具
  • 心灵鸡汤
  • 心路历程
  • 视频资料
  • 关于我们
    • 关于我
    • 关于我
  • 微信平台
    • 业务合作
  • 首页
  • 前端之旅
  • 后端之路
  • 软件工具
  • 心灵鸡汤
  • 心路历程
  • 视频资料
  • 关于我们
    • 关于我
    • 关于我
  • 微信平台
    • 业务合作
  • 关注本站
    • 微信
    • 微博
    • 腾讯微博
    • Twitter
    • Facebook
    • RSS订阅
Hi, 请登录     我要注册     找回密码
分享到:

前端之旅

去分类设置中添加分类描述吧

tinygo 安装及编译 wasm 笔记

2023-01-17admin阅读(51)评论(0)

上一篇文件: go 语言编译wasm 在浏览器端实现cbc模式加解密

为什么要使用 tinygo 安装及编译wasm? 上一篇文章写了使用 go 语言来编译 wasm, 但是编译出来文件有些大, 所以这次主要使用 tinygo 来编译, 可以编译出较小的 wasm 文件

先使用 brew 安装

brew tap tinygo-org/tools
brew install tinygo

tinygo version
如果安装失败, 可以直接下载代码包使用
安装方法:
https://tinygo.org/getting-started/install/macos/

注意: 安装成功后需要自己手动设置环境变量

设置成功后开始编译:

tinygo build -o main.wasm -target wasm ./main.go

报错:

error: could not find wasm-opt, set the WASMOPT environment variable to override

没有找到 wasm-opt

所以我们还需要安装

brew install binaryen

安装成功后继续编译: 报错

/usr/local/bin/wasm-opt: incompatible wasm-opt (need 102 or newer)

查看当前版本:
wasm-opt –version
wasm-opt version 101

版本太低了, 需要 102 以上

升级

brew 安装的版本太低, 手动编译:

https://github.com/WebAssembly/binaryen

这里需要注意:

这个仓库里面有个子仓库, 需要都 clone 下来才可以编译成功:

英文介绍如下:

Binaryen uses git submodules (at time of writing just for gtest), so before you build you will have to initialize the submodules:

git submodule init
git submodule update
After that you can build with CMake:

cmake . && make

下载编译

编译成功后, 把 bin 目录加入环境变量中,

重新打包:

GOOS=js GOARCH=wasm tinygo build -o static/main.wasm

打包出来的 wasm 只有 300kb

启动

报错: TypeError: WebAssembly.instantiate(): Import #0 module=”wasi_snapshot_preview1″ error: module is not

查找到原因:

要想在浏览器中跑 main.wasm ,首先需要 JS 胶水代码,golang 已经为我们提供了,直接复制过来。需要注意的是,使用 tinygo 和 原生编译的胶水代码是有差异的,根据具体情况拷贝对应的:

// 原生编译
$ cp “$(go env GOROOT)/misc/wasm/wasm_exec.js”.

// tinygo编译
$ cp “$(tinygo env TINYGOROOT)/targets/wasm_exec.js”./wasm_exec_tiny.js

所以重新拷贝文件,

执行成功, 但是有报错:

syscall/js.finalizeRef not implemented
找到解决方案:

https://github.com/tinygo-org/tinygo/issues/1140

1
2
3
4
5
6
7
8
9
<script>
const go = new Go();
go.importObject.env["syscall/js.finalizeRef"] = () => {};
WebAssembly.instantiateStreaming(fetch("main-tiny.wasm"), go.importObject)
.then((result) => {
const wasm = result.instance;
go.run(wasm)
});
</script>

 

在 js 中加入

go.importObject.env[“syscall/js.finalizeRef”] = () => {};

刷新即可

Document
wasm_exec_tiny.js:268 Hello World 🌍
go__encrypt(‘666’)
‘6M8FT8UhkLpfqHHomzvbLg==’

关于 go的其他包加解密可参考:

https://github.com/forgoer/openssl

go 语言编译wasm 在浏览器端实现cbc模式加解密

2023-01-16admin阅读(62)评论(0)

AES对称加密相比大家都很熟悉, 今天我主要是想实现在服务器数据加密, 然后在客户端进行解密操作,研究了 go 和 rust 的 wasm 编译, 奈何 rust 语法太难了, 之前学过一段时间 go 语言, 就用 go 简单做了个加解密操作, 然后编译为 wasm 给 js 调用。

实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main // 声明 main 包,表明当前是一个可执行程序
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"syscall/js"
) // 导入内置 fmt
 
// 全局的key
const useKey string = "9876787656785679"
 
func main() { // main函数,是程序执行的入口
message := "Hello World 🌍"
fmt.Println(message) // 在终端打印 Hello World!
js.Global().Set("go__encrypt", js.FuncOf(Encrypt))
js.Global().Set("go__decode", js.FuncOf(Decode))
<-make(chan bool)
}
 
// 加密数据后返回
func Encrypt(this js.Value, args []js.Value) interface{} {
input := args[0].String() // get the parameters
origData := []byte(input) // 待加密的数据
key := []byte(useKey)     // 加密的密钥
//log.Println("原文:", string(origData))
//log.Println("------------------ CBC模式 --------------------")
encrypted := AesEncryptCBC(origData, key)
//log.Println("密文(hex):", hex.EncodeToString(encrypted))
var baseData = base64.StdEncoding.EncodeToString(encrypted)
//log.Println("密文(base64):", baseData)
return baseData
}
 
// 解密数据
func Decode(this js.Value, args []js.Value) interface{} {
input := args[0].String()
key := []byte(useKey) // 加密的密钥
b1, _ := base64.StdEncoding.DecodeString(input)
decrypted := AesDecryptCBC(b1, key)
return string(decrypted)
}
 
// 加密
func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte) {
// 分组秘钥
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize()                              // 获取秘钥块的长度
origData = pkcs5Padding(origData, blockSize)                // 补全码
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式
encrypted = make([]byte, len(origData))                     // 创建数组
blockMode.CryptBlocks(encrypted, origData)                  // 加密
return encrypted
}
 
// 解密
func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key)                              // 分组秘钥
blockSize := block.BlockSize()                              // 获取秘钥块的长度
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
decrypted = make([]byte, len(encrypted))                    // 创建数组
blockMode.CryptBlocks(decrypted, encrypted)                 // 解密
decrypted = pkcs5UnPadding(decrypted)                       // 去除补全码
return decrypted
}
 
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

 

打包命令:  GOOS=js GOARCH=wasm go build -o static/main.wasm

注意如果是第一次使用, 需要 拷贝引用的 js 文件

cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” static

浏览器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="wasm_exec.js"></script>
  <script>
    const go = new Go();
    WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
      .then((result) => {
        const wasm = result.instance;
        go.run(wasm)
      });
  </script>
 
</body>
</html>

 

go 已经将两个方法写入到了浏览器的全局方法里

1
2
js.Global().Set("go__encrypt", js.FuncOf(Encrypt))
js.Global().Set("go__decode", js.FuncOf(Decode))

所以我们在浏览器里直接调用 go__enrypt(‘xxx’) 就可以实现加密。

存在的问题

 

这个打包出来的 wasm 文件有些大, 目前是 2M 左右, 在浏览器里直接使用还是不太现实的, 太大了。

后来查了资料, 说有一个 go 的子集叫: tinygo

打包出来的 wasm 文件体积小,  安装 tinygo 需要 xcode 13, 我电脑没有升级, 所以没有尝试。

Service Worker简介及用法介绍

2023-01-03admin阅读(54)评论(0)

Service Worker的由来

W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。service worker是浏览器的一个高级特性,本质是一个web worker,是独立于网页运行的脚本。 web worker这个api被造出来时,就是为了解放主线程。因为,浏览器中的JavaScript都是运行在单一个线程上,随着web业务变得越来越复杂,js中耗时间、耗资源的运算过程则会导致各种程度的性能问题。 而web worker由于独立于主线程,则可以将一些复杂的逻辑交由它来去做,完成后再通过postMessage的方法告诉主线程。 service worker则是web worker的升级版本,相较于后者,前者拥有了持久离线缓存的能力。

Service Worker的特点

  • 独立于主线程、在后台运行的脚本
  • 被install后就永远存在,除非被手动卸载
  • 必须是https的协议才能使用。不过在本地调试时,在http://localhost 和http://127.0.0.1 下也是可以跑起来的。
  • 不能直接操纵dom:因为sw是个独立于网页运行的脚本。
  • 可拦截请求和返回,缓存文件。sw可以通过fetch这个api,来拦截网络和处理网络请求,再配合cacheStorage来实现web页面的缓存管理以及与前端postMessage通信。

Service Worker的兼容性

下图是Service worker现有的浏览器支持版本, 从图上可以看出火狐和谷歌的支持是比较良好的,IE和safari需要相对比较高的版本才能够支持。移动端的话ios也需要从ios13才开始支持在安卓上的支持会相对广泛一点。

Service Worker的工作流程

  • Service Worker 文件只在首次注册的时候执行了一次。
  • 安装、激活流程也只是在首次执行 Service Worker 文件的时候进行了一次。
  • 首次注册成功的 Service Worker 不能拦截当前页面的请求。
  • 非首次注册的 Service Worker 可以控制当前的页面并能拦截请求
  • Service Worker 首次注册或者有新版本触发更新的时候,才会重新创建一个 worker 工作线程并解析执行 Service Worker 文件,在这之后并进入 Service Worker 的安装和激活生命周期

Service Worker的生命周期

当一个servicework被注册成功后,它将开始它的生命周期,我们对servicework的操作一般都是在其生命周期里面进行的。servicework的生命周期分为这么几个状态 安装中, 安装后, 激活中, 激活后, 废弃。

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,这个状态会触发 install 事件,一般会在install事件的回调里面进行静态资源的离线缓存, 如果这些静态资源缓存失败了,那 Service Worker 安装就会失败,生命周期终止。
  • 安装后( installed ):当成功捕获缓存到的资源时,servicework会变为这个状态,当此时没有其他的servicework线程在工作时,会立即进入激活状态,如果此时有正在工作的servicework工作线程,则会等待其他的 Service Worker 线程被关闭后才会被激活。可以使用 self.skipWaiting() 方法强制正在等待的servicework工作线程进入激活状态。
  • 激活( activating ):在这个状态下会触发activate事件,在activate 事件的回调中去清理旧版缓存。
  • 激活后( activated ):在这个状态下,servicework会取得对整个页面的控制
  • 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。新版本的 Service Worker 替换了旧版本的 Service Worker会出现这个状态

更新Service Worker

更新一个servicework,最直接的办法就是修改servicework.js这个文件,当刷新浏览器时,浏览器尝试重新下载servicework.js脚本文件,然后会与之前的版本比对,一旦发现文件内容不一致,就会进入更新流程。

  • 新的 servicework 被启动安装并触发 install事件。
  • 安装成功后,新版 servicework 进入等待状态,此时页面的控制权还在老版 servicework手中。
  • 当servicework控制的所有终端都关闭之后,或者手动self.skipWaiting(),旧的 servicework 才能被终止,此时新的servicework被激活,触发activate 事件。
  • 用户再次访问页面,或刷新页面,新的 service work 启动控制页面。

Service Worker 的简单实践

  1. 注册。 serviceWorker对象存在于navigator对象下,可以再主线程中调用navigator.serviceWorker.register()方法来注册servicework,register 方法接受两个参数,第一个参数表示servicework.js相对于origin的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性,用来指定你想让 service worker 控制的内容的目录。 默认值为servicework.js所在的目录。这个属性所表示的路径不能在 service worker 文件的路径之上,默认是 Serivce Worker 文件所在的目录。 成功注册或返回一个promise。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<span class="token comment">// 页面的入口文件</span>
 
<span class="token keyword">if</span> <span class="token punctuation">(</span>navigator<span class="token punctuation">.</span>serviceWorker<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'load'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'开始注册ServiceWorker'</span><span class="token punctuation">)</span>
    navigator<span class="token punctuation">.</span>serviceWorker
      <span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span><span class="token string">'./serviceworker.js'</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">reg</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ServiceWorker register success: '</span><span class="token punctuation">,</span> reg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ServiceWorker register failed: '</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
 
 
  1. 安装

 

1
2
3
4
5
6
7
8
9
10
self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'install'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'install事件'</span><span class="token punctuation">)</span>
  self<span class="token punctuation">.</span><span class="token function">skipWaiting</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//用来强制更新的servicework跳过等待时间</span>
  event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span>
    caches<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token constant">CACHE_NAME</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>cache<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> cache<span class="token punctuation">.</span><span class="token function">addAll</span><span class="token punctuation">(</span>urlsToCache<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
 

首先 self.skipWaiting() 执行,告知浏览器直接跳过等待阶段,淘汰过期的Service Worker脚本,直接开始尝试激活新的Service Worker。然后使用 caches.open 打开一个Cache,打开后,通过cache.addAll尝试缓存我们预先声明的文件。 CacheStorage 全局的cache Api 并非只有在sw中才能用 浏览器控制台直接用也是可以的,所以是挂在window下的

event.waitUntil() 只能在 Service Worker 的 install 或者 activate 事件中使用;看起来像是一个 callback,用来延长事件的作用时间,但是,即便你不使用它,程序也可能正常运行。如果你传递了一个 Promise 给它,那么只有当该 Promise resolved 时,Service Worker 才会完成 install;如果 Promise rejected 掉,那么整个 Service Worker 便会被废弃掉。因此,cache.addAll 里面,只要有一个资源获取失败,整个 Service Worker 便会失效。

在 install 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 installing 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:Service Worker 工作线程在所有依赖的核心 cache 被缓存之前都不会被激活。

在 activate 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 activating 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:任何功能事件不会被分派到 ServiceWorkerGlobalScope 对象,直到它删除过期的缓存条目。

当 waitUntil()运行时,如果 Promise 是 rejected 那么installing 或者 activating 的状态会被设置为 redundant。

  1. 激活
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'activate'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'activate事件'</span><span class="token punctuation">)</span>
  <span class="token keyword">var</span> cacheWhitelist <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token constant">CACHE_NAME</span><span class="token punctuation">]</span>
  self<span class="token punctuation">.</span>clients<span class="token punctuation">.</span><span class="token function">claim</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 保证 激活之后能够马上作用于所有的终端</span>
  event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span>
    caches<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">cacheNames</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>
        cacheNames<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">cacheName</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>cacheWhitelist<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> caches<span class="token punctuation">.</span><span class="token keyword">delete</span><span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
 

在激活servicework时需要删除之前的缓存,可将需要的缓存放在有个白名单中,然后通过caches.keys()拿到所有缓存,将不再白名单中的缓存删掉。

  1. 拦截网络请求
1
2
3
4
5
6
7
8
9
10
11
  self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'fetch'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">respondWith</span><span class="token punctuation">(</span>
      caches<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>request<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">)</span>
          <span class="token keyword">return</span> response<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 

通过监听servicework的 fetch 事件来拦截网络请求,调用 event 上的 respondWith() 方法来劫持当前servicework控制域下的 HTTP 请求,该方法会直接返回一个Promise 结果 ,这个结果就会是http请求的响应。上面代码中就一个简单的逻辑,先劫持http请求,然后看看缓存中是否有这个请求的资源,如果有则直接返回,如果没有就去请求服务器上的资源。 event.respondWith 方法只能在 Service Worker 的 fetch 事件中使用。

Cache Stroage 只能缓存静态资源,所以它只能缓存用户的 GET 请求;Cache Stroage 中的缓存不会过期,但是浏览器对它的大小是有限制的,所以需要我们定期进行清理。
对应post 请求我们也可以通过 fetch方法拦截到,来进行一些自定义的返回。

  1. service worker 与主线程之间的通信
  • 主线程
1
2
3
4
5
6
7
8
  <span class="token comment">// 传递</span>
  navigator<span class="token punctuation">.</span>serviceWorker<span class="token punctuation">.</span>controller <span class="token operator">&amp;&amp;</span> navigator<span class="token punctuation">.</span>serviceWorker<span class="token punctuation">.</span>controller<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">"this message is from page"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 
  <span class="token comment">// 接收</span>
  navigator<span class="token punctuation">.</span>serviceWorker<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'service worker传递的信息'</span><span class="token punctuation">,</span>e<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 
  • service worker
1
2
3
4
5
6
self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'页面传递过来的数据'</span><span class="token punctuation">,</span><span class="token keyword">event</span><span class="token punctuation">.</span>data<span class="token punctuation">)</span>  <span class="token comment">// 收到主线程传递的信息</span>
  <span class="token keyword">event</span><span class="token punctuation">.</span>source<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">'this message is from sw.js to page'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 向主线程传递信息</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
 
 
  1. service worker 卸载
1
2
3
4
5
6
7
8
9
10
  navigator<span class="token punctuation">.</span>serviceWorker<span class="token punctuation">.</span><span class="token function">getRegistrations</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">registrations</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> registration <span class="token keyword">of</span> registrations<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">//安装在网页的service worker不止一个,找到我们的那个并删除</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>registration<span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>registration <span class="token operator">&amp;&amp;</span> registration<span class="token punctuation">.</span>scope <span class="token operator">===</span> <span class="token string">'http://localhost:8080/'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        registration<span class="token punctuation">.</span><span class="token function">unregister</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
 
  1. serviceworker 和 http缓存
  • 在缓存文件更新方面
    • 如果是http缓存,一刷新页面就可以拿到最新的资源
    • 如果是sw缓存, 一刷新页面,会返回当前缓存中的资源(不是最新),然后请求sw.js文件发现更新后重新进入sw生命周期,重新去更新缓存,当你再次刷新时才能拿到最新资源。所以在缓存资源更新时,sw会延迟一次刷新才能获取最新资源
  • 在缓存控制方面:http一般是由服务器端控制的,而sw则是可以前端自己控制,可以更好地控制缓存。

    协商缓存返回状态码304, 强缓存返回的是200, 在这点上server worker和强缓存比较类似的返回也是200, 会对请求进行拦截,不会真是的发出请求。

其他

会有一些开源的框架对 service worker进行了一些封装,避免了我们重复繁琐的去写一下fetch监听,install事件,active事件等,大大简化了繁琐的写法。关注度比较高的应该是谷歌推出的 workbox, 围绕他也有一系列的工具,如 workbox-cli、gulp-workbox、webpack-workbox-plagin 等等。

workbox提供了一下几种缓存策略:

  • Stale-While-Revalidate 当请求的路由有对应的 Cache 缓存结果就直接返回,在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果 ( 从缓存中读取资源的同时发送网络请求更新本地缓存 )
  • CacheFirst 当匹配到请求之后直接从 Cache 缓存中取得结果,如果 Cache 缓存中没有结果,那就会发起网络请求,拿到网络请求结果并将结果更新至 Cache 缓存,并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求。 (有缓存用缓存,无缓存则请求网络)
  • CacheOnly 这个策略也比较直接,直接使用 Cache 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求。 (仅使用缓存)
  • NetworkFirst 采用网络优先的策略,也就是优先尝试拿到网络请求的返回结果,如果拿到网络请求的结果,就将结果返回给客户端并且写入 Cache 缓存,如果网络请求失败,那最后被缓存的 Cache 缓存结果就会被返回到客户端,这种策略一般适用于返回结果不太固定或对实时性有要求的请求,为网络请求失败进行兜底。 (有网的情况下采取网络,没网的情况下用缓存)
  • NetworkOnly 比较直接的策略,直接强制使用正常的网络请求,并将结果返回给客户端,这种策略比较适合对实时性要求非常高的请求。 (仅使用网络请求)

总结

本次只是对于service worker 的一些基础API的尝试和学习,可以当做是一些学习资料的整理, 希望有不对的地方可以指出,service worker的知识点还有很多,有比较深入了解的同学也欢迎补充

npm依赖管理之peerDependencies理解

2022-11-09admin阅读(176)评论(0)

引言

想必前端同学对npm的devDependencies和dependencies都比较熟悉,但是对peerDependencies可能就有点陌生,尤其是没有写过npm包插件的同学,比如之前使用grunt自动化工具的相关插件(如grunt-contrib-jasmine等)或者目前基于某个框架的ui组件库等等,这些都是需要对peerDependencies有一定了解的。下面我们就来说说peerDependencies。

npm2中dependencies与peerDependencies区别

假设我们当前的项目是MyProject,项目中有一些依赖,比方其中有一个依赖包PackageA,该包的package.json文件指定了对PackageB的依赖:

1
2
3
4
5
{
    "dependencies": {
        "PackageB": "1.0.0"
    }
}

如果我们在我们的MyProject项目中执行npm install PackageA, 我们会发现我们项目的目录结构会是如下形式:

1
2
3
4
5
MyProject
|- node_modules
   |- PackageA
      |- node_modules
         |- PackageB

那么在我们的项目中,我们能通过下面语句引入”PackageA”:

1
var packageA = require('PackageA')

但是,如果你想在项目中直接引用PackageB:

1
2
var packageA = require('PackageA')
var packageB = require('PackageB')

这是不行的,即使PackageB被安装过;因为Node只会在“MyProject/node_modules”目录下查找PackageB,它不会在进入PackageA模块下的node_modules下查找。

所以,为了解决这个问题,在MyProject项目package.json中我们必须直接声明对PackageB的依赖并安装。

但是,有时我们不用在当前项目中声明对PackageB的依赖就可以直接引用,尤其是,PackageA是一个类似于grunt的插件,例如grunt-contrib-jshint。

为什么在项目中不用声明就可以直接使用呢?这就不得不说说peerDependencies的作用了。

peerDependencies的引入

为了解决这种问题:

于是peerDependencies就被引入了。例如上面PackageA的package.json文件如果是下面这样:

1
2
3
4
5
{
    "peerDependencies": {
        "PackageB": "1.0.0"
    }
}

那么,它会告诉npm:如果某个package把我列为依赖的话,那么那个package也必需应该有对PackageB的依赖。

也就是说,如果你 npm install PackageA ,你将会得到下面的如下的目录结构:

1
2
3
4
MyProject
|- node_modules
   |- PackageA
   |- PackageB

你可能注意到:

在npm2中,即使当前项目MyProject中没有直接依赖PackageB,该PackageB包依然会安装到当前项目的node_modules文件夹中。

下面的代码现在可以正常工作了,因为两个包在”MyProject/node_modules”中被安装了:

1
2
var packageA = require('PackageA')
var packageB = require('PackageB')

 

总结一句话,peerDependencies的具体作用:

peerDependencies的目的是提示宿主环境去安装满足插件peerDependencies所指定依赖的包,然后在插件import或者require所依赖的包的时候,永远都是引用宿主环境统一安装的npm包,最终解决插件与所依赖包不一致的问题。

举个例子,就拿目前基于react的ui组件库ant-design@3.x来说,因该ui组件库只是提供一套react组件库,它要求宿主环境需要安装指定的react版本。具体可以看它package.json中的配置:

1
2
3
4
  "peerDependencies": {
    "react": "&gt;=16.0.0",
    "react-dom": "&gt;=16.0.0"
  }

它要求宿主环境安装react@>=16.0.0和react-dom@>=16.0.0的版本,而在每个antd组件的定义文件顶部:

1
2
import * as React from 'react';
import * as ReactDOM from 'react-dom';

组件中引入的react和react-dom包其实都是宿主环境提供的依赖包。

npm2和npm3中peerDependencies的区别

正如上一节谈论的,在npm2中,PackageA包中peerDependencies所指定的依赖会随着npm install PackageA一起被强制安装,所以不需要在宿主环境的package.json文件中指定对PackageA中peerDependencies内容的依赖。

但是在npm3中,peerDependencies的表现与npm2不同:

npm3中不会再要求peerDependencies所指定的依赖包被强制安装,相反npm3会在安装结束后检查本次安装是否正确,如果不正确会给用户打印警告提示。

就拿上面的例子来说,如果我们npm install PackageA安装PackageA时,你会得到一个警告提示说:

1
2
PackageB是一个需要的依赖,但是没有被安装。
 

这时,你需要手动的在MyProject项目的package.json文件指定PackageB的依赖。

另外,在npm3的项目中,可能存在一个问题就是你所依赖的一个package包更新了它peerDependencies的版本,那么你可能也需要在项目的package.json文件中手动更新到正确的版本。否则会出现类似下图所示的警告信息:

 

从npm v3.0开始,peerDependencies 对等依赖项不会自动安装在npm install,而且手动安装它们可能会很麻烦。这个  install-peerdeps   工具使过程快速而简单。

也适用于yarn

传送门 install-peerdeps

 

转载来自: https://www.cnblogs.com/taohuaya/p/14557509.html

创建全新的 react 项目

2022-11-03admin阅读(107)评论(0)

创建项目:

1
2
3
npx create-react-app my-app
cd my-app
npm start

添加路由

1
npm install --save react-router-dom

新建 router 目录, 新建 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";
import Index from  '../views/index';
import Demo from '../views/demo';
 
export default function BasicExample() {
  return (
    <Router>
      <Switch>
          <Route exact path="/">
            <Index />
          </Route>
          <Route exact path="/demo">
            <Demo />
          </Route>
        </Switch>
    </Router>
  );
}

遇到访问报错:

export ‘Switch‘ (imported as ‘Switch‘) was not found in ‘react-router-dom‘

原因:这些报错原因均为’Switch’ 和’Redirect’ 是react-router 5版本的接口,而最新版本是

“react-router-dom”: “^6.2.1″,并且已经将Switch改为Routes。

解决: 办法有二:

1.将所有 Switch 改为 Routes,Redirect 改为 Navigate ,withRouter改为 useNavigate

2.卸载新版本,再安装5的版本

1
2
npm uninstall react-router-dom
npm install react-router-dom@5

新建 jsx 文件:

新建 views目录.然后新建 index.jsx 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from "react";
export default class App extends Component {
  constructor(props){
    super(props)
    this.state = {
      goods: [
        { title: '零售', price: 19.8 },
        { title: '科技', price: 29.8 },
        { title: '同城', price: 19.8 },
        { title: '拼拼', price: 39.8 },
        { title: '购物', price: 59.8 },
      ],
      name: 'hello world'
    }
  }
  render() {
    return (
      <div style={{padding: '20px'}}>
        <h1>{this.state.name}</h1>
        {
          this.state.goods.map((val,index)=> {
            return (
              <div className="title" key={index}>
               <span> {val.title} </span>
               <span> {val.price} </span>
              </div>
            )
          })
        }
      </div>
    );
  }
 
  componentDidMount(){
    this.setState({
      name: '我更新了'
    })
  }
  componentDidUpdate(){}
}

路由处理

需要使用 withRouter 方法包裹抛出的 class, props 中会有 history 方法!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
goDemo() {
    // 隐式传参
    this.props.history.push({
      pathname: 'article',
      state: {
        id: 3,
        test: 1
      },
      query: {
        id: 3,
        test: 1
      }
    })
  }
  goDemo2(){
    this.props.history.push('article?a=1')
  }
  goBack(){
    this.props.history.goBack()
  }

 

教你在npm上发布自己的vue组件

2022-07-04admin阅读(689)评论(0)

以前做过几次组件, 但是今天想到要再发布一个小组件的时候,居然一下子想不起来了,简单做个记录吧

首先 本地需要安装 vue, 若是第一次使用, 应该还需要安装

npm install -g @vue/cli-service-global

首先, 初始化一个目录, npm init

一路按 确定 即可

38

init 完成之后,

我们本地新建一个.vue 文件, 然后使用 vue serve index.vue 启动文件

在文件里正常编写我们的代码即可, 例如我写了一个提示用户进行微信分享的小组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<style scoped>
.....
</style>
<template>
  <div class="friday">
    <div class="guide-board">
      <div class="flex-board guide-info">
        <div class="left-board">
          <div class="user-info">
            <div class="row">
              <div class="user-audio">
                <div class="avater" style="background-image: url(http://rescdn.yishihui.com/longvideo/pic/c8676c592b72428e88faa4176d7baa011650023705723);">
                  <img src="data:image/png;CYII=" class="play-btn">
                </div>
              </div>
              <span>点击右上角</span>
              <img class="dia"
                src="data:image/png;base64,iVBORw0KGgoAARrHQQi2stLuDK1IoNmeV7gjXhMQE4fAEG0Esh
            <div class="row">
              <span>分享给「朋友」或「朋友圈」</span>
            </div>
          </div>
        </div>
        <div class="right-board">
          <img class="icon-hand"
            src="data:image/pn" alt="">
        </div>
      </div>
      <img src="https://vlogh5.piaoquantv.com/core/static/img/wx_native_share_bar.3bbdeb4.png" alt="" class="guide-img" />
    </div>
  </div>
</template>
 
<script>
/**
* Created by iyahe@qq.com
* Date: 2022/7/4
* Time: 2:09 下午
* Description:
*/
export default {
  name: "friday",
  mixins: [],
  data() {
    return {}
  },
  computed: {},
  watch: {},
  components: {},
  methods: {},
  created() {
  },
  mounted() {
  },
}
</script>

 

组件开发完成后, 我们第一步需要新建一个js文件

1
2
3
4
5
import tips from "./index.vue";
const install=(Vue)=>{
  Vue.component(tips.name, tips)
}
export default install

这里需要注意: tips.name 这里就是注册组件名称, 若是组件里没有写 name, 这里需要手动写一个名称,

完成后我们开始打包组件,

我们在 package.json 里写入打包命令

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "name": "friday-tips",
  "version": "1.0.0",
  "description": "",
  "main": "friday/friday.umd.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {
    "build": "vue build --target lib ./index.js --dest friday --name friday"
  },
  "author": "",
  "license": "ISC"
}

 

其中的 friday 是目录名称, 我们打包到这个目录, 执行编译后, 我们可以看到

 

iShot_2022-07-04_15.31.45

什么是 umd ?

commonJS、requireJS 都是用来处理JS模块化的,其中 commonJS 用来给 nodejs 使用(使用了 module.exports 的用法)。后来使用 import/export 来导出/引入模块。umd 是统一模块定义方法,可以兼容所有其他的模块定义方法。

将 生成的 xx.umd.js 写入到 package.json 的 main 入口;

打包完成后, 我们开始上传到 npm

这里需要注意, 若是使用过 npm, yarn 等, 建议安装一个 nrm ,手动切换到 npm 服务器

SM xxx-mobile % nrm ls

npm ———- https://registry.npmjs.org/
yarn ——— https://registry.yarnpkg.com/
tencent —— https://mirrors.cloud.tencent.com/npm/
cnpm ——— https://r.cnpmjs.org/
taobao ——- https://registry.npmmirror.com/
npmMirror —- https://skimdb.npmjs.com/registry/
jd ———– http://registry.m.jd.com/

SM xx-mobile %

切换过去后, 直接登陆就可以了, npm login

登陆成功后, 执行 npm publish 开始发布

若不出意外, 发布成功后可以在 npm 官网个人中心看到了!

偶尔遇到报错提示发布失败, 实际发布成功!

发布成功后, 我们随便找一个项目, 安装我们的组件库看看

直接 npm install xxx

安装完成后, 我们先全局注册一个看看效果!

1
2
3
import friday from 'friday-tips'
import 'friday-tips/friday/friday.css' // 引入样式文件
Vue.use(friday)

注意要把 css 引入进来, 然后我们组件里直接使用 <friday /> 就可以了!

若要在实际项目里调试, 还有一个小技巧!

我们直接在组件目录打包完成后, 将

iShot_2022-07-04_15.31.45

直接拷贝到我们已安装过 组件的项目的 node_modules 目录里可以直接看到效果,

我们可以打开 node_modules 目录, 找到我们的插件目录 friday-tips , 然后直接拷贝覆盖就可以了!

 

 

关于对逐帧动画的一次研究

2022-06-28admin阅读(444)评论(0)

很早以前, 就想对芊云全景的语音讲解模块做个卡通人物做讲解, 第一次调研的时候, 是想使用 live2D, 也就是我博客主页这只小猫咪的实现方式, 但是这个做模型的方法特别 复杂, 而且没有现成的可用的客服类模型, 于是就搁置了, 但是近期发现某20 云居然上线了这个语音讲解功能, 并且命名: 元宇宙 xxx, 于是我又开始调研其他方案。

第一种方案, 就是去年调研过的 live2D 技术, 做模型太复杂了放弃了

第二种方案: 想使用建模的方式, 做一个 fbx 模型, 带动画那种, 然后设定几组动画重复播放即可, 但是去淘宝问了一圈, 淘宝店家报价 1w, 果断放弃

第三种方案: 既然 3D 的搞不成, 那弄 2D 的总可以了吧, 结果淘宝报价 3000 元, 放弃

作为一个动漫专业毕业的来说, 总不能东西就不做了吧~ 开始寻找能在网页上直接制作 2D 动画的网站, 看看能不能找到一些有用的

果然找到了2个网站可以做类似的2D 动画:  一个叫来画, 一个叫万彩动画大师

来画这个软件研究了下, 动画动作比较简单, VIP 相对便宜一些,

万彩动画这个, 动画场景比较多, 有我能使用的东西, 但是 VIP 一个月 900 快, 真实忍不了啊,

还是扒素材看看吧, 开始分析这个网站的动画生成方案, 看了看, 发现了一些端倪,

这个网站的动画, 都是用序列帧做的, 每个动画, 都有一个类似的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "1": {
    "png": "595a95c6a3a9c6cf11c8bb74a92aee94.png",
    "json": "8bdadf1036cf4a33912d3200c9cc009c.json",
    "time": 5.16
  },
  "2": {
    "png": "55c831370bdd776bbf7b275e842f865a.png",
    "json": "b2bfa00187daa2741336a34d8bd96111.json",
    "time": 5.16
  },
  "3": {
    "png": "42d45f6958f4c583b3c4ef58bc731045.png",
    "json": "3f651a72df83b5bc9937e4713a3e75a1.json",
    "time": 5.16
  },
....

其中,

png 就是动画的序列帧图片,

json 是配置文件

刚开始看到这个序列帧图片, 我以为直接拿图片去解析播放就可以了, 于是简单写了一段 css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@keyframes run {
      from {
       background-position-x: 0;
      }
      to{
        background-position-x: -9196px;
      }
    }
    .play {
      background-image: url("r1.webp");
      background-repeat: no-repeat;
      width: 23px;
      height: 62px;
      position: absolute;
      animation: run 12s steps(42);
    }

但是发现不对, 这个动画播不起来了, 跳来跳去, 研究完发现他们的序列帧图的位置是不固定的, 我这种方式就用不了了,

然后开始分析了一下他们的 json 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
{
  "frames": [
 
  {
    "filename": "25-129-1.png",
    "frame": {"x":6517,"y":1,"w":139,"h":406},
    "rotated": false,
    "trimmed": true,
    "spriteSourceSize": {"x":141,"y":120,"w":139,"h":406},
    "sourceSize": {"w":409,"h":614}
  },
.....

发现其中有 w 和 h 这两个参数, 猜测应该就是宽高了吧, 于是使用配置文件播放一次看看

随手引入一个 jq, 加载配置文件播放动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.get('6500.json').then(res=>{
   const frames =  res.frames
   const dom = document.querySelector('.play');
   let i = 0;
   setInterval(_=>{
     i++;
     if (i >= frames.length){
       i = 10;
     }
     dom.style.width = frames[i].frame.w + 'px';
     dom.style.height = frames[i].frame.h + 'px';
     dom.style.backgroundPositionX = -1 * frames[i].frame.x + 'px';
     dom.style.backgroundPositionY = frames[i].frame.y + 'px';
    
   }, 42)
})

果然是这样, 动画播起来了, 还算流畅, 但是也有问题

播放期间这个人物会来回跳, 不是站这里不动, 而官方他们的是不动的, 于是继续分析 json 文件, 发现他们使用了定位, 于是我也加上定位,

并且发现 spriteSourceSize 这个参数里有个 x 和 y, 值和第一帧是一样的, 于是把这个也带进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
$.get('6500.json').then(res=>{
   const frames =  res.frames
   const dom = document.querySelector('.play');
   let i = 0;
   setInterval(_=>{
     i++;
     if (i >= frames.length){
       i = 10;
     }
     dom.style.width = frames[i].frame.w + 'px';
     dom.style.height = frames[i].frame.h + 'px';
     dom.style.backgroundPositionX = -1 * frames[i].frame.x + 'px';
     dom.style.backgroundPositionY = frames[i].frame.y + 'px';
     dom.style.left = frames[i].spriteSourceSize.x + 'px';
     dom.style.top = frames[i].spriteSourceSize.y + 'px';
   }, 42)
})
</script>

播放了一下, 果然没有问题了,

后面给逐帧动画的人物简单调整一下, 多拼接几组动作, 瞬间感觉省了不少钱!

贫穷使我快乐

逐帧原图: https://online.xiuzhan365.com/tjuu/Draft/kpxq/files/extfile/ffd9374822dfa728db105eeaa382af6a.png?x-oss-process=image/format,webp/quality,lossless

配置文件: https://online.xiuzhan365.com/tjuu/Draft/kpxq/files/extfile/a913543e87bc73ace983f5a17d81b1f9.json

Snipaste_2022-06-28_16-37-57

关于如何获取浏览器的设备指纹的调研

2022-06-10admin阅读(709)评论(0)

最近在项目开发中, 需要用到统计用户登录的客户端浏览器, 用来防止一个账号多人使用的情况, 于是简单的调研了一下浏览器的指纹识别技术

首先, 考虑到使用 javascript 来查看  navigator, 看看能不能找到一些有用的信息, 寻找过程中, 找到了一个叫 fingerprintjs 的插件, 已经封装好了使用方法,

具体文档: https://github.com/fingerprintjs/fingerprintjs

试了试, 没有问题, 浏览器即使开启了无痕模式, 也可以正常检测到是同一个浏览器

看到这个名字, 突然想到, 微信支付里面好像有一个类似的东西

翻了好一会而微信支付的开发文档, 果然有一个:

https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_7&index=6

背景介绍

由于用户网络使用代理或网络环境复杂的原因导致无法获取准确的用户端IP,商户侧可在统一下单时传递fingerprint参数给微信侧,用于辅助校验

这里需要注意,浏览器指纹并不能完全代替客户端IP(spbill_create_ip),也就是说在设置fingerprint字段的同时,spbill_create_ip字段也必须传递且不能乱传(如传内网IP,调起支付还是会引发拦截)

fingerprint设置步骤

1. 在下单面引入JS: https://wx.gtimg.com/wxpay_h5/fingerprint2.min.1.5.1.js

2. 调用Fingerprint2()获取浏览器指纹

1
2
3
4
5
6
<script type="text/javascript">
     var fp=new Fingerprint2();
           fp.get(function(result){
               //result即为获取到的浏览器指纹值
          });
 </script>

刚试了试, 效果也不错!

还有一种方案: 具体可以查看 https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Authentication_API

用到的是service worker 中的push API。

Push API的作用就是根据用户的唯一表示进行主动消息推送的。

具体用法:

PushManager.getSubscription()

就像文档中所说,他最后会返回一个

PushSubscription

对象

这个对象中的endpoint 就是浏览器Push 服务器来寻找客户端的依据,

可以直接拿来当设备唯一标识符了。

有个小小的问题: 你webapp必须具有通知权限(转载来自知乎)。

关于对fluent-ffmpeg的一次简单尝试

2022-03-29admin阅读(1139)评论(0)

今天简单研究了一下怎么用 nodejs 来操作 ffmpeg, 发现这个包, 做了一些简单的尝试, 做个记录!

其中 这段关于计算视频的码率: 

// 如果一个视频文件大小有10GB,时长为90分钟(5400秒), 那么它的码率(视频+音频)为:

// 10 x 1024 x 8 / 5400 = 15Mbps

// 视频文件体积:(音频码率+视频码率) x 时长 / 8

// 如果一个视频的视频码率为3Mbps, 音频码率为512Kbps, 时长90分钟(5400秒), 那么整个视频文件的大小就是:

// (512/1024 + 3) x 5400 / 8 = 2362MB

// 相反,如果一个视频文件大小有10GB,时长为90分钟(5400秒), 那么它的码率(视频+音频)为:

// 10 x 1024 x 8 / 5400 = 15Mbps

// 例:有一个1.5小时(5400秒)的影片,希望转换后文件大小刚好为700M

// 计算方法如下:

// 700×8÷5400×1024≈1061Kbps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Created by 天明
* Date: 2022/3/29
* Time: 5:43 下午
* Description:
*/
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath('/Applications/ffmpeg');
 
class Then {
constructor() {
}
static init() {
new Promise((resolve, reject) => {
console.log('获取大小和时长');
resolve()
}).then(_ => {
return new Promise((resolve, reject) => {
const Flow = ffmpeg('test.mp4').size('1280x720')
.videoBitrate('24000') //24M
.inputFPS(24)
.audioBitrate('128k').autopad(true, 'white')
.on('progress', (progress) => {
console.log('进度: ' + progress.percent + '% done');
})
.on('error', (err) => {
console.error('出现错误');
reject(err)
}).on('end', () => {
console.log('转码完成');
resolve();
});
Flow.output('out.mp4').run();
})
}).then(() => {
console.log('加水印开始');
return new Promise((resolve, reject) => {
const Flow = ffmpeg('out.mp4').input('logo.png')
.inputOptions([
'-filter_complex',
'overlay=10:10'
])
.on('progress', (progress) => {
console.log('进度: ' + progress.percent + '% done');
}).on('error', (err) => {
console.error('出现错误:');
reject(err)
}).on('end', () => {
console.log('转换完成');
resolve();
})
Flow.output('out2.mp4').run();
});
}).then(() => {
console.log('转码结束')
}).catch(err => {
console.error(err)
})
}
static bitRate() {}
}
 
Then.init()

 

这段记录了视频转码和添加水印的流程!

关于 安装 ffmpeg 流程

首先我们下载到服务器

https://johnvansickle.com/ffmpeg/

在列表中选择适合自己的版本,鼠标右键,复制链接地址。这里我直接选择了amd64的最新版本,复制到的下载地址是https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz。
打开Linux中要安装ffmpeg的目录,使用wget命令下载文件:
wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz。

可以看到下载完成的文件后缀名为 .tar.xz ,执行对应解压命令解压文件:

xz -d ffmpeg-git-amd64-static.tar.xz

再解压

tar -xvf ffmpeg-git-amd64-static.tar

解压完成后进入解压出来的这个目录ffmpeg-git-20190424-amd64-static/:

当然,如果想要ffmpeg命令全局可用,可以在bin目录加个链接。比如,分别执行如下命令,即可在:/usr/bin目录下创建ffmpeg和ffprobe软链接。

cd /usr/bin
ln -s /data/software/ffmpeg-git-20190424-amd64-static/ffmpeg ffmpeg
ln -s /data/software/ffmpeg-git-20190424-amd64-static/ffprobe ffprobe

这样在其他目录也都可以直接使用 ffmpeg 和 ffprobe 而不用加目录前缀了。

记录一个比较好用的 nodejs 压缩与解压包

2022-03-04admin阅读(990)评论(0)

compressing

可以很便捷的压缩与解压文件与目录!

配合

archive

强大压缩能力, 能解决不少问题

使用方式: 压缩与解压

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const compressing = require('compressing');
 
// compress a file
compressing.gzip.compressFile('file/path/to/compress', 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);
// compress a file buffer
compressing.gzip.compressFile(buffer, 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);
// compress a stream
compressing.gzip.compressFile(stream, 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);

 

 

  • 1
  • 2
  • 3
  • 4
  • ...
  • 下一页
  • 共 13 页
关于我

小天明 北京·朝阳 前端搬砖工程师

碎碎念):(来自公众号)

热门文章

  • 踩坑记录——iphone上safari开启隐身模式时localStorage变为只读-雅荷心语博客踩坑记录——iphone上safari开启隐身模式时localStorage变为只读2017-02-21评论(4)
  • 程序员是怎样一群人-雅荷心语博客程序员是怎样一群人2015-12-08评论(3)
  • git 操作那些小事-雅荷心语博客git 操作那些小事2017-03-04评论(2)
  • PHPStorm10 下载安装破解汉化-雅荷心语博客PHPStorm10 下载安装破解汉化2015-12-15评论(2)
  • call_user_func_array 这个函数什么时候用-雅荷心语博客call_user_func_array 这个函数什么时候用2016-09-02评论(2)
2023年2月
一 二 三 四 五 六 日
« 一    
 12345
6789101112
13141516171819
20212223242526
2728  

最新评论

  • 前端小武 6年前 (2017-04-06)说:
    我看到了layer
  • 丁艳平 6年前 (2017-03-03)说:
  • Dawn 6年前 (2016-09-16)说:
    call_user_func_array最后的例子是错哦,你用bc方法去调用类里 另外一个方法就知道问题所在了。情况1.调用非静态方法 第一个参数应该传[类的实例,调用方法] (既然有类实例了直接-&
  • Dawn 7年前 (2016-06-21)说:
    tp框架设置了全局捕获异常的,这也没什么。坑的是 他捕获了异常。然后全部返回404。。。不知道的 还以为自己网站被删除了
  • Dawn 7年前 (2016-05-17)说:
    构造函数里的判断 用异常机制可能更好一些

左邻右舍

  • Brian's Blog
  • 易水寒
  • 楼教主
  • 贤心
  • 阿米龙

雅荷心语博客 -心之所向便是光

联系我们关于我们

© 2023 雅荷心语博客   网站地图

emlogdux_heademlogdux_footer