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

前端之旅 第3页

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

360AI助手调用记录

2024-02-04admin阅读(589)评论(0)

360AI助手使用 NodeJs 调用。

https://ai.360.com/

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
import axios from 'axios';
const data = {
    model: "360gpt-pro",
    messages: [
        {
            role: "system",
            content: "请扮演一个语义分析小助手,帮我分析出输入内容中的重点关键词进行分词输出,只输出分词关键词,关键词使用|分割,不要输出其他内容"
        },
        {
            role: "user",
            content: "打的屁股开花嗷嗷叫"
        }
    ],
    stream: false
};
 
const config = {
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'xxx在这里输入Key'
    }
};
 
axios.post('https://api.360.cn/v1/chat/completions', data, config)
    .then((response) => {
        console.log(response.data.choices);
    })
    .catch((error) => {
        console.error('error', error.response);
    });

输出结果:

1
2
3
4
5
6
7
[
  {
    message: { role: 'assistant', content: '屁股|开花|嗷嗷叫' },
    finish_reason: '',
    index: 0
  }
]

直接调用 chatGpt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ChatGPTAPI } from 'chatgpt'
import fetch from 'node-fetch'
async function example() {
const api = new ChatGPTAPI({
        // apiBaseUrl: 'https://api.v3.cm/v1',
        apiKey: `输入ApiKey`,
fetch,
        completionParams: {
            model: 'gpt-3.5-turbo',
            temperature: 0.5,
            top_p: 0.8
        }
});
    const t1 = new Date().getTime();
const res = await api.sendMessage(`请扮演一个语义分析小助手,帮我分析出输入内容中的重点关键词,并且进行分词输出,只输出分词后的关键词,关键词使用|分割,不要输出其他内容\n 输入内容: 陈平安默然无言啊`, {});
    const t2 = new Date().getTime();
console.log(res.text)
    console.log(`耗时(ms): `, t2 - t1);
}
await example()

输出结果:
[Running] node “demo/chatgpt.js”
陈平安|默然无言
耗时(ms): 2377
[Done] exited with code=0 in 2.671 seconds

如何将无头浏览器的操作录制为视频

2023-11-29admin阅读(924)评论(0)

之前一直使用 puppeteer 来进行一些业务操作, 但是近期因为需要将无头浏览器内部的操作录制为视频, 发现使用 puppeteer 来做有些 复杂, 要结合 ffmpeg 还要操作数据流之类的,

询问 chatGPT 发现有个更好用的自动化测试框架叫 playwright 有类似的能力,就浅浅尝试一下。

项目地址 : https://github.com/microsoft/playwright

首先我们安装一下: npm install playwright

随便写个 demo 试试

1
2
3
4
5
6
7
8
9
10
11
12
import {chromium} from "playwright";
 
(async () => {
const browser = await chromium.launch({
headless: false, // 非无头模式,方便观察录制效果
args: ['--start-fullscreen'], // 全屏模式
});
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://www.baidu.com/');
// 进行其他操作...
})();

若是遇到异常:

node:internal/process/promises:279
triggerUncaughtException(err, true /* fromPromise */);
^

可能是依赖没安装全, 重新安装一下: npx playwright install

最终配置如下:

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
import { chromium }  from "playwright";
import { exec } from "child_process";
(async () => {
const width = 1280;
const height = 720;
let browser = await chromium.launch({
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", // 自动查找浏览器的可执行文件路径
headless: false, // 非无头模式,方便观察录制效果
args: ['--start-fullscreen'], // 全屏模式
});
const context = await browser.newContext({
viewport: { width, height }, // 设置浏览器的初始大小
recordVideo: {
dir: 'recordings/', // 视频保存目录
frameRate: 60, // 视频帧率
size: { width, height }, // 视频分辨率
outputSize: { width, height }, // 输出视频大小
videoSize: { width: 1920, height:1080 }, // 视频大小
format: 'mp4' // 指定视频格式为 MP4
}
});
const page = await context.newPage();
await page.goto('https://vr.he29.com/tour/index?id=65432d6218a3326&h=1');
setTimeout(async (_)=> {
const video = await page.video(); // 获取录制的视频对象
const videoPath = await video.path(); // 获取录制的视频文件路径
console.log('录制的视频文件路径:', videoPath);
const outMp4 = videoPath.replace('.webm', '.mp4');
const run = `/Applications/ffmpeg -i ${videoPath} ${outMp4}`;
console.log(`run`, run)
exec(run);
await browser.close();
}, 5000)
})();

 

