0%

用 Axios 获取 EventStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const response = await axios.get('https://stream.example.com', {
headers: {Authorization: `Bearer ${token}`,
responseType: 'stream'
});

const stream = response.data;

stream.on('data', data => {
console.log(data);
});

stream.on('end', () => {
console.log("stream done");
});

使用 Go 获取 IP 地址和城市

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

package main

import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"log"
"net/http"
)

func main() {
fmt.Println("测试")
router := gin.Default()

var dbPath = "ip2region.xdb"
searcher, err := xdb.NewWithFileOnly(dbPath)
if err != nil {
fmt.Printf("failed to create searcher: %s\n", err.Error())
return
}

defer searcher.Close()

// 获取 IP
router.GET("/ip", func(c *gin.Context) {
ip := c.ClientIP()
c.String(http.StatusOK, ip)
})

// 获取 IP ip2region 数据
router.GET("/ip2region", func(c *gin.Context) {
ip := c.ClientIP()
data, err := searcher.SearchByStr(ip)
if err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK, data)
})

router.POST("/post", func(c *gin.Context) {

json := make(map[string]any) //注意该结构接受的内容
err := c.BindJSON(&json)
if err != nil {
return
}
log.Printf("%v", &json)
c.JSON(http.StatusOK, gin.H{
"text": json["text"],
"password": json["password"],
})

})
err = router.Run(":8080")
if err != nil {
fmt.Println("err:", err)
}
}

后记

发现 Nginx 可以通过 proxy_set_header 来解决 CORS 问题,Caddy 也可以通过 header 来解决。

代码

一般情况下的部署是这样的,但是没有转发后端接口

1
2
3
4
5
6
7
:80 {
header / {
Access-Control-Allow-Origin *
Access-Control-Allow-Methods GET, POST, OPTIONS
Access-Control-Allow-Headers Origin, X-Requested-With, Content-Type, Accept
}
}

如果需要转发后端接口,可以这样

