年更 again (今年其他事情比较多,blog 没太多干货分享啦
发现电信下发的 IPv6 连续连接十几天后会失效 (光猫路由 SLAAC 下发的,不知道是哪的问题),重新连接就好了.
NAS 需要 IPv6 才能连接 Zerotier (没有要公网 IPv4,马上双栈公网就莫得了[1]),最简单的办法是,每天查下 IPv6, 没有就重启.
第一时间大部分人都能想到,curl
一下 ifconfig.io
或者 ip.sb
这样.
1 | PS C:\Users\xiaopc> curl ip.sb |
那如果不走 HTTP 呢?通过 EDNS Client Subnet (ECS)[2],在 DNS 查询中递归解析器将客户端 IP 给到权威服务器,来实现地域化精细解析.
一个例子就是用 DNS 查 IP:
1 | PS C:\Users\xiaopc> nslookup -type=TXT o-o.myaddr.l.google.com ns1.google.com |
nslookup
不像 dig
,没有 +short
,那怎么把地址抽出来呢?
Powershell 名字里的 Power,它 Power 在哪呢?
比如说它的 builtin function:
1 | PS C:\Users\xiaopc> Resolve-DnsName -Name o-o.myaddr.l.google.com -Server ns1.google.com -Type TXT |
其实这个函数的输出是个对象 (毕竟是微软,肯定是 .NET 了),当然,Powershell 是强类型的.
(它是个 HashTable)
可以用管道符传给 Get-Member
看看有什么成员:
1 | PS C:\Users\xiaopc> (Resolve-DnsName -Name o-o.myaddr.l.google.com -Server ns1.google.com -Type TXT) | Get-Member |
那么就很简单了:
1 | PS C:\Users\xiaopc> (Resolve-DnsName -Name o-o.myaddr.l.google.com -Server ns1.google.com -Type TXT)[0].Strings[0] |
加上重启的判断:
1 | if (-not (Resolve-DnsName -Name o-o.myaddr.l.google.com -Server ns1.google.com -Type TXT)[0].Strings[0].StartsWith("240e")) { |
注:5:20
是关机原因—网络连接丢失(计划外)
但是弹黑框不好看,不妨借鉴一下一些红方用的持久化方法[3].
1 | PS C:\Users\xiaopc> powershell /? |
那么最终的命令就是:
1 | powershell.exe -NonI -c "if (-not (Resolve-DnsName -Name o-o.myaddr.l.google.com -Server ns1.google.com -Type TXT)[0].Strings[0].StartsWith(\"240e\")) { shutdown /r /t 120 /d 5:20 }" |
把它加到任务计划就行了.
注:在既没有 IPv4 又没有 IPv6 时(aka 断网)脚本会报错退出,这样正好满足需求.
[1] https://www.ithome.com/0/716/097.htm
[2] https://developers.google.com/speed/public-dns/docs/ecs
[3] https://www.freebuf.com/articles/system/229209.html
]]>新年好!
网上下载的某些 PDF 文档,经过层层转压,里面带上了一堆水印,很是影响阅读.
对于纯图片的文档(比如翻拍的纸质书)来说,来一个简单的脚本去除文本水印即可.(图片水印就不是一个脚本能解决的了)
PDF 是一种页面描述语言,而 PDF 文件由文件结构、文档对象,和一系列描述内容的操作符组成. 一段显示一串 8 的操作符序列如下:
1 | BT |
BT
和 ET
是文本 block 开始和结束,Tf
操作选择字体 F0
字号 192pt,Tm
是位置矩阵,Tj
是字符内容. 更多操作符可参考 PDF Explained 及标准文档.
再看这个脚本就很简单了,遍历每个页面对象,获取内容操作符列表,去掉 Tj
.
当然,还有待完善的地方,比如去掉整个文本 block, 根据文字删除等等.
水完了~
本文参考链接已标注
]]>半年更博主 ✅
虽然现在(应该、大约)没有 Android Kitkat API 19
以下的新设备了,但是为了响应欧盟号召,旧设备还是要好好利用啊.
目前大多数人接触得到的这类旧设备就是机顶盒了,而旧版 Android 内置的 OpenSSL 还支持 SSLv3(有 Poodle 漏洞),而且不支持现代的 TLSv1.3(HTTP/2 必须),这会导致许多连接问题.
(不关闭 SSLv3 的话,在 Qualys SSL Test 评估会直降一级)
由于这个问题在中文互联网好像没人写(除了 Stackoverflow 采集站以外),所以就来写一下.
如 TLDR 可直接看最后一节
Java 安全提供者 (JSP) 包括密码学扩展 (JCE) 和安全套接字扩展 (JSSE). 密码学扩展就是 OpenSSL、BoringSSL 这些密码库,安全套接字扩展则是使用密码库来处理 TLS/SSL 连接的 Client/Server 组件.
支持 TLSv1.3 的话,首先需要支持它的密码套件(比如说,加密算法必须支持 AEAD),这有赖密码学扩展支持.
而后,需要在协议握手时正确向目标提供密码套件列表(ClientHello/ServerHello),进行证书鉴别等等,然后正确完成建立连接这一系列过程,都是安全套接字扩展负责.
在 Kitkat 及以前,Android 内置的密码学扩展是 openssl-1.0.1e
,而到 1.0.2j
版本才支持,内置的安全套接字扩展自然没有 TLSv1.3 的支持. [1]
Google 近些年才想到把系统各组件拆开更新,而对于 JSP 则是通过 GMS 来提供,随着 Play Services 一起更新.
使用 GMS 的应用可以通过 com.google.android.gms.security.ProviderInstaller
安装 GMS 的一揽子 Provider:
然而,Kitkat 能安装到最新的 Play Services 版本只有 7.x,还是不够用.
如果只为了禁用 SSLv3 且使用最优的密码套件,NetCipher 只是修改 JSSE 的配置,几乎不会造成打包后体积变化.
'info.guardianproject.netcipher:netcipher'
提供了一个 StrongConnectionBuilder
套在 HttpURLConnection
外面,代码改动很小.
不过,它提供适用 okhttp
的构件 info.guardianproject.netcipher:netcipher-okhttp3
目前为止有 Bug 无法正常使用,且暂无更新计划. [2]
Conscrypt 是 Android 系统内置的 JSP,Google 将它独立了出来,使用自己的 BoringSSL 密码库,只需要在应用内置新版就行了.
但是不像它在 README 里说的那么简单,由于 Conscrypt 没有实现证书管理器 X509ExtendedTrustManager
而且也暂无更新[3]等缺失,所以需要额外实现.
适配 okhttp
的代码如下(后附一点点说明);
okhttp
对 API 19 的支持到 3.12.x 版本KeyManager[]
, TrustManager[]
, SecureRandom
, null 时使用系统默认第一个本文参考了:
[1] https://enzowyf.github.io/android_alpn.html
[2] https://gitlab.com/guardianproject/NetCipher/-/issues/17
[3] https://github.com/google/conscrypt/issues/848
[4] https://gist.github.com/Karewan/4b0270755e7053b471fdca4419467216
]]>再写 GA 相关的内容怕是要变成 GA 小站的样子了(水平弗如,正经内容还是得看人家写的
本月,被阿里收购的 CNZZ 正式砍掉了免费服务,百度统计免费版除基础数据以外的功能也关闭了,51.la 刚重构的 v6 突然承接到这两家的「难民」,也饱受巨大的流量冲击. 于此同时,Google Analytics 旧版「通用统计 Universal Analytics」也将于明年停止服务(统计代码以 UA-
开头).
之前(还在用 WordPress 时)写的Nginx 反代 Google Analytics 和 reCaptcha 实践貌似是本博客搜索量最高的文章,那么就来更新一下.
六年前大家还在自己搭环境建站,在 Serverless、边缘计算等风潮席卷后,现在当然要用最简单、最便宜的方式来做. Cloudflare Workers 可以部署 Node.js 应用(其他语言转换为 JS,但不完美). 免费额度是每天 100000 请求,每个请求最多 10 毫秒 CPU 时间.
Cloudflare 基础使用就不赘述了,在 Dashboard 里找到 Workers 开通就好. 创建服务那里,「启动器」只是选择初始模板,随便选即可.
编辑代码为:
要编辑的就是前三个变量,按注释说明修改. 这里路由地址可以先使用分配的 workers.dev
地址,测试成功以后再修改过来.
先测试一下代码是否正常加载,访问 https://example.workers.dev/routerpath/a.js
(根据变量修改为响应地址),在响应的 JS 里搜索 COLLECT_PATH
值,看是否成功替换.
然后按照 Google Analytics 里的说明安装统计代码,只是把 JS 地址改成上面这个. 安装后查看下页面,在 F12 Devtools 里看下有没有 POST 到 COLLECT_PATH
的成功 204 响应. 成功的话,就可以在 Google Analytics 的实时页面看到统计了.
接下来的可选操作是将自己的域名解析到 Worker.
如果域名是 NS 接入 Cloudflare 那就很简单了,假设域名是 example.com
想用 subdomain.example.com
,若这个二级域名已经使用且经过 Cloudflare(开启了橙色云朵)即可下一步,没有的话需要设置这个二级域名为任意记录,并开启橙色云朵. 然后在 Workers 里「触发器-添加路由」添加 subdomain.example.com/*
即可.
如果域名是之前用 Partners 方法用 CNAME 接入的(现在 Partners 已经不能新增接入了),可以在 Partners 那边给二级域名开启橙色云朵,然后同样进行第二步操作. 其实还有一个方法,Cloudflare Pages 支持 CNAME 接入,可以创建一个空白 Pages,CNAME 到 yourpages.pages.dev
,成功之后这个二级域名即接入了 Cloudflare 网络,那么还如前文所述一样操作即可.
当然,改完后不要忘记把 Workers 代码里的 DOMAIN
变量修改过来,还要更新页面安装的 JS 地址.
相比仅代理上报接口(Measurement Protocol),代理 gtag.js
的好处是不用自己维护这个文件的更新,且不用修改其他的代码(如使用 Google Analytics 做了自定义事件上报等等),甚至(或许可以,但未测试)支持 Tag Manager,功能更丰富. 当然缺点也显而易见,gtag.js
也会消耗一次请求,访问量较大时免费的请求数可能会吃紧.
最后回答为什么要反代 Google Analytics 这个问题,一方面是境内确实会有部分网络无法访问 google-analytics.com
,个人经验而言,同时使用 GA 和其他国内统计工具,GA 的数值会低 10% 左右. 另一方面,支持 BigQuery 的 GA4 对会 SQL 的使用者而言无疑非常强大,这是其他统计工具仍无可望其项背的.
最后强调一下,反代 Google Analytics 目前(唯一?)的问题是,Measurement Protocol 现在不能手动设置来源 IP,这意味着无法获取访客地理位置、运营商等信息. 不过如果在 Workers 使用如 lib-qqwry
去查询 IP 库后修改上报内容,或许不失为一种权宜之策.
本文参考了:
[1] https://placeless.net/blog/faster-ga4-with-cloudflare-worker
[2] https://blog.skk.moe/post/cloudflare-workers-cfga
]]>「众所周知」First Paint (白屏时间) 是评估前端体验的重要指标,减少首次渲染之前的空白时间能减少用户的焦虑感.
那么可能不「众所周知」的是,外部 (<link href>
) CSS 默认是以最高优先级加载的,在其加载完成前会阻塞渲染.
所以如果为了优化 FP 做了个加载中效果,却不处理外部 CSS,结果会是 FP 没有任何减少.
(图懒得截了,略)
这就是 LoadCSS [1] 的奇妙之处了:
1 | <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'; this.onload=null;"> |
media="print"
的优先级会被调成 Low,且不再阻塞加载. 在其加载完成后,用 onload
事件把 media
改回来以免影响样式.
这带来了新的问题,如果外部 CSS 多的话免不了要用 LoadCSS 这个 JS 库,这又引入了新的加载延迟;此外,onload
事件在禁用 JS 的浏览器上无法触发,样式无法应用.
解决禁用 JS 的问题,就是 Sukka 的版本[2]了:
1 | <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'; this.onload=null;"> |
(没想到吧.jpg)
还有一个没解决的问题,外部 CSS 很多,如果用 Webpack 打包页面,怎样自动给每个标签都应用上 LoadCSS 呢?
哈哈,其实到这里应该能猜到,本文其实是在介绍如何写 Webpack 插件.
「众所周知」的是,从模板生成到输出文件优化这一系列操作,是 Webpack 通过各种插件和加载器来处理的.
插件在 Webpack 运行生命周期上绑定各种钩子进行操作,也可以发布自定义钩子供其他插件使用.
这里来为使用比较广泛的 html-webpack-plugin
插件写一个 LoadCSS 的插件.
在 html-webpack-plugin
文档中,介绍了一个插件 link-media-html-webpack-plugin
,它的作用是根据 CSS 文件名自动添加 media
属性。
看起来和本文的需求差不多?直接来看看它的代码 (只保留了主要逻辑)[3]:
1 | class LinkMediaHTMLWebpackPlugin { |
来快速查一下 [4],再捋一捋逻辑:Webpack 启动以后会有个唯一的 compiler
对象,然后对每个要编译的模块走一遍 compilation
;每次启动先调用所有注册的 plugin
对象 apply
方法来初始化. 插件用 tap
来绑定同步钩子,这里绑定了 compiler
的 compilation
钩子,compilation
的 htmlWebpackPluginAlterAssetTags
钩子.
然后在 html-webpack-plugin
文档中 [5] 找到 alterAssetTagGroups
的定义:
1 | AsyncSeriesWaterfallHook<{ |
翻翻代码,在 [6] 找到 HtmlTagObject
的定义,顺便还在 [7] 找到了包装好的生成器方法.
html-webpack-plugin
文档中的示例代码:
1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
发现两个问题:一是之前获取 html-webpack-plugin
钩子的方法在新版中修改了;二是用的是 tapAsync
绑定的异步钩子.
修改过后的最终版本:
(只是跑通了,没有做优化,先就这样?)
本文参考了:
[1] https://github.com/filamentgroup/loadCSS
[2] https://blog.skk.moe/post/improve-fcp-for-my-blog/
[3] https://github.com/probablyup/link-media-html-webpack-plugin/blob/master/index.js
[4] https://segmentfault.com/a/1190000012840742
[5] https://github.com/jantimon/html-webpack-plugin#alterassettags-hook
[6] https://github.com/jantimon/html-webpack-plugin/blob/main/typings.d.ts#L260
[7] https://github.com/jantimon/html-webpack-plugin/blob/main/lib/html-tags.js
[8] https://juejin.cn/post/6844903713312604173
]]>前文说到,GA4 的全部回报均为事件驱动,那么数据就是一系列事件组成的集合,每个事件有一定的属性...
这不就是结构化数据嘛,结构化数据查询语言是... SQL 啊...
GA4 给到的原始数据表就是这样的:
不过看起来和传统关系型数据库有一点点不一样,这个下面再说.
打开 GA4 的管理 - BigQuery 关联,点击「关联」.
点击「选择一个 BigQuery 项目」,勾选一个 BigQuery 项目.
没有 BigQuery 项目,或是勾选后提示「所选的 Google Cloud 项目未启用 BigQuery API」怎么办呢?
那你一定没开通 Google Cloud 吧.
但不想开通(不想浪费掉新用户免费试用)怎么办呢?开一个 Firebase 就有了。
(不需要的可以跳过下一节)
Firebase 是 Google 的开发者平台,由于「混乱的大公司」问题,它并没有被合并到云服务部门,而且它还送独立的云服务资源.
打开 Firebase,创建一个免费的项目(这里就不截图了).
侧边栏齿轮 - Project Settings - Integrations - BigQuery:
这里写到,免费版提供的 BigQuery 是沙箱,只保留 60 天数据,最大 10GB.
然后点 Continue 继续,在第二步所有的选项都不勾选(可以看到,基本上提供的都是 App 开发服务),然后点 Link to BigQuery.
显示可用空间 10GB 就表示已经完成了.
回到刚才的 GA4 关联向导,这时就可以选刚才创建的 Firebase 项目提供的那个.
数据位置随便选,多个项目关联到同一个 BigQuery 的话,位置必须选一样的. 然后下一步.
勾选「每天」,当然付费用户可以选择流式.
每天的导出时间大概在 GMT 零点后,也就是北京时间早上八点.
点下一步,提交,配置转发就完成了,接下来就是等第二天看数据.
那么哪里才能找到 BigQuery 呢?当然是 Google Cloud 了.
Google Cloud 用户可以在导航栏的 Big Data 类目找到,也可以通过 https://console.cloud.google.com/bigquery 直接进入.
左边栏是功能及数据集树状图,右边分别是查询语句输入和查询结果窗格,和传统数据库客户端基本一样.
它的 SQL 也是标准的 SQL 语言,符合 SQL 2011 标准,有问题可以查官方文档.
(之前 BigQuery 用的不是标准 SQL,遗留问题不必管他)
数据的层次是项目-数据集-表,也很像. 数据集默认命名是 analytics_统计ID
,表就是 events_
.
(这里是把每日的 events_日期
表合并显示了)
选中 events_
表,点 Query Table 会自动写当前表的 SELECT FROM
语句.
可以看到,有语法检查. 这个语法检查有两个层级,一是语法检查,二是结合数据表检查.
(对真大数据玩家来说,可以节约一点时间)
统计跳出站外的链接这个需求,很少有统计系统去做.
GA4 里明明能看到 click
事件,还能在实时监控里看到最近的 link_url
事件属性,然而在统计里就是调不出来.
所有指标里就是没有这个属性,把这个属性设置自定义指标也拿不到数据,这就令人难受了.
(如果有调出来的方法的话可以留言)
那就只能用 BigQuery 看看.
但是,如果写一个这样的 SQL 查询:
1 | SELECT event_params |
会得到 event_params.key, event_params.value.string_value, event_params.value.int_value, event_params.value.float_value, event_params.value.double_value 这些项.
然而用点号访问,例如 event_params.value.string_value
,会得到一个错误:
Cannot access field value on a value with type ARRAY<STRUCT<key STRING, value STRUCT<string_value STRING, int_value INT64, float_value FLOAT64, ...>>>
event_params 被定义为嵌套了数组(ARRAY)和结构体(STRUCT)的数据,ARRAY 包括每个属性,每个属性有属性名和属性值,属性值还分 string/int/float...
啊,这就难到学老版本 SQL 标准的了. 不过感谢万能的 StackOverflow,文末的参考链接给出了一个辅助函数:
1 | CREATE TEMP FUNCTION paramValueByKey(k STRING, params ARRAY<STRUCT<key STRING, value STRUCT<string_value STRING, int_value INT64, float_value FLOAT64, double_value FLOAT64 >>>) AS ( |
定义了一个临时函数(每次请求都要定义)paramValueByKey('属性名', event_params)
,取出这个属性.
那么,统计 click
事件的 link_url
的次数就可以这么写:
1 | SELECT url, COUNT(*) AS count |
前面跟上辅助函数的实现,提交,就有了.
点击 Save Results 可以把结果导出到本地或者 Google Drive.
本文参考了:
https://stackoverflow.com/questions/41090396/how-to-select-multiple-custom-firebase-event-parameters-in-bigquery
]]>给一个 <td>
的 :hover
伪类随便写点 transition
过渡就可能遇到这样的情况:
但是,如果同时加了一点 3D 效果,比如 translateZ(0)
,问题就又消失了。
如果看一遍 DevTools 的性能面板,就能知道区别在哪:
没有 3D 效果时,重绘 (repaint) 的图层 (layer) 是整个文档;而有 3D 效果时,这个节点会被独立成一个图层进行渲染。
打开 DevTools - More tools - Layers 或是在 Rendering 工具里勾选 Layer Borders 即可验证。
当然,translateZ(0)
会有一些副作用,所以一般会用 backface-visibility: hidden
这个本来用来控制 3D 元素背部可见性的属性来达到同样的效果。
但是这带来了一个问题,底边没了:
如果给底边单独设双倍的高度,则可以正常显示。那么问题很明显,就是边框高度溢出了图层。
但是,这问题没有一致性,几乎一致的几个表格,不一定哪个会出现。
不过,打开 Layer Borders 可以发现一个规律:
回想之前的动效残留,可以发现这其实是同一问题。那么最简单的解决方案就是:
增大图层范围。这里增加一个 box-shadow
来撑开图层。
这时,如果把之前加的 backface-visibility: hidden
去掉,会发现它已经不再被需要了。
但是「众所周知」的是,box-shadow
会吃渲染性能的(尤其是没有优化的低版本浏览器)。
而如果去掉强制独立图层的话,整页渲染加上 box-shadow
无疑会降低性能。
这时候就要 will-change
属性,预先告诉浏览器,该元素将会对哪个属性添加动效。
浏览器支持方面,Chrome 和 Firefox 版本均为 36+。
即便预告了 box-shadow
,但优化还是很有限,因为毕竟还有重绘的过程。
那么 box-shadow
可以不重绘吗?
给 ::after
伪元素画上盒阴影,然后用 opacity
隐藏掉,待需要时再显示。
opacity
改变的元素,会被单独放在一个图层,而且透明度改变的性能成本较低。
不过,这样就没法用来解决第一个问题了。
所以结论就是,<table>
已经没人要用了 ≡(▔﹏▔)≡
本文参考了:
[1] https://stackoverflow.com/questions/28511539/the-underlying-magic-of-webkit-backface-visibility
[2] https://www.barretlee.com/blog/2015/10/14/a-incredible-bug-in-taobao-homepage/
[3] https://www.zhangxinxu.com/wordpress/2015/11/css3-will-change-improve-paint/
[4] https://juejin.cn/post/6844903584077889544
[5] https://segmentfault.com/a/1190000011337088
]]>结果发现,CNZZ 被友盟收购、再被阿里收购以后就从来没更新过了;
腾讯统计悄无声息地下线了(有个站用着,现在才发现 orz);
51.la 的管理页面还挂着一堆灰产的广告(马上要上的新版,终于加了页面内行为统计等等这些早就该有的功能);
百度统计好歹还能跟得上时代(只有它能拿到百度过来流量的关键词),但是分析功能要花钱钱...
那么,「yyds」Google Analytics 呢?
2020 年 10 月 Google 把应用分析和网页分析合并,推出了 Google Analytics 4. 它基于新的 gtag.js
框架,整合了 Google 所有分析、广告等服务.
v4 有几个好:
可以选择通过 Google Signal 将多端打通进行分析(开启后国内流量会因为无法访问 analytics.google.com
而无法加载);
管理控制台简化了逻辑,增强了自定义可视化分析能力;
不再基于 session(因应隐私保护要求提高),全部回报均为事件驱动;
全量数据可以导出至 Big Query.
Tag Manager(跟踪代码管理器)用来统一管理所有的第三方代码,只需要在页面中添加 Tag Manager 就可以动态设置接入的服务,还可以通过配置各种自定义代码来进行针对性的信息收集. 比如支持将 SPA 化为常规页面进行跟踪,收集自定义事件,定时回报等操作.
用 Google Analytics 和 Tag Manager 可能有两个问题,一是国内可能(概率性)由于众所周知的原因无法加载 JS,二是(有的)广告拦截可能直接把 Tag Manager 直接拦截掉,从而使全部代码失效.
如果不需要考虑这两点,那么就继续吧.
在管理菜单,旧版 Analytics(现称 Universal Analytics)会提供一个「GA4 设置助理」,可以自动配置新的「GA4 媒体资源」.
可以通过只部署新版/旧版统计代码,将数据转发至旧版/新版 Analytics(参考链接 1).
简单起见,这里使用建立新的 GA4 媒体资源. 现在默认流程创建的是 GA4 版,如需旧版需要在高级选项里选中「创建 Universal Analytics 媒体资源」.
创建 GA4 媒体资源后,添加网站「数据流」.
GA4 的「测量 ID」是以 G-
开头的,而旧版的「跟踪 ID」是以 UA-
开头的.
不急着添加页面内代码,因为接下来要用 Tag Manager 配置.
在 Analytics 左上角的账号切换菜单里,切换到跟踪代码管理器,创建账号,同时配置一个 Web「容器」.
容器创建以后,这个需要在 head
和 body
里添加的代码才是需要部署的.
在工作区中「新建代码」,选择「GA4 配置」,填入测量 ID,勾选发送浏览事件. 保存以后提交版本,统计就上线部署了.
以前做数字营销的,以及现在做「出海」增长的应该熟稔 Google Analytics 用来进行流量跟踪的 UTM 参数
,不了解的可以见参考链接 2.
简言之是在 HTTP GET 参数里添加 utm_source
utm_medium
utm_campaign
utm_content
这些参数来区分用户渠道,目前算是通用的标记形式.
但是国内市场上,每家都会搞自己的来源标记,像是微信分享出去的链接会加上 from=groupmessage&isappinstalled=0
这类参数,支付宝分享出去的会带 chInfo=ch_share__chsub_ALPContact
这样的.
那么怎么根据这些「小尾巴」区分流量来源呢?在旧版 Analytics 中,可以通过手动设定自定义渠道实现(参考链接 3); 或是在 Tag Manager 生成 UTM 参数,直接设置为 Analytics 的参数(参考链接 4).
这里在 Analytics 4 中实现后一种方法.
Tag Manager 有三个核心概念:代码、触发器和变量. 代码顾名思义,插入页面中用来执行某一动作;触发器可以感知用户操作、Javascript Event 等等;变量则是声明 Tag Manager 配置文件里的变量是从哪里获取.
此外 Tag Manager 还提供了模板功能,可以从模板社区中获得自定义的代码和触发器.
这里用到了 URL Parser
这个变量模板,作用是从 URL 中提取需要的片段. 其本质是一个自定义 Javascript 函数(等下就会用到).
添加了这个变量模板以后,开始创建需要的变量.
首先需要提取出分享参数,这里以支付宝的 chInfo
参数为例. 在「变量」中,先配置内部变量,勾选「Page URL」. 然后创建用户变量,类型为 URL Parser
,按下图配置. 带双花括号的是变量,可以通过输入框右边的快捷选中按钮添加.
这个变量的作用是,将 {{Page URL}}
中名为 chInfo
的请求参数的值提取出来.
同样的方法,再创建一个变量用来提取 {{Page URL}}
中的 utm_campaign
,变量命名为 Get utm_campaign
.
创建一个变量,类型为「自定义 Javascript」,代码如下:
代码很简单,就是如果 URL 自带 utm_campaign
就返回原来的值,否则就返回 chInfo
参数的值.
以此类推,按照同样的方法可以自定义 utm_source
utm_medium
这些参数.
最后,在原来的 GA4 配置中,设置字段名称覆盖默认值. 这里要注意,Analytics 4 使用的字段名与旧版的小写驼峰有所不同(旧版的见参考链接 5),目前还没找到官方文档.(一个方法是查看已统计数据中的字段名)
上线以后,有新流量以后就可以在 Analytics 4 的「流量获取」里看到来源了.
应用性能管理(Application Performance Management)在前端中的落地,主要是收集连接速度、元素渲染时间及资源消耗和运行错误这些数据,从而为前端性能优化提供参考.
不过现在各家的前端 APM 好像没有免费的,不如就用 Analytics 4 + Tag Manager 来实现一个简单的性能收集.
旧版 Analytics 其实也有速度统计,不过默认只抽样 1% 统计统计打开时间(可以修改,见参考链接 6),而且有最大样本数限制.
而 Analytics 4 所有数据均使用事件机制获取,事件种类最高 500 种,没有总量限制. 这里使用一个事件来收集性能信息.
先添加一个窗口加载就触发的触发器.
然后创建一段代码,类型是「自定义 HTML」,它会在页面中插入这段 HTML. 这里写一段被 <script>
标签包裹起来的 Javascript 代码,完整代码可以参考 这里.
大致上是使用 Performance API
获取性能数据(用法可以参考 MDN 或者参考链接 7),创建一个包含数据的事件,然后用 dataLayer.push
送到 Tag Manager 的数据层.
然后为数据层中需要上报的参数添加对应的「数据层变量」.
添加一个触发器,类型是「自定义事件」,事件名是刚才 push 到数据层的事件.
最后,新建一个代码,类型为「GA4 事件」,选择之前的 GA4 配置,把所有的数据层变量添加进来.
Tag Manager 提供了一个测试工具 Tag Assistant,只需要点击「提交」旁边的「预览」按钮就可以开始本地测试.
在 Tag Manager 测试时,Analytics 4 新增的 DebugView 也会同时启用,可以查看实时事件及接收到的数据.
接下来对 Analytics 4 收到的数据进行一些配置. 在「自定义定义 - 自定义指标」里为对应的参数设置名称.(自定义定义/指标一旦设置就不能彻底删除,只能选择归档)
在「分析」里可以添加自定义视图来对数据进行分析.
新建一个「探索」视图,图表选择表格,行可以在「维度」里选择,这里选择主机名和网页标题; 值可以在「指标」里选择,这里就选择事件数以及刚才定义的性能指标;过滤器设置为事件名完全匹配.
这里可能会发现一个问题,这里性能指标是所有样本的和,而不是更有意义的统计值.
解决办法嘛有两个,一是将在「管理 - BigQuery 关联」里配置,将原始数据导出至 Google Cloud BigQuery 进行分析,二是在视图右上角将视图导出到 Google Sheets(属于 Google Docs)进行分析.
也希望 Analytics 4 以后增加一些统计选项,直接 out-of-box 即用.
本文参考了:
[1] https://www.ichdata.com/docs/google-analytics-4/ga4%e7%9a%84%e5%b8%83%e7%bd%b2%e4%b8%8e%e8%b0%83%e4%bc%98/%e8%bd%ac%e5%8f%91%e5%b8%83%e7%bd%b2%e6%b3%95
[2] https://zhuanlan.zhihu.com/p/86453662
[3] https://www.ichdata.com/how-to-identify-wechat-traffic-in-google-analytics.html
[4] https://www.seerinteractive.com/blog/how-to-alter-your-campaign-values-using-google-tag-manager/
[5] https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference
[6] https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings
[7] https://serverless-action.com/fontend/fe-optimization/
[8] https://github.com/mikeg-de/HTML5-Performance-API-GTM-Script
[9] https://www.youtube.com/watch?v=nyD54NEn0ac
]]>2023-11-18 update: 三年后 IBM Cloud 砍了云函数业务,本文可以不用看了
最后来一些豆知识收尾。
.so
的 packages,务必在与 Cloud Function 容器相同的环境下安装到 virtualenv。 (否则会遇到诸如 ModuleNotFoundError
这种问题[1])1 | # ’python:3.7‘ 环境目前是: |
针对 Python 版本问题,一个解决方法是:
安装 pyenv;
安装编译 Python 所需依赖[2];
安装对应版本的 Python(从 python.org 下载安装包太慢的话,自行下载放至 pyenv 的
cache
目录下即可);安装 virtualenv(
pyenv-virtualenv
插件生成的 virtualenv 没有bin/active_this.py
);按照之前的步骤创建 virtualenv.
或者用 Docker 的解决方法,这里还有写默认环境有哪些 packages。
因为只需要 active_this.py
,所以 virtualenv/bin/
下 python*
的二进制软链接其实没用。 在 zip
打包时可以加 -y
参数,只打包软链接而不是二进制文件。(可减少 10MB+ 体积)
Cloud Function 上传 zip 包最大限制为 50MB。(fyi, numpy 就超了)
通过公开 RESTful API 执行函数,出错时客户端得到的代码并不是 「激活标志」,需要到日志查看。
通过公开 RESTful API 执行函数,传给 main
的 dict
里还有 __ow_body
, __ow_query
等内容,详见这里。
如果 Webhook 接口返回 HTTP 4xx 错误(main
有未处理异常)的话,Telegram 会一直重试;而 Cloud Function 是按调用次数计费的...
发送时间超过 48h 的消息是没法撤回的,尝试撤回的话 python-telegram-bot
会丢出异常。
Telegram 的 Markdown/MarkdownV2 都不支持表格,HTML 标签也只支持给定的几个。 (可以试试只用 Pillow 把表格画成图片,matplotlib 什么的就不要想了)
调用其他 Function 其实也是用 HTTP API,一个示例:
1 | APIHOST = os.environ.get('__OW_API_HOST') |
加上个 NoSQL,基本上已经可以解决大部分写 Bot 的需求了。
Cloudant 是基于 Apache CouchDB 的 NoSQL 数据库,用 RESTful API 访问,JSON 输出。免费层送 1GB 空间。
这是 CouchDB 的 API,Cloudant 因为要用 IBM Cloud 的认证,所以有所不同。
IBM Cloud 用了很多 Apache 的东西,Cloud Function 用的也是 Apache Openwhisk。
如果基础云服务都有通用的协议的话,可能才会真的会有全部「上云」的那天吧。
本文参考了:
[1] https://stackoverflow.com/questions/58698406/aws-lambda-python-so-module-modulenotfounderror-no-module-named-regex-rege
[2] https://github.com/pyenv/pyenv/issues/240
]]>2023-11-18 update: 三年后 IBM Cloud 砍了云函数业务,本文可以不用看了
第二集,让它跑起来。
此时目录结构应该是这样的:
1 | telegram |
接下来是打包成 zip:
1 | $ zip -r target.zip *.py virtualenv |
(就是全部打包成一个 zip)
接下来是部署:
1 | $ ibmcloud fn action create telegram/repeat --kind python:3.7 target.zip --param BOT_API_KEY **********:****************************** --web true |
将上面的星号部分换成 Telegram 的 Bot key。
解释一下这条命令:
fn
是刚才安装的 Cloud Functions 插件, action
是「操作」
create
是创建函数,update
是更新, etc...
接下来第一个参数是函数名,可以带一级目录
--param
参数可以设置直接传入 args
的环境变量,在网页控制台的「参数」页面也可以设置
--web true
会为这个函数提供不需要认证的 HTTP 接口,如果设置为 raw
的话,传入的请求不会被解析成对象(纯文本);在网页控制台的「端点」页面也可以设置,接下来也要用
其他的用法看帮助:
1 | $ ibmcloud fn action create --help |
上传过程可能比较慢,还有可能上传成功了却没响应...
打开网页控制台,找到刚才创建的函数。在「端点」页面那里的 HTTP 方法
部分的 URL 应该类似这样的:
1 | https://us-south.functions.appdomain.cloud/api/v1/web/******%40*****.com_dev/*****/***** |
正如上面写的,后面加扩展名可以输出对应种类的 Content-type
,这里 Telegram Bot 的请求头会处理的。
这里要用到 Telegram Bot 的原始接口了。放心,就用这一下。
设置 Webhook:
1 | https://api.telegram.org/bot**********:******************************/setWebhook?url= |
星号换成 Key,后面接上接口地址,那么完整链接就是:
1 | https://api.telegram.org/bot**********:******************************/setWebhook?url=https://us-south.functions.appdomain.cloud/api/v1/web/******%40*****.com_dev/*****/***** |
随便找浏览器打开就行了。
然后查看一下状态:
1 | https://api.telegram.org/bot**********:******************************/getWebhookInfo |
会以 JSON 形式返回状态:
1 | { |
可以检查下是否设置成功,在出现调用失败的时候也能通过这个接口查看问题,以下为出现错误请求时的状态:
1 | { |
这样一个 Bot 就做好了~
如果刚才测试过接口的话,会发现每次响应都包含一个 code
。Cloud Functions 为每次请求都生成了一个 「激活标志」 ID,可以通过这个 ID 来进行调试,查看请求、输出等等日志信息。
在网页控制台的「监视」页面可以查看统计信息,还可以点击请求的激活标志查看日志。
而在 CLI 中可以通过这个命令打开日志 poll 推送:
1 | $ ibmcloud fn activation poll |
这里只显示基础日志(stdout、stderr 输出),查询详细日志:
1 | $ ibmcloud fn activation get **********激活标志********** |
到此,只用一个 Cloud Functions 做一个简单的 Bot 就完成了。
下一集是如何调用其他 Function,以及如何加嫖 IBM Cloud 其他服务了。
本文参考了:
[1] https://useless-ideas.hashnode.dev/building-a-telegram-bot-with-ibm-cloud-functions-ck7dmyvi7000c8qs1tuklfuf6
[2] https://core.telegram.org/bots/api
]]>2023-11-18 update: 三年后 IBM Cloud 砍了云函数业务,本文可以不用看了
网上的教程都是教白嫖 Cloud Foundary 建站开梯子的,弄得 IBM 都开始封 IP 了,哼。 (*  ̄︿ ̄)
来玩点正经的吧!今天用 Serverless 来做 Telegram Bot。
IBM Cloud 的免费 tier 有一个好,不用绑信用卡(这也是被薅太多的原因之一)。
在 https://www.ibm.com/cloud/free 能看到免费项目,这里首先要用 IBM Cloud Functions。
Cloud Functions 是 IBM 提供的 Serverless 服务,类似阿里云的函数计算、腾讯云的云函数。与 App Engine 服务不同的是,函数计算只在被触发时才会运行,在限定执行时间返回结果,不能持久运行。
IBM Cloud 给了 500 万次/月的免费额度,阿里云和腾讯云也有一定的免费额度。不过这次是来做 Telegram Bot,这里就来嫖 IBM 的。
注册的时候,如果在最后一步遇到错误,那么可能是被 ban IP 了,可以试试挂梯子注册...
注册以后来到网页控制台里的 Cloud Functions。控制台里有这些选项卡:
操作:这里是函数列表。
触发器:这里可以添加从 IBM Cloud 其他服务或第三方消息源收到信息后触发某个函数,这里还用不到。
API:把函数整理成 API,目前还用不到。
监视:能看到最近请求的日志
名称空间设置:namespace,顾名思义。默认分配的位于达拉斯,名称是邮箱。也可以添加其他地方,但这里有坑,后面再说。
如果在网页控制台里新建操作,会发现它只给了个单文件代码输入,而且还没办法添加 packages,这就很鸡肋了。
所以要安装 CLI 来在本地写代码:(下面是 Linux 安装,其他安装方法见这)
1 | $ curl -fsSL https://clis.cloud.ibm.com/install/linux | sh |
登录,注意这时要选区域,如果是达拉斯那就是 us-south
:
1 | $ ibmcloud login |
需要额外设置「组织」和「空间名」两个设置,在网页版右上角「管理-账户-Cloud Foundry 组织」可以找到组织名(一般是账号邮件地址),点进去有空间名(一般是 dev
)。
前面提到的坑在于,在 Cloud Functions 里建立的命名空间不是基于 Cloud Foundary 的,无法用 CLI 来进行操作(aka 只能用网页操作)。而 Cloud Foundary 在免费 tier 只提供了 us-south
,所以东京等等节点基本上没法用...
2021.1.25 update
(基于 IAM 而不是 Cloud Foundary 的命名空间) 其实可以用 CLI,区域选新命名空间所在的区域,然后用ibmcloud fn property set --namespace '命名空间'
为 Function 单独指定即可,不需要设置 Cloud Foundary 相关参数.
2021.9.14 update
如果需要设置上述命名空间,现在需要先设置资源组。在「管理-账户-资源组」找到标识(一串十六进制),用ibmcloud target -g 标识
命令设置.
1 | $ ibmcloud target -o 组织名 -s 空间名 |
设置好以后查看下账号信息,应该是这样的:
1 | $ ibmcloud target |
(资源组可以不设置)
然后安装 Functions 插件:
1 | $ ibmcloud plugin install cloud-functions |
这样 CLI 就配置好了。
首先要有 Telegram 账号
搜索 @BotFather 按照提示建立就好。
记录一下 HTTP API 的 token **********:******************************
。
这里选择是用 Python 3.7 的环境,使用 python-telegram-bot 这个库。
首先建立一个名叫 virtualenv
的 virtualenv...(好像其他名字不认)
1 | $ virtualenv virtualenv |
安装依赖:
1 | $ pip install python-telegram-bot |
主入口是 __main__.py
,代码如下:
1 | from telegram import Bot, Update |
代码里不设置 token,在部署的时候会作为参数传入。
这就完成了一个无情的复读机 (o=^•ェ•)o ┏━┓
如何部署,见下回分晓...
本文参考了:
[1] https://medium.com/@aliabdelaal/telegram-bot-tutorial-using-python-and-flask-1fc634da9522
]]>internationalization, 抛去首尾是 18 个字母。
在后端做 i18n 时一般是通过检查 HTTP 头 Accept-Language
来判断语言的。(也有加 IP 判断什么的那种)
前端没法读到这个请求头,而是通过 navigator.language
返回一个首选语言,此外还有 navigator.languages
(实验性)提供如 Accept-Language
那样的语言优先顺序列表。
当然,如果已经习惯了各家浏览器对 W3C 标准的差异实现的话,对下面的内容应该不奇怪:
IE<=10 是 navigator.browserLanguage
和 navigator.userLanguage
。userLanguage
是系统语言,Internet 选项
仅对 Accept-Language
有效。
IE<10 和 Safari<10.2 的语言代码没有按 IETF 语言标签规范,是全小写的。
已经有一份比较详细的说明了,直接甩链接:
https://github.com/CommanderXL/D-i18n
目前搜索引擎对 JS 动态生成的内容基本不怎么支持(Google 说它们的 bot 支持一部分 JS 也只是处理跳转)。
要是真看重这些的话,还是分站的比较好(在 url param 里放语言标识符效果也不好)。
文本替换只是第一步,能正确显示出来是第二步。CJK 文本和拉丁文本在显示位置、字体 hinting 等等都还要处理。
这还没提到 RTL(right-to-left) 的阿拉伯语、希伯来语等,这种即使文字能正确从右至左显示,在展现方式等方面调整几乎无异于重新设计了。
internationalization 离 localization 还是有点距离的啊。
本文参考了:
[1] https://developer.mozilla.org/zh-CN/docs/Web/API/NavigatorLanguage/language
]]>首先,有线链路必须满千兆(从光猫口出发都是千兆口,网线在超五类及以上);
其次,至少 WiFi 5(802.11ac),而 WiFi 4(802.11n)也只有在理论速度上能用了...
再次,信号强度要够强,信号弱的情况下协商出来的速率有可能还不如用 2.4G 来的高。
AC+AP (无线控制器+接入点)当然是很成熟的技术了,大型场所的无线网络配置就选它。
但是在上面列的条件下,它也许并不合适。
狗东上最便宜的四口千兆 AC 都在 450+(当然了,搞软路由也是可以,但是一般家庭网络应该不会搞个这个玩意吧)。
还需要那么二三四个千兆 AP,总共加起来八九百的样子。
接下来就是 Mesh 了。
无线 Mesh 技术是一种与传统无线网络完全不同的新型无线网络技术。在传统的 WLAN 中,每个客户端均通过一条与接入点(AP)相连的无线链路访问网络,用户若要进行相互通信,必须首先访问一个固定的AP,这种网络结构称为单跳网络。而在无线 Mesh 网络中,任何无线设备节点都可同时作为路由器,网络中的每个节点都能发送和接收信号,每个节点都能与一个或多个对等节点进行直接通信。
简而言之,网络内的无线路由器互相连接,设备连接到一个无线路由器后,如果因为信号不好等原因,可以无缝切换到另外一个路由器。
那这技术这么好,是不是随便整两台路由器连起来就能用?
Mesh 是一个组网方案,并没有一套标准。比如华硕叫 AiMesh,TP-LINK 叫易展,等等。目前(貌似)只有同一生产商之内才能互认。
因为便宜。( o=^•ェ•)o ┏━┓
比 TP-LINK WDR7650 这款便宜的只有一款 H3C B5mini,后者看评测信号功率稍小一点,就选了前者。
(以后有机会的话也可以测测 B5mini,当然要是有钱的话也是可以测测华硕的 (●ˇ∀ˇ●)
如果超过了 5 台的话,还是 AC+AP 更好,带机量更高些。(这是给住大平层/别ye朋友的建议)
还是贴两张开箱图:
两台路由器,两个电源适配器,一根(不到一米?)超五类线,一本不用看系列,右上角那张用来记密码的纸。
(那张纸感觉没什么用,要记的人自然有纸本,不记的人也不会用...)
疫情期间,奠信搞了个活动,用「小翼管家」App 可以领三个月 200M 提速包。(就不放链接了)
昨天学校又发了个通知,「停课不停学」可以直接升 300M 三个月,所以目前网络环境是 300Mbps 下行,50Mbps 上行。
(但是在写这篇 blog 的时候又跑不满了,黑人问号)
网络拓扑和平面图如下:
青色的 ABC 三点是有线网络连接的地方,红色的 a-h 是房间。
(后文的漫游测试均为从 a 到 h 的顺序)
有线网络的布线在装修的时候就没考虑过,直接就是开发商预埋的样子,导致网络布局不合理。
然后这个光猫还只有一个千兆口(刚开始配置的时候速度一直跑不上去,还找了半天问题  ̄へ ̄),所以只能用菊花链的方式连接两台路由器了。
(只要两台路由器在同一个网络下就可以)
有懂的朋友可能要问了,Mesh 可以无线连接两台路由器,不用有线啊?别急,后面会解释。
测试了笔记本,iPad 和手机,下文中的测试设备均为某厂 Mate 20 Pro with Android 10,2*2 MIMO。
总体上体验还好,各屋都能收到 5GHz 信号,协商速度都在 100Mbps 以上,大部分能达到 400Mbps,一半能达到 600Mbps 以上。
还是有两个小问题:
一是切换的阈值不好把握,测试设备要等信号强度到 -65dBm (左右)以下才会切换到另一个路由器,然而此时的协商速度已经比较低了。这对于要达到全屋都高速的目标来讲是个问题,有时候只能断开重连才能连接到最优的节点。
(App:Cellurar-Z)
二是漫游的过程中速度不是太稳定,信号好的时候也不一定协商到高的速度。
(App:WiFi 魔盒)
在没有 Mesh 的时候,无线回程就相当于一个无线中继,就是增加了一个单独的接入点,只是 SSID 和主热点相同而已。
效果嘛,只是信号格子看起来更满了而已,速度谁用谁知道。
那 Mesh 无线回程会好一些嘛,下面是测速结果:
有线回程是 B 点的路由器,无线回程把它放到了 f 点(阳台),因为在之前的漫游测试中都是在这个点位出现了漫游切换。
可以看到,无线回程的性能嘛,还行,但不是最好的状态。(无线回程在漫游测试的时候竟然没有发生切换,这就很奇怪)
再说说 MU-MIMO,简单来讲,所有连接的设备要都支持 MIMO 才能用上 MU-MIMO。
家里但凡有个老点的设备都会影响到,所以...就看自己需求了。一条解释视频
WiFi 6 (802.11ax)引入了 OFDMA,能让 MU-MIMO 真正好用。
那么,WiFi 6 再见!
]]>昨天大家都在看自己能不能领到那笔钱 ╮(╯▽╰)╭
(不知道怎么回事的点这)
在添加 SSH key 那里还可以添加 GPG key。那么什么是 GPG,GPG 有什么好处,怎样用 GPG 呢,接下来小编...
PGP(英语:Pretty Good Privacy,中文翻译“优良保密协议”)是一套用于讯息加密、验证的应用程序,采用 IDEA 的散列算法作为加密和验证之用。
PGP 本身是商业应用程序;开源并具有同类功能的工具名为 GnuPG(GPG)。PGP及其同类产品均遵守 OpenPGP 数据加解密标准(RFC 4880)。
(看到这一段就想到上信息系统安全那个课的时候 →_→)
使用过 Github 的网页 Git 编辑器或是在网页 merge,可能会注意到 commit 就有 Verified 标志,提示 This commit was created on GitHub.com and signed with a verified signature using GitHub’s key. 它的公钥是 https://github.com/web-flow.gpg.
(username.gpg
可以看任意用户的 GPG 公钥,username.keys
可以看任意用户的 SSH 公钥)
签名的目的是确认「这是你发的内容」,你使用私钥加密消息的 hash,任何有你的公钥的人可以用公钥解密得到这段 hash,从而可以确认内容就是你发的,并且没有被篡改。
它可以用来签名邮件、消息等等,Git 也支持用 GPG 签名 commit。
如果你用的 Windows,安装的是 Git on Windows,那么你很可能已经安装有 GPG 了。
检查一下 C:\Program Files\Git\usr\bin
(根据 Git 的安装目录调整),里面如果有 gpg.exe
那就是已经有了。
如果你用的是 GNU/Linux(→_→),那么很可能也有了。
如果用的是 macOS 得安装一下,link.
在终端输入 gpg
如果能找到的话就没问题。(不过可能要注意一下 GPG 版本最好大于 2
)
对 Windows 用户,最好用 Git Bash(C:\Program Files\Git\git-bash.exe
)。
1 | xiaopc@desktop MINGW64 / |
需要注意,邮件地址必须是绑定 Github 的地址,或是 username@users.noreply.github.com
这个 Github 提供的转发地址(隐藏邮件地址),并且邮件地址必须和 Git 设置的地址相同。
1 | xiaopc@desktop MINGW64 / |
GPG 的 keys 都是二进制存储的,要把公钥转换成编码的文本文件才能上传:
1 | xiaopc@desktop MINGW64 / # 下面 *** 就是上面的 ID |
Settings -> SSH and GPG keys -> New GPG key,将上面命令输出的内容复制过来,确定。
(就不放图了)
Git 的设置是有层级的。
系统级设置在 C:\Program Files\Git\mingw64\etc\gitconfig
.(GNU/Linux /etc/gitconfig
)
本地用户全局设置在 C:\Users\<用户名>\.gitconfig
.(GNU/Linux ~/.gitconfig
)
单个仓库的设置在仓库目录的 .git\config
.
在哪级设置都可以,方法是一样的。
1 | [user] |
就是正常的 commit 过程,只是会提示输入 key 的密码。
更多的操作可以看参考链接 1。
本文参考了:
[1] 用 PGP 保护代码完整性(六):在 Git 上使用 PGP https://linux.cn/article-10421-1.html
[2] Sign your git commits with tortoise git on windows https://dev.to/c33s/sign-your-git-commits-with-tortoise-git-on-windows-3mlf
[3] 在 TortoiseGit 中使用 GPG 签名 https://blog.rathena.cn/post/use-gpg-in-tortoisegit/
]]>(在讨论 nodemcu 中)
冰神:「天猫精灵没得靠谱的温度传感器,决定自己做一个」
窝:「米家空气监测仪啊」
冰神:「可是我不用小爱同学」
智能家居品牌都喜欢搞独占这一套(指国内)。
比如,只能接入米家的「米家智能插座 WiFi 版」只要 39 RMB,而 ZigBee 版要 69 RMB(还得再买个网关)才能接入 Homekit。
(虽然 ZigBee 版功能多一点)
用米家 WiFi 芯片的 IoT 设备只能接入米家的云服务,再加上现在国内貌似只有米家的生态链最齐全,感觉已经有米家垄断的影子了。
引述文档的一个中文版翻译 [1]:
Home Assistant 是一款基于 Python 的智能家居开源系统,支持众多品牌的智能家居设备,可以轻松实现设备的语音控制、自动化等。
简单讲,hass 就是一套统一的网关系统,与所有的硬件进行通讯,然后对外提供 Web GUI 和接口。
然后有人在天猫精灵的平台做了 hass 的适配,由于窝没有天猫精灵就不尝试了,链接在这。
目前官方适配的硬件列表在这,当然也可以自己写适配器,就参考官方文档啦。
推荐配置是树莓派 4B,树莓派可以直接用基于 Raspbian 的 Hass.io 镜像。(当然也有一些国情优化版,自行选择 ;D
也有 Docker 版,能装到群晖或者软路由什么的上面。
当然,如果是在本地测试的话,要求 Python 3.5.3+。
1 | pip3 install homeassistant |
默认端口号是 8123
,第一次启动是一大堆配置,用户名密码什么的。
(当然,推荐是用 venv)
连接 ZigBee 设备是比较简单的(见这),而直接连接云服务的 WiFi 设备就比较头疼了。
这类设备的接口被叫做 miIO
,走这个接口的设备见此。(想研究协议本身可以看这个)
与每个使用 miIO
的设备通信都需要一个 token,目前只能通过提取 Android 米家 5.0 至 5.0.19 版本的数据库文件 miio2.db
才能获取到。(4.x 实测已经不能显示此类设备)
(注意,这个 token 在重置网络后会失效)
有 root 就很简单,直接打开 /data/data/com.xiaomi.smarthome/databases/miio2.db
,里面 devicereord
表就有设备对应的 token。
没有 root 的话就只能用应用备份把数据库备份出来,再解包备份文件。
通过 adb 备份(不是所有的 ROM 都可以,窝用 Smartisan M1 就没法备份,当然用模拟器可能更简单):
1 | adb backup -noapk com.xiaomi.smarthome -f backup.ab |
然后用 adbextractor
(sourceforge) 提取: 1
java -jar ../android-backup-extractor/abe.jar unpack backup.ab backup.tar ""
SQLite 数据库有很多在线查看的工具,比如 https://inloop.github.io/sqlite-viewer/。
此外,配置还需要设备的 IP,看下路由表就有,刚才那张表的 localIP
列也有。要确认 hass 运行的设备能连接上这个 IP。
hass 所有的配置都在配置目录的 configuration.yaml
里,在支持的设备列表里有配置说明,在参考链接 [2] 也有(这个翻译版有的地方有点旧了,有官方适配的还是以官方文档为准)。
这里以米家空气检测仪为例,文档页面在此。
直接在 configuration.yaml
里添加:
1 | air_quality: |
注意 yaml 的语法(缩进),以及根节点(比如上面就是 air_quality
)不能有重复的,要写成一个列表。
在第一次运行的时候,可能会注意到在 Web 界面上就能添加一些设备,但是不知道为什么那个入口没有米家(???);然后 yaml 也有 import
,可以自己去拆分成几个文件;再然后 Web 界面等等的配置也是在 configuration.yaml
里面改,具体见文档。
先放个图吧。
如果仔细看图可以发现,虽然能获取到数据,但是显示还是有点小问题(\(\mathrm{CO_{2}e}\) 这个值是因为窝关了这项检测)。此外,米家空气检测仪还有温湿度检测,但是官方的适配就强行把它归类到空气质量类 sensor,不显示温湿度...(python-miio
库明明就有输出,当然咯也可以自己写适配器)
窝觉得,这个项目迟早要重构(
本文参考了:
[1] Home Assistant 中文文档 https://home-assistant.cc/
[2] WiFi - Home Assistant 中文文档 https://home-assistant.cc/component/xiaomi/wifi/
]]>鉴于 Gitlab 等早就有自动构建工具了,Github 已经晚了许多,那 Github Actions 能打得过吗?来上手试一下。
个人项目很少会用到 CI/CD 工具,那几乎唯一能用到自动部署的就是静态博客了。
而且折腾 Blog 是咱最喜欢的娱乐活动之一...(写 Blog 不是)
先在 GitHub Actions 申请测试,然后等邮件通知。
咱等了不到一周的样子就收到了开通邮件。
当然,如果是体验 CI 的话,也可以选择其他的工具,比如 Travis CI。 这是之前写的介绍:持续集成与 Travis CI 入门实践,这篇结尾有个链接介绍如何部署 Hexo 的,可以参考。
如何安装及配置 Hexo 不是本文重点,请自行查询官方文档。
建议先在本地跑通部署到 Github Pages 以后再继续。
配置完成后,把 _config.yml
里 deploy
的 repo 地址换成 SSH 地址(仓库页面 Clone and Download - Use SSH 里的地址),以便后面用密钥 push 到仓库。
因为用户名 username.github.io
的 Github Pages 仓库只能部署 master 分支,而 Actions 的配置文件需要放在 master 分支(仅为个人猜测,因为放在其他分支出现了问题)。 所以需要再开一个仓库,放本地的源代码以及配置 Actions。
2020-06-06 update: 若不使用
username.github.io
这个仓库,而是在其他名字的仓库使用 Pages 的话,是可以放在同一个仓库的不同分支上的。 Actions 配置文件仅在当前分支上会被触发(source),因此开一个分支source
里面放源码和 Actions 配置文件是可以的 。详见英文版 Blog 的操作。
新建一个仓库,先不需要初始化。在本地生成一个 key:
1 | ssh-keygen -t rsa -b 4096 -C "xiaopc@users.noreply.github.com" -f ~/.ssh/github-actions-deploy |
(也可以生成其他种类的 key,如果用上面的命令,需要修改一下用户名)
在新仓库的 Settings -> Secrets 里添加刚刚生成的私钥,名称为 ACTION_DEPLOY_KEY
。
然后在 Github Pages 的仓库,Settings -> Deploy keys 添加刚刚生成的公钥,名称随意,但要勾选 Allow write access。
2020-02-19 update: 如果 Github 账号有添加过 SSH key 是可以直接用的(那个 key 有所有仓库的权限),只需要添加
ACTION_DEPLOY_KEY
即可,不需要添加公钥。 但是为了安全起见呢,最好还是新建一个吧 (o=^•ェ•)o
2020-06-06 update: 同一个 Github 账号下不能添加两个相同的 SSH key,也就是说按照上面的方法添加的 key 无法再被添加到另外一个仓库,或是设为账号全局 key。
可以在网页上 Actions 里编辑配置文件,也可以直接在本地目录添加直接 commit。
网页上可以看到,Github 提供了很多的模板:
对于大部分应用的自动测试构建发布是足够的了,这个相比 Travis CI 降低了一点点门槛。
此外,Actions 还可以将动作打包发布到 Marketplace,这是 Actions 的一个亮点,大大增加了复用能力。
不过要部署 Hexo,现在还没有打包的动作,需要自己写。
如果在网页编辑配置文件的话,选择 Blank workflow
。 如果是在本地目录提交配置文件的话,将配置文件存至 .github/workflows/*随便起名*.yml
。
1 | name: Build and Update Github Pages |
对这个配置文件做几点说明:
Actions 在早期测试时用的是 HCL 格式,而现在使用 YAML 配置,HCL 格式配置文件已被废弃。YAML 格式需要严格按照缩进。
on
标注什么事件会触发这个 workflow,可以指定 branches,详情参考文档。
runs-on
设置运行平台,目前有 Windows、Ubuntu、macOS,见文档。
uses
是使用打包好的 action,可以通过 with
传参数。官方提供了一些 Git 基本操作和环境安装的包,也可以使用 Docker。
env
可以设置这一步的环境变量,这一步设置的变量不会继承到下一步。刚才设置的私钥可以通过 secrets
模板变量获取到,具体见文档。另外直接将密钥 echo 出来会被打码 :)
在网页上保存私钥很可能会把 key 存成 CR-LF 换行模式的,而私钥文件要求 LF 模式,要用 tr -d '\r'
去掉回车符。(在这卡了几个小时(ノへ ̄、))[3]
Git 配置请更改为自己的。
由于咱用了 hexo-renderer-pandoc 引擎渲染 Markdown(LaTeX 公式支持更好),要装 pandoc。 但是没有 root 权限,只能手动装在其他目录。 官方的 toolkit 提供了一个缓存下载内容的工具 actions/tool-cache,但是它仅在一个 workflow 里作用,so...
上面说的那个工具下载保存目录是 $RUNNER_TOOL_CACHE
,这个环境变量没有在文档里,目前值为 /opt/hostedtoolcache
。
配置好了,commit & push 后在网页查看 build 状态:
和 Travis CI 相比,Actions 提供的平台更多,扩展性更强,但是缺少像 build cache 这些功能(build 的时候每次都要重新装依赖)。
虽然和它声称的 word-class CI/CD 还有一些距离,但是 Actions 已经比目前免费的工具高到不知道哪里了。
再加上它与 Github 的深度集成,以及 marketplace 仓库,可以发挥 CI/CD 的更多潜能。
对了,Actions 公开仓库免费,私仓按运行时间计费。
本文参考了:
[1] 通过 GitHub Actions 自动部署 Hexo https://gythialy.github.io/deploy-hexo-to-github-pages-via-github-actions/
[2] Deploying Hugo With Github Actions https://cupfullofcode.com/blog/2018/12/21/deploying-hugo-with-github-actions/
[3] .gitlab.ci.yml for SSH with private key https://gist.github.com/yannhowe/5ab1501156bd84c8ac261e2c17b8e3e0
]]>backdrop-filter
已经脱离实验选项,成为正式支持的 CSS 属性。那么什么是backdrop-filter
,backdrop-filter
有什么用呢?接下来小编...
(如果上面这个例子没有效果,说明你得升级浏览器了)
如果浏览过 Apple 的产品页面,应该会发现在 Safari 下的导航条有着和 iOS 7+ 一样的实时模糊效果,这就是backdrop-filter
的作用了。
下图就是新 Mac Pro (刨丝器)页面的效果:
backdrop-filter
不是只有模糊这一个效果,
backdrop-filter
CSS 属性可以让你为一个元素后面区域添加图形效果(如模糊或颜色偏移)。 因为它适用于元素背后的所有元素,为了看到效果,必须使元素或其背景至少部分透明。[1]
属性示例:
1 | /* <filter-function> 过滤器函数 */ |
来看一下现在的浏览器支持情况。
Safari 是最早支持的(目前还要加 -webkit-
前缀),Edge 也很早就支持了(但是为什么也用 -webkit-
前缀啊)。
尽管如此,当时能够使用backdrop-filter
的流量占比仍然不高(实验室特性不算,当时存在着一些 bug)。
而现在 Chrome 正式支持后,基本就覆盖了大多数流量。 (Firefox 已经在 Consideration 中了,实现的 bug 也解决了 2/3)
用 Chrome 打开刨丝器那页时,只有集显的 6200U 的 CPU 使用率最高达到了 85%+5%...
新特性带给浏览器更强大的图形处理能力,也带来了更高的性能要求啊。
本文参考了:
[1] backdrop-filter - CSS(层叠样式表) https://developer.mozilla.org/zh-CN/docs/Web/CSS/backdrop-filter
]]>首先要有数据源,Mock.js 提供了假数据生成的功能。
但是
在网上搜索到的 Mock.js 教程要么是旧版的(Mock.js 原来是通过拦截 axios 等库的请求来实现的),要么就是以下这种方法:
- 建立一个引入了
mockjs
的 mocker- 修改
webpack.dev.conf.js
,添加before
钩子直接引入这个 mocker:
1 | devServer: { |
而这些教程给的 mocker 大致是这样的:
1 | module.exports = function(app){ |
那 rep
是干什么用的呢?(而且应该是 request 吧)
如果把它 console.log
出来会发现这是请求对象,但是没有处理请求的内容(POST)。
连请求内容都没有,还有什么用啊?连登录验证都 mock 不出来,实在是没什么实用性。
当然,这个就能解决问题了。
webpck.dev.config.js 1
2
3
4
5
6
7
8
9
10const apiMocker = require('webpack-api-mocker')
module.exports = {
devServer: {
// ...
before(app) {
apiMocker(app, path.resolve(__dirname, '../mock/index.js'), {})
}
}
}
mocker 还是那个 mocker,现在 「rep
」就有 body
query
params
方法了:
1 | module.exports = { |
webpack-api-mocker
还有一点好,它是支持热更新的。
webpack 的 devServer 其实是一个 express 服务,那么用法也就是 express。
在登录模拟时也要模拟 cookie,express 设置 cookie 就很简单:
1 | res.cookie(name, value [, options]); |
那在读取 cookie 时就需要 cookie-parser
: 1
2
3
4
5
6var cookieParser = require('cookie-parser');
// ...
before(app) {
app.use(cookiePareser())
apiMocker(app, path.resolve(__dirname, '../mock/index.js'), {})
},
React 生态真的是完善的多,比如 roadhog 等等都自带 mock。
再看 Vue.js,没一个框架做了 mock(如果有的话请留言)。
但是不论是之前的 License 风波,还是 JSX 这种怪异玩意,
很难喜欢上 React 啊。
本文参考了:
[1] 关于几种数据Mock的手段 - worldzhao https://worldzhao.github.io/2018/10/20/webpack-dev-server/
[2] Express cookie-parser middleware https://expressjs.com/en/resources/middleware/cookie-parser.html
]]>结果呢?Too young, too simple!
国内销售的盒子,由于广电对播控平台的要求,只能装那几个众所周知的视频平台。
那不能 root 吗?
国产盒子的 root 的麻烦程度快比手机难受了,而且那些 xx 精灵真的一言难尽啊。 (其实可以买电信/移动的定制盒子然后 adb,据说体验还行,不过怕两个盒子的遥控乱掉 orz)
那国外盒子呢?
tb 上转了一圈,没有看的上的...(关键词“外贸盒子”,一水的刷好评的)
那海淘 Android TV?
为啥要花那个钱啊...(不过小米盒子国际版是能看 Netflix 的)
先装好 Windows,换了 SSD,速度还是可以的。(嘤特尔牙膏++)
电视操作还是需要遥控器的,tb 上买了个飞鼠。wow, awesome~(其实体验真不如电视的动感遥控器)
然后就是考虑怎么上 Android。 因为国内在线视频网站的播放体验被人为削弱了,桌面版应用在电视上看着很难受,UWP 应用... 只有爱奇艺还没弃坑吧。
虚拟机?模拟器?蓝叠?这个机子能带的起来就鬼了。(蓝叠卡的要命,连进播放界面都卡死)
这个 U 的核芯显卡是 HD Graphics 2500,HDMI 是 1.0 的,输出最多 2k。然而电视是 4k 的... 就将就吧。
国内桌面安卓做的比较成熟的(好几年没倒就算)也就凤凰了。
下载,硬盘安装,一气呵成。
凤凰 OS 的硬盘安装是不划分区的,分区是以镜像文件放到任意分区加载的,这一点好。
重启进入,当贝市场安装(广告费结一下),云视听系列安装。
然后就遇到第一个问题了。
是的,如果用 HDMI 连电视,是没有音频输出的。
原因大概是 pcm 设备默认配置的是除了主输出以外全禁用...
解决: 1. 在控制台(先开启 root,控制台用自带的或者 SSH 工具都行):
1 | su |
记一下名称里有 HDMI 的设备 pcm 号。
/dev/snd/
里 pcm
开头,刚才记录的 pcm 号结尾的设备文件,然后在控制台里杀掉音频服务进程:1 | killall audioserver |
看现在有声音了没?如果有就下一步,没有的话就说明...删错了,重来吧...
(我的是 pcmC0D3p
)
/system/etc/init.sh
里初始化声卡(alsa_amixer
那几行)的后面。这就是第一个问题了。
凤凰的桌面是给 PC 用的,电视上几乎看不清。
那换成第三方电视启动器,例如当贝桌面?(结广告费x2)
答案是不行的,系统屏蔽了的。
解决: 1. 下载启动器的 apk,建个文件夹把 apk 放里面,在里面建立 lib/arm
两级目录,把 apk 包里的 lib/armeabi/libDecryptNative.so'
解压到这。
把那个文件夹拷到 /system/app
里,文件夹权限 755,文件权限 644。这一步已经装好了。
如果启动器不能全屏,修改 /data/system/app_window_settings.xml
里对应包名的 windowState
属性为 5
。
用幸运破解器把文件管理器冻结掉。
自带的广告足够恶心,真·牛皮癣。
只要一动广告那个包(凤凰VIP)就开不到机了...
Remix OS 已经凉凉了。(不过这几天看起来要回光返照?)
官网已经没有下载地址了,能下载的地址在 https://www.fosshub.com/Remix-OS.html 。
就 UI 来讲,觉得比凤凰好一丢丢。
也是声称不能换启动器的,但是却自带了个 Lawnchair?这是什么操作?
Remix OS 就比较简单,直接冻结当前启动器就行了。(然后就切到 Lawnchair 了)
对,这几乎是 Android x86 基本都有的问题。
解决方法一样,只是...
Remix OS 启动分区没有写权限...
解决:找到 grub.cfg(在 EFI 分区),在引导项后面加 REMOUNT_RW=1
由于已经凉了,系统内核有点旧,OpenGL 也有点旧,好几个视频应用会出问题。(当然不能排除是这个核显的锅)
云视听极光无法播放但是快进以后可以,奇异果、酷喵、电视猫和 Youtube 无法播放,云视听小电视没问题。
网页播放没问题,但是网页播放为啥要装 Android?
直接翻车。
Android x86 已经到 8.1 了,然而硬盘安装器只支持到两年前的版本...
甚至虚拟机安装卡内核日志...
这是 ChromiumOS,支持装 apk。
但是 Android 启动器就别想了... 桌面基本没用...
只支持硬盘划分区安装...
Android 的实现有点奇怪,云视听极光播放页是被选中的,然后满屏蓝色的 active 状态框...
没一个能打的啊,怕是只能用 TNT 了吧(
参考链接
[1] 刚刚装了凤凰OS,系统没有声音怎么办?? http://bbs.phoenixstudio.org/cn/read.php?tid=16959&fid=12
[2] 装上凤凰os,使用正常就是没声音 http://bbs.phoenixstudio.org/cn/read.php?tid=15196&fid=12
[3] PhoenixOS 如何安装第三方桌面 http://bbs.phoenixstudio.org/cn/read.php?tid=26250&fid=12
[4] 请求帮助:root权限(Remixos) http://bbs.phoenixstudio.org/cn/read.php?tid=25689&fid=12
]]>1 | composer require isaced/flarum-ext-email-verification-switch |
暂时没发现有支持的插件。
vendor/flarum/core/src/Api/Controller/SendConfirmationEmailController.php
90 行的1 | $this->mailer->raw($body, // 其后省略 |
改为
1 | $this->mailer->send(['html' => $body], [], |
vendor/illuminate/mail/Mailer.php
213 行起的1 | $data['message'] = $message = $this->createMessage(); |
改为
1 | $message = $this->createMessage(); |
Mailer.php
,将 315 行(左右)的 $message->$method($raw, 'text/plain')
改为 $message->$method($raw, 'text/html')