可以正常录制到 webm 格式的视频, 后面使用 ffmpeg 转为 mp4 就可以了

不过目前录制的视频清晰度有些低,目前还没有找到好的办法

Matomo 自定义埋点上报参数分析

2023-07-24admin阅读(2412)评论(0)

具体分析如下:

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
// 微信小程序内容追踪
const config = {
e_c: '点击', // 事件类别。不得为空。(例如。曝光,点击,浏览)
e_n: '快快快', //事件名称。(例如,打开网页, 曝光)
e_a: '导航条点击', // 事件操作。不得为空。(例如。播放,暂停,持续时间,添加播放列表,下载,点击...
ca: 1, // 代表自定义操作。 可以选择与任何不是网页浏览的跟踪请求一起发送。例如,它可以与事件跟踪请求一起发送。优点是,如果您禁用事件插件,那么事件跟踪请求将被忽略,如果未设置参数,即使页面视图不是页面视图,也会被跟踪页面视图。有关更多背景信息,请查看#16570。请勿将此参数与跟踪请求一起使用。
idsite: 1, // (必填)— 我们正在跟踪其访问/操作的网站的 ID。
rec: 1, // (必需) — 跟踪需要,必须设置为 1,例如 。&rec=1
r: 438192,
h: 16, // 当前小时(本地时间)。
m: 12, // 当前分钟(本地时间)。
s: 50, // 当前秒(本地时间)。
url: 'https://vr.he29.com/', // (推荐)— 当前操作的完整 URL。
uid: '天明(1000)', // 定义此请求的用户 ID
_id: ' 052f83fae07d81ae', // (推荐)— 唯一的访客 ID 必须是 16 个字符的十六进制字符串。必须为每个唯一访客分配一个不同的 ID,并且此 ID 在分配后不得更改。如果未设置此值,Matomo (以前称为 Piwik) 仍将跟踪访问量,但唯一身份访问者指标可能不太准
cookie: 1,
res: '1920x1080', // 访问者正在使用的设备分辨率,例如 1280x1024。
urlref: '', // 完整的 HTTP 引用网址。此值用于确定某人如何访问您的网站(即,通过网站、搜索引擎或广告系列)。
ua: '',
rand: '', // (推荐)— 用于保存在每个请求之前生成的随机值。使用它有助于避免浏览器或代理缓存跟踪请求。
apiv: '', // 参数 &apiv=1 定义要使用的 api 版本(当前始终设置为 1)
c_n: "", // 内容跟踪 内容的名称。例如“Ad Foo Bar”
c_p: "", //  实际内容片段。例如,图像、视频、音频、任何文本的路径
c_t: "", //  网址
c_i: "", //  与内容交互的名称。例如“点击”
}

数据填充后使用 GET 请求写入即可上报!

使用ws开发简易的聊天室

2023-07-24admin阅读(612)评论(0)

ws 是什么?

https://www.npmjs.com/package/ws

ws 是一个 nodejs 的 webscoket 库, 用来创建 websocket 服务。

下面用简单的思路来实现一个聊天室应用的 nodejs 端服务。

JavaScript
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import WebSocket, { WebSocketServer } from 'ws';
import express from 'express';
import chalk from 'chalk';
 