1
2
3
4
5
6
7
8
:80 {
header /api/* {
Access-Control-Allow-Origin *
Access-Control-Allow-Methods GET, POST, OPTIONS
Access-Control-Allow-Headers Origin, X-Requested-With, Content-Type, Accept
}
reverse_proxy /api/* http://localhost:3000
}

比较成熟的方案

一般方案

1
2
3
4
5
6
7
:8088 {
reverse_proxy /* http://xxxx:80 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_down Access-Control-Allow-Headers *
header_down Access-Control-Allow-Origin *
}
}

带模块的方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(cors) {
@cors_preflight method OPTIONS
@cors header Origin {args.0}

handle @cors_preflight {
header Access-Control-Allow-Origin "{args.0}"
header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE"
header Access-Control-Allow-Headers "Content-Type"
header Access-Control-Max-Age "3600"
respond "" 204
}

handle @cors {
header Access-Control-Allow-Origin "{args.0}"
header Access-Control-Expose-Headers "Link"
}
}

example.com {
import cors https://example.com
reverse_proxy localhost:8080
}

这是网上看到的,但是存在一个问题就是写死的不够智能,比如 Access-Control-Allow-Origin 的值是 *,在请求参数中有 Cookie 的情况下就不能实现动态的跨域了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(cors) {
@cors_preflight method OPTIONS
@cors header Origin {http.request.header.Origin}

handle @cors_preflight {
header Access-Control-Allow-Origin {http.request.header.Origin}
header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE"
header Access-Control-Allow-Headers "Content-Type, x-xsrf-token"
header Access-Control-Max-Age "3600"
header Access-Control-Allow-Credentials true
respond "" 204
}
}

:8088 {
import cors http://localhost:8088
reverse_proxy http://192.168.234.49:8080
}

还有个写法是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(cors) {
@origin{args.0} header Origin {args.0}
header @origin{args.0} Access-Control-Allow-Origin "{args.0}"
header @origin{args.0} Access-Control-Allow-Headers "content-type, x-requested-with"
header @origin{args.0} Vary Origin
}
myawesomewebsite.com {
root * /srv/public/
file_server
header Access-Control-Allow-Methods "POST, GET, OPTIONS"
@options {
method OPTIONS
}
respond @options 204
import cors https://member.myawesomewebsite.com
import cors https://customer.myawesomewebsite.com
}

后记

如果请求参数中有 Cookie 的情况下,需要设置 Access-Control-Allow-Credentialstrue,并且 Access-Control-Allow-Origin 的值不能为 *,需要设置为请求的域名。

参考

这个是占位符的文档,可以看到有很多占位符可以使用

这个是匹配器

这个是官方 Demo

Caddy 实战

还有个Whistle也可以解决跨域,这儿就不多说了

问题

在网页上选中文本,然后获取选中文本的所有 DOM 块元素。

解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var allWithinRangeParent = range.commonAncestorContainer.getElementsByTagName("*");

var allSelected = [];
for (var i=0, el; el = allWithinRangeParent[i]; i++) {
// The second parameter says to include the element
// even if it's not fully selected
if (selection.containsNode(el, true) ) {
allSelected.push(el);
}
}

console.log('All selected =', allSelected);
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
function getSelectedBlockElements() {
const selection = window.getSelection();
if (selection.rangeCount === 0) return [];
const range = selection.getRangeAt(0);
const startContainer = range.startContainer;
const endContainer = range.endContainer;
const startElement = startContainer.nodeType === 3 ? startContainer.parentElement : startContainer;
const endElement = endContainer.nodeType === 3 ? endContainer.parentElement : endContainer;
const commonAncestor = range.commonAncestorContainer;
const commonAncestorElement = commonAncestor.nodeType === 3 ? commonAncestor.parentElement : commonAncestor;
const startElementParents = [startElement];
let startElementParent = startElement.parentElement;
while (startElementParent !== commonAncestorElement) {
startElementParents.push(startElementParent);
startElementParent = startElementParent.parentElement;
}
const endElementParents = [endElement];
let endElementParent = endElement.parentElement;
while (endElementParent !== commonAncestorElement) {
endElementParents.push(endElementParent);
endElementParent = endElementParent.parentElement;
}
const blockElements = [];
let startElementParentsIndex = 0;
let endElementParentsIndex = 0;
let startElementParentElement = startElementParents[startElementParentsIndex];
let endElementParentElement = endElementParents[endElementParentsIndex];
while (startElementParentElement !== endElementParentElement) {
blockElements.push(startElementParentElement);
startElementParentsIndex++;
startElementParentElement = startElementParents[startElementParentsIndex];
}
blockElements.push(startElementParentElement);
return blockElements;
}

后记

问题

在网页上选中文本,然后触发事件

实现

1
2
3
document.addEventListener('selectionchange', function () {
console.log('selectionchange');
});

selectionchange 事件适用于:Android (Chrome) 和 iOS (Safari)。

contextmenu 事件适用于:Android(Chrome、Mozilla 和 Opera)。

后记

还有一种是监听 mouseup 事件,但是这种方式不太好,因为 mouseup 事件会在鼠标松开时触发,而不是选中文本时触发。

1
2
3
document.addEventListener('mouseup', function () {
console.log('mouseup');
});

移动端的话可以试试 touchstarttouchend 事件。

1
2
3
4
document.addEventListener("DOMContentLoaded", function(event) { 
window.addEventListener("touchstart", touchstart, false);
window.addEventListener("touchend", touchend, false);
});

问题

指定元素的开头和结尾,选中文本。

实现

1
2
3
4
5
6
7
8
function selectText(start, end) {
const range = document.createRange();
range.setStart(start, 0);
range.setEnd(end, end.childNodes.length);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}

后记

问题

在 Mac 上安装了 mysql,但是在 zsh 中输入 mysql 命令,提示 command not found mysql

1
zsh: command not found: mysql

解决

在 zsh 中输入

1
export PATH=${PATH}:/usr/local/mysql/bin/

然后输入

1
2
source ~/.zshrc   # If you use Oh-My-Zsh
source ~/.bashrc # If you use Default Bash

我的 DockerFile 是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM --platform=amd64 python:3
WORKDIR /app
ARG POETRY_VERSION=1.3.0
RUN apt-get update && \
curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install -y nodejs && \
pip3 install --no-cache-dir poetry
COPY package*.json ./
COPY pyproject.toml ./
# Install dependencies
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN poetry install && npm install
COPY . .
# poetry run python3 bin/openai-auth.py mtrucc@gmail.com Mt.r!openai132
# CMD ["poetry", "run", "python3", "bin/openai-auth.py", "mtrucc@gmail.com", "Mt.r!openai132"]
CMD ["node", "testChat.mjs"]
EXPOSE 3001

在 Mac m1 上运行的时候,报错

1
2
3
4
5
6
7
8
9
10
  'During handling of the above exception, another exception occurred:\n' +
'\n' +
'Traceback (most recent call last):\n' +
' File "/app/bin/openai-auth.py", line 47, in <module>\n' +
' raise RuntimeError("Could import revChatGPT. Please install it first. You can run `poetry install` to install it.")\n' +
'RuntimeError: Could import revChatGPT. Please install it first. You can run `poetry install` to install it.',
failed: true,
timedOut: false,
isCanceled: false,
killed: false

按照关键词理解,是说没有安装依赖,但是我在 DockerFile 里面已经安装了依赖

然后我往上翻,发现了一个有意思的报错

1
2
3
4
5
6
7
8
9
10
11
e>\n' +
" library = ctypes.cdll.LoadLibrary(f'{root_dir}/dependencies/tls-client{file_ext}')\n" +
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
' File "/usr/local/lib/python3.11/ctypes/__init__.py", line 454, in LoadLibrary\n' +
' return self._dlltype(name)\n' +
' ^^^^^^^^^^^^^^^^^^^\n' +
' File "/usr/local/lib/python3.11/ctypes/__init__.py", line 376, in __init__\n' +
' self._handle = _dlopen(self._name, mode)\n' +
' ^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
'OSError: /root/.cache/pypoetry/virtualenvs/chatbot-twitter-9TtSrW0h-py3.11/lib/python3.11/site-packages/tls_client/dependencies/tls-client-amd64.so: cannot open shared object file: No such file or directory\n' +

这个报错是说,我在 Mac m1 上运行的时候,找不到 tls-client-amd64.so 这个文件

然后我在 DockerFile 里面加了一行

1
FROM --platform=amd64 python:3

这段代码的意思是,我要在 Mac m1 上运行的时候,使用 amd64 的镜像,

然后就可以运行了

后记

这个报错还是雷罗同学帮我找到的,他跑我这个 DockerFile 的时候,一点问题都没有,我才意识到可能是 Mac m1 的问题

使用 Android Studio 打包安卓应用,需要签名,这里记录一下签名的过程。

Android之Keystore文件签名

Android Studio 生成签名文件

在 Android Studio 中,新建一个空白工程,构建后,会在项目的app目录下生成debug.keystore文件,这个就是签名文件。

签名文件的路径

1
2
3
4
# Windows
%USERPROFILE%\.android\debug.keystore
# Linux/Mac
~/.android/debug.keystore

签名文件的密码

1
2
3
4
# Windows
keytool -list -v -keystore %USERPROFILE%\.android\debug.keystore -alias androiddebugkey -storepass android -keypass android
# Linux/Mac
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

如果还是没有文件,可以在 Android Studio 中,点击Build->Generate Signed Bundle/APK,然后点击Next,然后点击Create New,然后输入密码,然后点击Finish,就会生成签名文件了。

或者 使用命令行生成签名文件

1
2
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000

这个是谷歌的文档,可以参考一下。

Signing Your App

其他