const pageConfig = {
ping: 5000, // 心跳检测
msgPort: 3000, //内部推送端口
socketPort: 8020, // socket端口
}
const app = express();
const wss = new WebSocketServer({ port: pageConfig.socketPort });
const groups = new Map();
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
const data = JSON.parse(message);
ws.uid = data?.uid ?? '';
const action = data?.action ?? '';
const room = data?.room ?? '';
sameUser(ws); // 同一个用户在不同的客户端打开; 则把这些用户加入到一个房间内
if (action === 'login') {
// 登陆系统; 返回令牌
replyData({}, action, ws);
}
// 加入房间
if (action === 'join') {
// 加入到房间;
room && addGroup(room, ws);
}
// 数据转发到其他room
if (action === 'transmit') {
// 将本次数据转发到此分组所有用户
sendMessageToGroup(room, data, action);
}
// 其他操作
if (action === 'other') {}
});
  ws.on('close', function close() {
  console.log(ws.userId, '离开了');
    groups.forEach(function (value, key) {
      if (value.has(ws)) {
        value.delete(ws);
        if (value.size === 0) {
          groups.delete(key);
        }
      }
    });
  });
});
/**
* 回复消息给客户端
* @param data
* @param action
* @param ws
*/
const replyData = (data, action = '', ws) => {
const config = JSON.stringify({
txt: data,
action,
time: new Date().getTime(),
uid: ws.uid
});
console.log(chalk.green(`🚀 回复消息`), action, config);
ws.send(config);
}
/**
* 加入到房间
* @param group
* @param ws
*/
const addGroup = (group, ws) => {
if (group) {
if (!groups.has(group)) {
groups.set(group, new Set());
}
groups.get(group).add(ws);
console.log(chalk.red(ws.uid), chalk.blue('加入了房间'), group);
}
}
/**
* 多个客户端相同 UID 用户处理
* @param ws
*/
const sameUser = (ws) => {
// 查找所有在线用户里 uid 等于自己的, 加一个分组里;
// Set 会自动过滤重复的
wss.clients.forEach(function each(client) {
// 把所有的自己都加入到 room Id 是 uid 的组里;
if (client.readyState === WebSocket.OPEN && client.uid === ws.uid) {
addGroup(ws.uid, client);
}
});
}
/**
* 推送消息给房间(分组)
* @param group
* @param message
* @param action
*/
const sendMessageToGroup = (group, message, action = '') => {
if (groups.has(group)) {
groups.get(group).forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
replyData(message, action, client);
}
});
}
}
/**
* 给所有人发消息
* @param txt
*/
const sendAll = (txt) => {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(txt);
}
});
}
app.get('/', (req, res) => {
const params = req.query;
sendAll(params?.txt ?? '你好');
res.send('Hello, this is a GET request!');
});
// 给房间发消息
app.get('/send', (req, res) => {
const params = req?.query ?? {};
const action = params?.action ?? '';
sendMessageToGroup(params?.room, params?.txt ?? '你好', action);
res.send('Hello, Send Success');
});
// 获取房间个数
app.get('/count', (req, res) => {
let size = 0;
groups.forEach(function (value, key) {
const room = req?.query?.room ?? '';
if (key === room) size = value?.size;
});
res.send(size.toString());
})
// 心跳检测;
setInterval(() => {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
replyData(`Hello ~ 你还在吗?`, 'ping', client);
} else {
// 删除已关闭的客户端
groups.forEach(function (value, key) {
console.log(`名称`, key, '在线个数', value.size);
if (value.has(client)) {
value.delete(client);
if (value.size === 0) {
groups.delete(key);
}
}
});
}
});
}, pageConfig.ping);
 
app.listen(pageConfig.msgPort, () => {
console.log(chalk.blue(`内部推送服务已启动: ${pageConfig.msgPort}`));
});

 

服务包含了心跳检测, 多分组, 内部推送服务等!

使用node-fetch调用其他网址的 chatgpt 接口

2023-04-14admin阅读(867)评论(0)

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
const fetch = require('node-fetch');
const config = {
"prompt": "马化腾是谁",
"options": {
"parentMessageId": "chatcmpl-756yYyWTlMacS71BLTcBtxCQZw3rj"
},
"systemMessage": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\nKnowledge cutoff: 2021-09-01\nCurrent date: 2023-04-14"
};
fetch("http://43.143.166.59:3000/api/chat-process", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"content-type": "application/json",
"Referer": "http://43.143.166.59:3000/",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": JSON.stringify(config),
"method": "POST"
}).then(response => {
return response.text()
}).then(res=>{
const answer = [];
res.split(`\n`).forEach((value)=> answer.push(JSON.parse(value)))
return answer;
}).then(list=> {
return list[list.length-1];
}).then(out=> {
// 下一次回复, 需要携带上一次的 parentMessageId
console.log(out)
})

 

puppeteer的简单使用方法

2023-04-13admin阅读(625)评论(0)

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
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const https = require('https');
(async () => {
const browser = await puppeteer.launch({headless: true, args: ['--window-size=814,896', '--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1']}); // launch browser in non-headless mode and set user agent to iPhone 13
const page = await browser.newPage();
await page.goto('https://skybox.blockadelabs.com/');
// Wait for 2 seconds
await page.waitForTimeout(3000);
// Find the button element on the page with the text "Confirm" and click it.
// Find the button element on the page with the text "Confirm" and click it.
const elements = await page.$x('//*[@id="radix-:R36ml9:"]/div[2]/button');
console.log('elements', elements)
if (elements.length > 0) {
await elements[0].click();
}
console.log('input', 'input');
// Use page.$ to select the input element using the provided CSS selector
const inputElement = await page.$('body > div.container.absolute.inset-x-0.bottom-12.z-50.w-full.max-w-5xl.sm\\:bottom-10 > form > div.flex.w-full.flex-wrap.justify-between.gap-2.sm\\:flex-nowrap > div.relative.w-full > input');
 
// Type the text "你好" into the input element
await inputElement.type('Ultraman eats hot pot');
 
// Press the enter key
await inputElement.press('Enter');
// hidden underline underline-offset-2 sm:block
// disabled:opacity-20
// Intercept browser download and get download link
 
// Wait for the .psv-loader-container element to become visible
await page.waitForSelector('.psv-loader-container', { visible: true, timeout: 60*60*24*1000 });
console.log('完成')
 
page.on('response', async (response) => {
const contentType = response.headers()['content-type'];
if (contentType.startsWith('image/')) {
const url = response.url();
console.log('Image URL:', url);
// Use Node.js built-in 'fs' module to write the image data to a file
// Extract the filename from the URL using the path module
const filename = path.basename(url);
// Use the https module to send a GET request to the URL
https.get(url, (response) => {
// Create a write stream to the desired file location
const file = fs.createWriteStream(`./${filename}.jpg`);
// Pipe the response data to the file stream
response.pipe(file);
// Log a message when the download is complete
file.on('finish', async () => {
console.log(`Download complete: ${filename}`);
// Close the browser and exit the system
// Close the current page
await page.close();
await browser.close();
process.exit();
});
// Use the nodemailer module to send an email with the downloaded image as an attachment
});
}
});
 
// Find the first button element inside the form element and click it
await page.click('body > div.container.absolute.inset-x-0.bottom-12.z-50.w-full.max-w-5xl.sm\\:bottom-10 > form > div.flex.w-full.justify-between.border-b-2.border-black > div.flex.place-items-center.gap-4.sm\\:gap-5 > button:nth-child(1)');
// Intercept the download by listening to the 'response' event of the page object
// Filter the responses to only include those with a content-type of 'image'
// Extract the URL of the image from the response and log it to the console
})();

 

网页端使用JS识别人脸并且自动打马赛克

2023-03-30admin阅读(962)评论(0)

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>人脸识别</title>
    <script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script>
</head>
<body>
<div class="content" id="app">
    <input type="file" id="image-input">
    <canvas id="canvas"></canvas>
</div>
<script>
    const imageInput = document.getElementById('image-input');
    Promise.all([
        faceapi.nets.ssdMobilenetv1.load('/models'),
        faceapi.nets.faceLandmark68Net.load('/models'),
        faceapi.nets.faceRecognitionNet.load('/models'),
    ]).then(start);
 
    function start() {
        imageInput.addEventListener('change', async () => {
            const imageFile = imageInput.files[0];
            const imageSrc = URL.createObjectURL(imageFile);
            const image = await faceapi.fetchImage(imageSrc);
            const detectionOptions = new faceapi.SsdMobilenetv1Options({
                minConfidence: 0.25, // 以控制需要检测到人脸的最小置信度分数。
                minFaceSize: 100, // 参数以检测较小的人脸
                maxFaceSize: 300, // 参数以检测较大的人脸
            });
            const detections = await faceapi.detectAllFaces(image, detectionOptions);
            const canvas = document.createElement('canvas');
            canvas.width = image.width;
            canvas.height = image.height;
            document.body.appendChild(canvas);
            const context = canvas.getContext('2d');
            context.drawImage(image, 0, 0);
            detections.forEach((detection) => {
                const { x, y, width, height } = detection.box;
                const centerX = x + width / 2;
                const centerY = y + height / 2;
                const radius = Math.max(width, height) * 0.5;
                const gradient = context.createRadialGradient(centerX, centerY, radius / 3, centerX, centerY, radius);
                gradient.addColorStop(0, 'rgb(255,255,255)');
                gradient.addColorStop(1, 'rgba(255, 255, 255, 0.8)');
                context.beginPath();
                // 绘制椭圆
                context.ellipse(centerX, centerY, radius, (radius+4), 0, 0, 2 * Math.PI);
                context.fillStyle = gradient;
                context.fill();
            });
            const newImageSrc = canvas.toDataURL();
            const newImage = document.createElement('img');
            newImage.src = newImageSrc;
            document.body.appendChild(newImage);
        });
    }
</script>
</body>
</html>

 

tinygo 安装及编译 wasm 笔记

2023-01-17admin阅读(2983)评论(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阅读(961)评论(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阅读(1136)评论(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的知识点还有很多,有比较深入了解的同学也欢迎补充

  • 上一页
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • ...
  • 下一页
  • 共 16 页
关于我

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

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

热门文章

  • 踩坑记录——iphone上safari开启隐身模式时localStorage变为只读-雅荷心语博客踩坑记录——iphone上safari开启隐身模式时localStorage变为只读2017-02-21评论(4)
  • 程序员是怎样一群人-雅荷心语博客程序员是怎样一群人2015-12-08评论(3)
  • 百度你个大毒瘤 - 吐糟博客这几天打不开事情-雅荷心语博客百度你个大毒瘤 – 吐糟博客这几天打不开事情2015-12-28评论(2)
  • PHP 非对称加密 openssl 加密及解密方法-雅荷心语博客PHP 非对称加密 openssl 加密及解密方法2016-05-17评论(2)
  • PHPStorm10 下载安装破解汉化-雅荷心语博客PHPStorm10 下载安装破解汉化2015-12-15评论(2)
2025年8月
一 二 三 四 五 六 日
« 六    
 123
45678910
11121314151617
18192021222324
25262728293031

最新评论

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

其他类型

  • 芊云全景
  • 配音兔AI配音神器

博客类型

  • 芊云全景
  • 配音兔AI配音神器

左邻右舍

  • 易水寒
  • 楼教主
  • 芊云全景
  • 贤心
  • 配音兔AI配音神器

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

联系我们关于我们

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