首先我妈的钱是存在存折里,农商银行,那天我妈把钱都取出来,想直接转我中国银行卡,不过有一笔 50 左右的手续费,柜员建议我妈去农业银行转,这样可以省下这比手续费。我妈平时省吃俭用,如果只是花点时间就能省下 50 块钱,自然是会采用这种方案。哪怕是我也会毫不犹豫去其他地方转,因为另一个银行只相距几百米,电动车马上就能到。
于是我妈先回家翻了一通农业银行卡,等我妈到了农业银行,银行的柜员说不行,建议我妈直接去中国银行,相当于直接去往我卡里存钱,我妈也懒得跟柜员撤,想着去一趟算了,虽然中国银行在隔壁镇。期间,我妈还开错路,走了回头路。
结果到了中国银行说也不行,虽然我不知道具体原因,难道是因为没有实体卡所以不行?通过我妈的转述,我也不敢肯定具体原因是什么,毕竟我亲眼见识过我妈的转述能力。
最后我妈受不了,还是去了农商银行,交了 42 块的手续费。耗时好几个小时,提心吊胆带着十几万现金,吹了一路冷风,那天还刚好是最冷的一天,零下五六度,电瓶车还骑没电,确实让人生气。
到现在我也还是不知道农业银行跟中国银行为什么不行。是柜员嫌麻烦,糊弄一下不懂的老年人,还是有其他真的无法办理的原因,我不得而知。
]]>上周日将家里的拨号软路由拆了,我用的软路由是之前在前年 5 月份买的 R2S,因为最近家里的科学上网老不稳定,周日折腾了好久依旧不行,加上最新版的 Apple TV现在可以用代理了,可以完美地将它设置成旁路由,所以想了想就把软路由拆了。
本文仅做记录,以便将来再次折腾时查阅。
balenaEtcher - Flash OS images to SD cards & USB drives
TF 卡读卡器
打开 KoolCenter 固件下载服务器,进入对应软路由型号的目录,.img.gz
结尾的文件就是镜像文件,选择想要的镜像文件下载,一般选择最新的镜像,可以从文件名上的时间戳来或后面的上传时间判断。
2.1 TF 开插入读卡器,读卡器插入电脑 USB 接口。
2.2 打开 balenaEtcher,点击【从文件烧录】,选择下载的镜像。
2.3 接着选择目标磁盘,记得确认磁盘大小,确保没有选错。系统盘一般是隐藏的。
然后点击开始烧录即可,一开始需要你输入一下开机密码进行验证,然后就是刻录,刻录完还会进行一次验证,最后会弹出一个弹窗,选择推出。
到这里就完成了 iStore OS 镜像的刻录,很简单,跟 U 盘刻录 windows 系统差不多。
iStore OS 的默认管理地址是 192.168.100.1,默认账号密码是 root/password。
iStore OS 手动安装的插件,无法通过商店卸载,需要通过终端命令行卸载:
ssh root@192.168.100.1
IP 根据实际情况调整。
列出所有已安装的应用:
opkg list_installed
卸载应用:
is-opkg remove 'nps' 'luci-app-nps' 'luci-i18n-nps-zh-cn' 'app-meta-nps'
上面命令是批量卸载应用,单个卸载只需要填对应的应用名称。
]]>2 月份,我卖掉用两三年的富士,入手了理光 GR3x.离谱的是这台二手富士,我用了几年,最后反而还涨价了。当然,理光涨的也很离谱,贴了点钱入的理光。
本来富士我打算本地面交,结果咸鱼遇上个广东还是深圳的学生。开始我不知道,甚至不知道对方是性别,因为对方很少用咸鱼,信息不是很完善,甚至没有芝麻信用,像一个新号,所以我压根没有打算卖给她,但我们还是聊了挺久,最终以互相祝福告别。
几天后她又来找我,因为她看我相机挂了好几天也一直没卖出去。最终她打动了我,我当时也急着出掉回血买理光,因为理光的价格不停在上涨。于是我让她认证了芝麻信用,又加了她微信,想通过朋友圈验证她是不是个爱拍照的人,与她的交谈中,我发现她并不是一个一时头脑发热决定买相机,所以我觉得她朋友圈至少有一些关于摄影的东西,最后确实也验证了我的想法,于是我把机身与 15-14 的镜头打包卖给了她。
还有一个 35f2 的镜头,我出给了杭州的一个买家,没有当面交易,但他急着用,因为马上要去旅游,打算寄出的时候,我发现镜头上有一个非常细小的刻痕,应该是之前借人的时候,把我 UV镜磕碎时候伤到的,不过这人也没多计较,说不影响成像,镜头在他机子上有点问题,他说可能是固件原因,没找我麻烦,总之还是顺利完成交易。
今年带着相机走了好多地方,上海、莫干山、舟山、杭州的各个景点,这是这几年来,我走过最多地方的一年。
5 月份房子交付后,我开始着手处理房子相关的东西,其实东西也不多主要就是美缝、然后再在墙上添加几个插座,为了装电动窗帘。只有处理完这些才能安装柜子。6 月 30 日晚,刚好是周五,我下班后拎着行李箱正式入住新家,可惜家里一塌糊涂,只有一个睡觉的沙发。然后我开始改装家里电路,把家里大部分灯光以及空调接入了米家。自己随心所欲地布置家里可太有成就感了。
家离地铁站有一段距离,走路的话大概有一公里多。刚来的时候我每天步行上下班,后来觉得太累,发现家附近有公共自行车的租赁点,于是开始骑自行车去地铁站。但自行车还是麻烦,光走出小区就得花五六分钟,然后走到租赁点又要花费五六分钟,有时还会遇到借不到车或者车位满了还不了车的情况。最后决定为自己添置一个“大件”——电动自行车。毕竟我要在这里长期居住,电动车作为周围几公里范围内的出行工具,还是必不可少的。看了一圈后,我下单了九号 V30C,是刚出的一个新型号,很便宜,定位就是家庭的买菜车。目前使用快 5 个月,总体来说体验很好。期间出现过几次自动开锁失败,但过了几天又恢复了,可能是传感器因为灰尘覆盖导致失灵。冬天电池电量下降较快,因为用的磷酸电池,但也正是这个原因,它才便宜嘛。
这是我非常坚定一定要安装的东西,但厨房太小,如果安装嵌入式的洗碗机,我只能把我的碗篮拆掉,然后在灶下安装一个洗碗机,不过这样的话碗的存放又是一个麻烦事。最终我决定买一个方太的水槽洗碗机,虽然贵了点,但是能省去很多事,在咸鱼上淘了一个线下店铺里的样机,便宜了几千块,保修与新机一样,附带免费安装。因为厨房太小,安装那天师傅搞好久才给我弄上,期间还收到了其他业主的投诉,因为切割台面跟柜子噪音巨大,最后我上门道了个歉,周末装修不允许发出太大的噪音。目前试用 4 个月,已经完全离不开。
因为住的确实比较远,所以打算在明年购入一辆代步汽车,目前看中的深蓝 S7。今天去 4S 店线下看了实车,感觉还不错,不出意外的话会在明年上半年购入,希望它能带给我更多不同的生活。
今年把很多时间都花在娱乐上,因为谈恋爱,生活发生了挺大的变化,以前一个人时候的节奏被完全打乱,但这无法避免,明年希望逐渐在感情与自己之间找到平衡。
不想立太多的 flag,生活总是充满了不确定,今年的很多事情也并不在计划之内,不确定是一个中性词,它会带来惊吓,但也会带来惊喜。
积极面对未知,感受当下,享受过程。
]]>下面来说我最终是怎么成功装上插件的:
本想通过 SFTP 直接上传文件到对应目录,但是始终无法访问根目录,网上的方法都试了一遍,修改配置文件之类。最终我想到为什么不直接通过浏览器的 DSM 来传呢…传入之后,只要通过终端复制或移动一下文件就够了。
先在控制面板启动 SSH 功能
终端登录
ssh admin@192.168.1.2 -p 22
我用的是 admin 账号,用其他有管理员权限账号也可以。如果用不了 admin 账号,可能是用户面板把 admin 禁用了,启用就好。
取得 root 权限
suso -i
在 MeiamSubtitles Release 下载最新的插件,解压,然后通过浏览器将文件上传至比较方便的目录。
可通过【右键】-【属性】查看文件夹路径。
保险起见,将插件复制到这三个目录中,可以使用 cd
先查看一下路径是否存在,不用用户路径可能会不一样,特别是第三个目录。
cp -r /volume1/Downloads/Emby/. /var/packages/EmbyServer/var/pluginscp -r /volume1/Downloads/Emby/. /var/packages/EmbyServer/target/system/pluginscp -r /volume1/Downloads/Emby/. /volume1/@appdata/EmbyServer/plugins
此时重启 Emby,然后去插件列表查看,可能依旧没有发现安装的插件,因为 Emby 可能没有权限读取插件文件,所以需要重新设置一下文件的权限:
cd /var/packages/EmbyServer/var/pluginschown -R emby:emby Emby*.dllsudo chmod 777 Emby*.dllcd /var/packages/EmbyServer/target/system/pluginschown -R emby:emby Emby*.dllsudo chmod 777 Emby*.dllcd /volume1/@appdata/EmbyServer/pluginschown -R emby:emby Emby*.dllsudo chmod 777 Emby*.dll
此时再重启 Emby,就会发现插件已经成功装上。
最后别忘了关闭 SSH 跟禁用 admin 账户。
网上看教程好像 10 分钟就能搞定,然而昨天我实际却摸索了三四个小时,实践果然是检验真理的唯一标准。
]]>这几天在折腾 Emby ,装插件,昨天折腾了半天群晖的 SFTP 权限,死活装不上,所以我打算将 Emby 装到 R2S 上来(不过最后还是没装在这里),反正闲着也是闲着,就在拉 Docker 镜像这一步的时候,就出问题了,提示我磁盘空间不足,于是就有了我后续一系列的折腾,下面开始正题。
###1. 通过终端连接至 OpenWrt 并获取管理员权限
# 每人根据实际情况调整ssh root@192.168.2.1# 输入密码登录后,先获取管理员权限sudo -i
df -h
可以看到目前以挂载的分区的使用情况,上面挂载在 /data
下的 /dev/mmcblk0p3
就是我昨天新添加的分区。
###3. 查看所有磁盘信息(包括未挂载磁盘)
fdisk -l
可以看到有两个磁盘,上面的不用管,很明显。下面的 /dev/mmcblk0
就是我插的 32G 内存卡。
可以看到现在内存卡有三个分区,从第 2 步的信息来看,mmcblk02
分区被创建了,但并没有被挂载。
fdisk /dev/sda
输入 m
可以获取帮助。
输入 n
并回车。
输入 p
选择分区类型,也可以直接回车,默认为 p
。
可选1-4,我已经创建了三个,所以只能选 4,一般默认就好。
这里有个坑,导致我昨天明明有很多的剩余空间,但只能创建出 31M的分区,从第 3 步的磁盘信息中可以看到,我的扇区不是从最开始(2048)进行分配的,而是从 65536 开始的,如果你的的磁盘最前面的扇区没有被分配,那么分区的时候,默认是从 2048 开始的,所以这时候,就需要我手动填写。mmcblk02
的结束扇区是 1703935,所以我昨天创建 mmcblk03
的时候,开始扇区填写的是 1703936。
结束扇区的话,默认会到最后(或者下个分区开始扇区的前一位),如果你不想分配完,那也可以手动指定。
按 w
保存并退出。
fdisk -l
查新查看磁盘信息,会发现刚才创建的分区 mmcblk0p4
已经显示,31M,因为我只剩这么点未分配的空间了。
这是操作的所有步骤
此时刚建好的分区还不能挂载,因为还不知道文件系统的类型,因此需要格式化一下,我将它格式化成了 ext4 格式。
mkfs.ext4 /dev/mmcblk0p4
先在根目录创建一个目录,用于挂载分区
cd /mkdir data2
然后进行挂载
mount /dev/mmcblk0p4 /data2
可以看到已经被成功挂载了,已经可以正常使用 data2 目录了。权限可以根据用途自行设置。
umount /data2
fdisk /dev/mmcblk0
输入 p
回车,查看分区的信息
按 d
删除分区,然后输入要删除的分区编号,回车。
磁盘分区相关的基本操作就这些,结果是,我分好了区,最终还是没顺利把 Emby 跑起来,最后还是在群晖上顺利安装上了插件,只是过程依旧艰辛,下篇文章再讲。
]]>不是很懂这个纠错的机制,反正我是觉得这个错别字是挺明显的。
虽然有些缺点,不过整体瑕不掩瑜,于是现在成了我的主力输入法(还有一个原因是 iOS 自带的输入法联想真是太笨了)。
微信输入法 Mac\Windows 端正式版现在均已发布,大家可以到官网下载体验,下文提到的一些缺点也已经修正,不具有参考价值。
我开始期待 Mac 端,因为一些快捷短语就在网上搜索微信输入法 PC 端,没想到还真有泄漏的版本了。体验了一下,响应速度还行(对我来说),不过没有双拼,只支持全拼,那我就只能暂时放弃,不过好消息是至少它有在开发。想要尝鲜的可以下载体验下:
我体验之后马上就删了,等正式版。下面三个目录是微信键盘的应用本体、缓存目录,全部删除即可完全卸载。
/Library/Input Methods/WeType.app~/Library/Application Support/WeType~/Library/Caches/WeType
]]>Error: Exit code: ENOENT. spawn /usr/bin/python ENOENT
查了一下,原来 Monterey 把系统内置的 python 2.7 移除了,对 python 开发者来说也许是一件好事,但对我来说却是给我造成了极大的困扰。我折腾了好久,网上找了一圈方法,其实在 vue-cli-plugin-electron-builder 的 issue#6767 中已经有人提了解决方法,就是把 electron-builder 升级至 v23.0.3
。但是 vue-cli-plugin-electron-builder 的作者并没有更新,最后我在另一个 issue#1691 找到了解决方法,其实就是这位兄弟 fork 了 vue-cli-plugin-electron-builder,然后将依赖的 electron-builder 的版本进行了升级。而我们要做的就是将 vue-cli-plugin-electron-builder 的引用地址改一下,改为如下:
"devDependencies": { "vue-cli-plugin-electron-builder": "git://github.com/spuky/vue-cli-plugin-electron-builder#b6826fc"}
然后删除 node_modules 重新安装依赖,之后就能正常打包了。
如果是直接用 electron-builder 进行打包的,那么直接升级版本应该就可以解决。
]]>经过晚上一番搜索,原来是 ssh key 的读写权限过于开放,过多的用户可以读取,可能导致安全问题,因此需要改一下 ssh key 的读写权限:
chmod 400 path/to/key
后续安装 nvm 过程中,我还遇到了 The unauthenticated git protocol on port 9418 is no longer supported 的问题,解决方式是使用 http(s) 地址来 clone 仓库,而不是用 ssh。
]]>下面说下如何设置 AP 模式:
192.168.2.1
,点击「功能设置」-「LAN 设置」。192.168.1.1
,那么我就设置成了192.168.1.3
,总之不要与其他 IP 冲突。这个方法有一个缺陷,就是会白白浪费一个 WAN 口,恩山上也有改网口的教程。我没这么多 LAN 口要用,所以懒得折腾,稳定最重要。
]]>首先创建注册 Gitee 账户就不细说了,注册好后顺便把微信公众号绑定好,不然后面部署的时候可能会导致失败。
在私有仓库的 Actions secrets 中再添加两个 secret,在上一篇中我已经加了一个 secret,现在再加一个 Gitee 的登录密码跟 git 的本地私钥。
Gitee 的本地密码用处是后续需要脚本模拟人工操作,部署 Gitee Pages,因为 Gitee Pages 每次更新都需要手动点击部署按钮。
本地私钥的话是为了有权限往 Gitee 仓库推送代码。因为现在是两个不同的平台之间同步代码,所以需拉取 Github 上博客的公开仓库,然后推送到 Gitee 的目标仓库。正常情况下用过 Github,本地电脑应该已经生成了 KEY。
我使用的是 Mac,KEY 生成的目录默认是~/.ssh
:
id_rsa
是私钥,id_ras.pub
是公钥。
通过 cat ~/.ssh/id_rsa
可以读取公/私钥内容。
公钥需要添加到 Gitee 中:
私钥添加到 GitHub 的博客私有仓库。
接下来在 GItee 创建一个仓库,仓库名最好与账户同名。至于原因可以看这里。
创建好后我的是这样的:
顺便把 Gitee Pages 服务开通:
第一次我们需要手动点一下更新,否则后面脚本会执行失败。
接下来写配置:
name: gitee-deployon: push: branches: [main] workflow_dispatch:jobs: build: runs-on: ubuntu-latest steps: - name: Sync to Gitee uses: wearerequired/git-mirror-action@master env: # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} with: # 注意替换为你的 GitHub 源仓库地址 source-repo: git@github.com:Tit1e/evolly.github.io.git # 注意替换为你的 Gitee 目标仓库地址 destination-repo: git@gitee.com:tit1e/tit1e.git - name: Build Gitee Pages uses: yanglbme/gitee-pages-action@main with: # 注意替换为你的 Gitee 用户名 gitee-username: tit1e # 注意在 Settings->Secrets 配置 GITEE_PASSWORD gitee-password: ${{ secrets.GITEE_PASSWORD }} # 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错 gitee-repo: tit1e/tit1e # 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在) branch: master
这是单独一个配置文件,我实际配置文件跟这个略有不同,因为我之前还有一个 Hexo 自动部署的脚本。
如果两个脚本分开写,那么在文章提交后,两个脚本同时执行,但博客自动部署脚本需要安装依赖,生成静态文件,这需要不少时间,而同步 Gitee 的脚本立马就可以执行,这会导致 Gitee 拉取到的代码永远是旧的,因此我把两个脚本合到一起,并规定了执行顺序,下面是我实际的使用脚本:
name: 部署 GitHub Pages 并同步至 Gitee Pageson: push: branches: - main # master 分支有 push 行为时就触发这个 actionjobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@master - name: Build and Deploy # 使用专门部署 Hexo 到 GitHub pages 的 action uses: Tit1e/hexo-deploy-github-pages-action@master env: PERSONAL_TOKEN: ${{ secrets.HEXO_DEPLOY }} # secret 名 PUBLISH_REPOSITORY: Tit1e/evolly.github.io # 公共仓库,格式:GitHub 用户名/仓库名 BRANCH: master # 分支,填 gh-pages 就行 PUBLISH_DIR: ./public # 部署 public 目录下的文件 async-gitee: # 需要 build-and-deploy 这个 job 执行完再执行当前脚本 needs: build-and-deploy runs-on: ubuntu-latest steps: - name: Sync to Gitee uses: wearerequired/git-mirror-action@master env: # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} with: # 注意替换为你的 GitHub 源仓库地址 source-repo: git@github.com:Tit1e/evolly.github.io.git # 注意替换为你的 Gitee 目标仓库地址 destination-repo: git@gitee.com:tit1e/tit1e.git - name: Build Gitee Pages uses: yanglbme/gitee-pages-action@main with: # 注意替换为你的 Gitee 用户名 gitee-username: tit1e # 注意在 Settings->Secrets 配置 GITEE_PASSWORD gitee-password: ${{ secrets.GITEE_PASSWORD }} # 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错 gitee-repo: tit1e/tit1e # 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在) branch: master
第二个 job 添加了needs: build-and-deploy
这个配置。有了它,第二个脚本就会在第一个脚本执行之后才开始执行,这样就能保证拉到的博客是最新的。
注意 Gitee 一定要绑定微信公众号。
然后就提交博客源码可以看下执行结果,正常的话 Gitee 会收到登陆提醒。
如果有报错,可以对照错误找解决方案。
]]>.md
文件后实现自动部署。我的博客是使用 Hexo 搭建的,之前都是在本地机器上通过命令来部署,对我来说其实也还好,因为我几乎不在移动端编写大段的文字,主要这次想折腾。有了 Actions 以后,我可以在移动端访问 GitHub,通过在线编辑的方式发布文章。就当是个尝试。
首先我们需要准备两个仓库:一个私有仓库,一个公有仓库。
私有仓库是用来存放整个博客项目的,包括配置文件,文章源代码等,因为里面有一些个人的配置,所以不能公开;公有仓库则是用来存放 Hexo 编译出来的静态文件。
这两个仓库的工作流程就是:在私有仓库编写博客,写完后将文件推送至远端仓库。当指定的分支有更新时,会自动触发 Actions,Actions 会执行我们配置的命令,重新编译博客的静态文件,然后把静态文件更新到公开仓库的固定分支。
上述流程涉及到了 Actions 自动往仓库推送的操作,正常情况下肯定是不行,因为虽然我的仓库是公开的,但是这个仓库的所有者是我,正常情况下只有我可以向它推送,因此如果要让“陌生人”也有推送权限,我需要给这个“陌生人”一个“通行证”,到时候仓库看到这个“通行证”就知道是我给“陌生人”权限了。
我们可以在这里生成 Github Token。Expiration 选择永不过期,否则你就得定时更新 Token。权限的话按照下方图上勾选即可。生成后注意保存 Token,因为 Token 只在生成后出现一次,关闭页面后就再也看不到了。如果不小心关了,也没关系,删除原来的重新生成一个就行。
之后需要将这个 Token 添加到私有仓库的 Secrets
中。
名称无所谓,随便填写,你自己能认识就好。
接下来在私有仓库根目录编写配置文件:
.github
文件夹.github
文件夹下新建 workflows
文件夹workflows
文件夹下新建 xxxx.yml 文件(命名随意)接下来就是编写 yml 配置文件:
注意配置中的 PERSONAL_TOKEN值要与之前添加的 Secrets 对应,否则脚本在执行的时候会获取不到 Token 导致无权限。
name: 部署 GitHub Pageson: push: branches: - main # master 分支有 push 行为时就触发这个 actionjobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@master - name: Build and Deploy # 使用专门部署 Hexo 到 GitHub pages 的 action uses: Tit1e/hexo-deploy-github-pages-action@master env: PERSONAL_TOKEN: ${{ secrets.HEXO_DEPLOY }} # secret 名 PUBLISH_REPOSITORY: Tit1e/evolly.github.io # 公共仓库,格式:GitHub 用户名/仓库名 BRANCH: master # 分支,根据实际填写 PUBLISH_DIR: ./public # 部署 public 目录下的文件,hexo 一般都是这个目录,可根据实际调整 async-gitee: needs: build-and-deploy runs-on: ubuntu-latest steps: - name: Sync to Gitee uses: wearerequired/git-mirror-action@master env: # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} with: # 注意替换为你的 GitHub 源仓库地址 source-repo: git@github.com:Tit1e/evolly.github.io.git # 注意替换为你的 Gitee 目标仓库地址 destination-repo: git@gitee.com:tit1e/tit1e.git
上面配置中用到了大佬编写的第三方部署插件 hexo-deploy-github-pages-action,不过插件中配置的 node 版本与我本地的不一致,导致 Hexo 无法正常编译,所以我 fork
了一份,将 node
版本改成了 v12
。修改的地方如下图,其实我觉得 node 版本可以做成配置项,由用户自己定义。
配置中的分支名字一定要跟实际的仓库对应。
最后,我的 Hexo 版本还停留在 Hexo 3,这个版本的主题是要用户自行下载,然后放到 themes
文件夹下。但是这个主题本身也是一个 git
仓库,这种情况,主题文件夹不会被提交。我的解决办法是:删除主题文件夹根目录中的 .git
文件夹,这样这主题文件就能正常被提交。Hexo 5 主题可以从依赖引入,也就是说可以通过 npm i
进行安装,不会有上述情况。
最后将私有仓库提交至 GitHub,我不清楚 Actions 第一次提交会不会执行,不行就再改点东西提交一次。
正常我们就可以在 Actions 标签中看到我们配置的 workflows,以及每次的运行结果,还能点进去看具体的运行日志。
]]>软件锁的界面一般是一个遮罩盖住整个页面,然后进行人脸或指纹验证,验证通过就关闭遮罩这么个逻辑。
我一开始使用 fixed 进行全屏覆盖,但是显然没这么简单:
这个遮罩无法覆盖原生的头部与底部栏。
此方法作罢,继续寻找尝试,最后发现 nvue 页面可以实现全屏幕的覆盖而不受影响原生控件的影响。
为什么呢,因为用这种方式实现的遮罩其实并不是在软件首页,而是跳转至了一个专门用来做遮罩的二级页面,这个二级页面什么也没有(当然要有其他你也可以自己加),没有原生的头部,二级页面自然也没有底部的导航栏。上面这个特点普通的 vue 页面就能做到,但是 nvue 页面还有一个特点是可以将页面的背景图片设置为透明,这是普通页面无法做到的。uniapp 的多级页面显示其实就是一层一层往上叠加页面,把下面的遮盖住,但是如果上面的页面是透明的,那么下面的页面自然就显示出来了。
当然如果没有透明度要求那直接用普通页面做也可以满足。
下面来说下实现过程:
首先新建一个页面,我命名为 mask.nvue
(注意扩展名是 nvue):
<template> <!-- blurEffect 是 nvue 中的 view 标签独有的属性,可选值有:dark/extralight/light/none,用于背景高斯模糊 --> <!-- nvue 中 css 的使用限制很大,无法使用 filter 等属性 --> <view class="mask" blurEffect="dark"></view></template>
<script> export default { onLoad() { // 进入页面就开启验证 uni.startSoterAuthentication({ requestAuthModes: ['facial', 'fingerPrint'], success: res => { // 因为识别有动画效果,所以延迟一秒等动画完成再返回 setTimeout(() => { uni.navigateBack({}) }, 1000) } }) } }</script>
<style scoped> .mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; }</style>
然后在 pages.json 中添加路由:
{ "path": "pages/mask/mask", "style": { "navigationStyle": "custom", "app-plus": { "animationType": "none", // 不使用页面切换动画,否则就会被发现是页面切换 "background": "transparent", //页面设置透明背景 "popGesture": "none" //禁用侧滑返回,防止用户取消验证使用侧滑返回而绕过检验 } }}
这样就完成了,使用的话你只要直接在应用进入首页的时候,把页面跳转至验证页面就可以了,验证成功自动返回首页。
// /pages/index/indexonLoad(){ uni.navigateTo({ url: '/pages/mask/mask' })}
当然,在跳转前你需要使用 uni.checkIsSoterEnrolledInDevice(OBJECT) 判断当前设置是否已经录入了指纹或者 Face ID并且在软件设置中打开了启用软件锁的开关(如果有的话),否则就不需要验证。
这是触发的效果:
一开始我在网上找了很多插件,总之不太行,还有很多是后端处理的方案,但我这要前端处理也没有办法,最终试了一圈,还是自己画吧。
先分析一下头像可能出现的情况:
最少的情况:
成群最少三个,三个时三个头像在图中间平铺。
四个时比较特殊:
头像呈现一个“田”字,而不是大多数的三等分。
5 至 6 个时:
一排三个 ,另一排两个或三个,少的那排头像永远居中,上下两排在垂直方式也是居中排列。
7 至 9 个时:
两排三个,剩下一排 1 至 3 个,此时行数已经撑满整个头像区域。
到这里基本已经完成了大半,剩下的就是绘制了。
绘制完成后我将 canvas 转成了 base64,并通过 @loaded
传递了出来,用户可以后续自行处理。
最终效果:
代码在这里贴一下:
<template> <canvas :style="{height: canvasSize + 'px', width: canvasSize + 'px'}" :canvas-id="canvasId" :id="canvasId" > </canvas></template>
<script> export default { props: { // 不满三个的行显示在上面还是在下面,默认在上面(同微信) avatarReverse: { type: Boolean, default: true }, // 头像数组 images: { type: Array, default: () => ([]) }, // 头像背景色 backgroundColor: { type: String, default: '#ffffff' }, // 边框宽度 borderWidth: { type: Number, default: 2 }, // 每个头像的尺寸 avatarSize: { type: Number, default: 30 } }, data() { // 如果同时渲染多个,会出现 id 重复问题,所以 加上时间戳跟随机数 const now = (+new Date() + Math.random().toFixed(4) * 10000) return { canvasId: `__myCanvas${now}`, avatarArray: [], colNumber: 3 } }, computed: { canvasSize() { return this.avatarSize * 3 + this.borderWidth * 4 }, avatarSize2() { return (this.canvasSize - this.borderWidth * 3) / 2 }, pointMap() { // 这里的 1,2,3 为每行 / 每列的数组长度 return { 1: (this.canvasSize - this.avatarSize) / 2, 2: (this.canvasSize - (this.avatarSize * 2 + this.borderWidth)) / 2, 3: this.borderWidth } } }, created() { this.init() }, mounted() { this.drawAvatar() }, methods: { drawAvatar() { const ctx = uni.createCanvasContext(this.canvasId, this) ctx.setFillStyle(this.backgroundColor) ctx.fillRect(0, 0, this.canvasSize, this.canvasSize) for (let i = 0; i < this.avatarArray.length; i++) { const item = this.avatarArray[i] // 按九宫格分的情况 if (this.colNumber === 3) { const colStart = this.pointMap[this.avatarArray.length] const rowStart = this.pointMap[item.length] for (let v = 0; v < item.length; v++) { ctx.drawImage( item[v], rowStart + (this.avatarSize + this.borderWidth) * v, colStart + (this.avatarSize + this .borderWidth) * i, this.avatarSize, this.avatarSize) } } // 按4宫格分的情况 if (this.colNumber === 2) { for (let v = 0; v < item.length; v++) { ctx.drawImage( item[v], this.borderWidth + (this.borderWidth + this.avatarSize2) * i, this.borderWidth + (this.borderWidth + this.avatarSize2) * v, this.avatarSize2, this.avatarSize2) } } } ctx.draw(true, ret => { // #ifdef APP-PLUS uni.canvasToTempFilePath({ x: 0, // 起点坐标 y: 0, width: this.canvasSize, // canvas 宽 height: this.canvasSize, // canvas 高 canvasId: this.canvasId, // canvas id success: res => { const savedFilePath = res.tempFilePath // 相对路径 const path = plus.io.convertLocalFileSystemURL(savedFilePath) // 绝对路径 const fileReader = new plus.io.FileReader() fileReader.readAsDataURL(path) fileReader.onloadend = (res) => { // 读取文件成功完成的回调函数 this.$emit('loaded', res.target.result) } } }) // #endif }) }, init() { const images = this.images.slice(0, 9) this.avatarArray = this.splitArray(this.avatarReverse ? images.reverse() : images) }, splitArray(data) { if (data.length === 4) { this.colNumber = 2 } const result = [] for (let i = 0, len = data.length; i < len; i += this.colNumber) { result.push(data.slice(i, i + this.colNumber)) } return this.avatarReverse ? result.reverse() : result } } }</script>
我一开始使用的是 CI,因为我的应用只运行在 Mac 环境下,所以我使用的是 AppVeyor,一开始我没仔细看,照着网上的教程配置了老半天 Travis-CI,最后在控制台看报错信息的时候发现 Windows 的目录,然后再一看教程,这个是专门针对 Windows 环境打包的,AppVeyor 才是 Linux 跟 Mac OS 的,于是又重新配置了一遍。发布也成功了,但是最后发现,其他环境下打包是没有签名的,因为签名在我自己开发的电脑上。为什么需要签名呢?因为只有签名了的应用才能自动安装更新。
网上搜了很多资料,还是没解决,最后打算使用 electron-builder 配置来自动上传。
我的打包配置项写在 vue.config.js
中,有些可能写在 package.json
中,这都不是大问题,反正将配置都写在一个地方就行,我的配置如下:
// vue.config.jsmodule.exports = { // ...其他配置项 pluginOptions: { publish: { provider: 'github', repo: 'kindle2Flomo', // git仓库 owner: 'Tit1e', // 拥有者 releaseType: 'release', vPrefixedTagName: false, publishAutoUpdate: true // 发布自动更新(需要配置GH_TOKEN)。 默认true } } // ...其他配置项}
发布的配置项配置完了之后,还需要配置 GH_TOKEN。这个 token 是用来授权一些权限的,因为要往你的私人仓库上传文件,所以必须有相应的权限。
GH_TOKEN 可以在这里生成 https://github.com/settings/tokens/new
输入 token 的备注,选择有效期,我选择的是永不过期,最后将 repo 选上,拉到最底下生成 token。
⚠️ 注意:token 只在生成完成时候显示一次,关闭网页之后就无法再查看,所以及时保存至其他地方。当然真的忘了,那就删了重新生成一个。
生成完需要将 GH_TOKEN 添加到环境变量中去,当然你可以在你执行打包命令的终端中临时添加:
export GH_TOKEN="YOUR_TOKEN"
先执行上面的命令,然后执行打包命令。而加到系统变量中就免去了这一步的麻烦。
sudo vim ~/.bash_profile
然后在文件中添加:
export GH_TOKEN="YOUR_TOKEN"# 下面这个变量我是多加的export GITHUB_TOKEN="YOUR_TOKEN"
添加完之后去执行打包命令,最后会发现还是找不到 GH_TOKEN,在网上查了之后,发现是终端加载的是 ~/.zshrc
文件,而在 .zshrc
文件中没有定义任务环境变量,因此我们需要在这个文件中加入一句:
source ~/.bash_profile
网上说这句要加在最后一行,但是我加在最后不行,就加在了稍微前面一点,也可以正常运行。
所以我在想是不是直接在 ~/.zshrc
中定义变量,就不用执行 source ~/.bash_profile
了?不过最后我没有尝试。
⚠️ 除了这些别忘了还需要开发者的证书,证书的生成教程网上有很多,查一下就行。
最后,在 package.json
中加入一条新命令:
"electron:publish": "vue-cli-service electron:build --publish always"
执行上面这条命令,正常情况下会先执行打包流程,打包完成后可以在终端看到文件上传的进度。
等终端执行完毕,去 GitHub 的 Releases 页面,就会看到一个新的 releases 草稿,之后你就可以编辑更新信息,或者直接发布了。
]]>调整主要针对客户端,主要为一下几点:
最开心的是将客户端的窗口的状态栏隐藏了,界面看着立马“高级”了许多。
不过从昨天的升级情况来看,还有点小问题,不过不影响功能。
使用 i18n 给应用添加了语言切换的功能,不过由于我自己的英语能力过弱,所以这个只好委托少楠去帮我做这个翻译。我将应用中的中文整理出来,写到 Notion 中,然后甩了个链接给他。
以上比较明显的升级,用户可以感知。技术层面就是 Vue 版本的升级。算是一个 Vue 3 的练手项目,公司主要以稳定为主,所以也不会使用比较新的东西。这个版本中用到了 TS,自我感觉写得一塌糊涂,后面再慢慢将变量的类型加上去。
https://github.com/Tit1e/kindle2Flomo
一直想给自己写一个养成习惯(目前暂且这么定义)的 APP,通过完成事情来获取金币,享受事物则需要消耗金币。我觉得这个方式应该是有一个名称的,但是我并不知道,不过不影响我开发 App。本来我是想先学 swift,用原生来写,但是后来觉得我需要先把这个东西做出来,因为不一定成功,从头开始学一门语言的成本是比较大的,而我这个小项目成不成还是个未知数,所以先把东西写出来,用起来是最为重要的,至少现阶段我觉得是这样。
]]>$alert
来显示一个弹窗,展示弹窗的代码相同的情况下,其中一个页面的弹窗正常,另一个弹窗的动画比正常的慢很多。我通过一步步的排查,最终定位到了一段 html 代码上,这段代码中加上了很多的毛玻璃效果,而这个毛玻璃效果也正好是我之前告诉这位同事的。我将 CSS 中的 backdrop-filter
注释掉之后,弹窗动画就正常了。然后我顺便对 backdrop-filter
进行了一些试验,想看看哪些因素会影响会对动画效果有影响。最终得出非常主观的结论是:
backdrop-filter
元素的数量blur()
中的像素值backdrop-filter
所作用元素的大小你可以在这个 Demo 中试验各个因素对动画的影响。
最后我的猜测是因为使用 backdrop-filter
渲染非常耗性能,导致动画出现掉帧等问题。我是在 Chrome 开启硬件加速的情况下测试的,如果关闭硬件加速,backdrop-filter
的渲染效果就会大打折扣,动画掉帧也会更加严重。
以上结论仅仅是凭我在自己电脑上测试后的结果得出,不具有任何权威。不知道更高配置的电脑上是否还会有这个问题。
在 backdrop-filter
之前有一个 filter:blur(10px)
可以实现类似高斯模糊的效果,其实这两个 CSS 语法完全相同,只是效果有所不同。
backdrop-filter
实现的是类似 iOS 毛玻璃的效果,模糊效果会随着背景的改变而改变,类似透过一块毛玻璃看物体,看什么什么就模糊,而 filter
的高斯模糊作用于固定的背景图片上,所以 filter
只需一个元素就可以实现,而 backdrop-filter
需要两个元素才能实现:背景 + backdrop-filter
作用的元素,当然这个元素可以是伪元素。
兼容情况的话 backdrop-filter
大部分主流浏览器都已经兼容,不过 filter
的兼容性更好
backdrop-filter
兼容情况
filter
兼容情况
<input class="uni-input" cursor-spacing="10">
cursor-spacing
有最大有效值,可自行试验。
我开发过程中是在安卓上发现这个问题的,iOS 不加也可以正常显示。
]]>Finder-前往-前往文件夹,输入地址:/Library/Preferences/Parallels
,打开目录。
注:此目录下的文件改动需要有管理员权限,所以在保存文件时会要求你输入密码(锁屏密码)。
这两个问题,我只实践过网络问题,成功解决了。USB 的问题没有实践过,但我觉得这种方式靠谱,所以一起放上来了。
推荐使用代码编辑器打开 network.desktop.xml
,搜索 UseKextless
。
如果有,将标签之间的值改为 0。
如果没有,那在根标签下加如下代码,然后保存文件。
打开 dispatcher.desktop.xml
,搜索 <Usb>
。
将标签中的值修改为 1。
]]>上面只是粗略的步骤,还有很多需要注意的细节在具体过程中会提到。
先说一下我原本的光猫与路由器的连接模式是光猫联网,然后路由器 DHCP 自动分配 IP 地址联网,然后发送无线。正常来说大部分家庭应该都是这种模式。这种情况下,我的光猫的 IP 是 192.168.1.1
,路由器的 IP 为 192.168.2.1
。
我的路由器是 K2P,刷了padavan 固件,之后的说明都在这个基础上展开,其他的路由器大致应该都差不多,再再网上搜一下应该没什么大问题。
群晖系统我升级了 DSM 7.0,但问题不大,跟之前相比基本只是 UI 有点区别。
域名是阿里云申请的。
首先要登录移动光猫的后台,访问地址一般在光猫背后的信息中:
在当前的环境下可以直接访问到光猫的后台,但之后修改连接模式后就将无法直接访问。
路由器后面的信息中也提供了后台的管理账号,但是这个账号功能有所阉割,权限还是不够大,但是移动的后台超级管理员账号其实都是统一的,网上一搜就行:
我的用第一个就能登录。进入后进入【网络】-【宽带设置】-【网络连接】
【链接名称】处应该是个可以选择的下拉框,选择第二项,可以看到下方的封装类型为 PPPoE,这就对了。
然后将【连接模式】由【路由】改为【桥接】,正常情况下默认应该是【路由】模式。
点击下方【保存/应用】按钮,等设置完成,页面刷新,可以等刷新完成再确认一下设置是否生效。
网上有些教程这一步是先删除这个配置,然后重新建一个,我也试过,发现跟修改后直接保存没什么区别。
之后登录路由器后台,进入【外网 WAN】-【外网设置】。
设置如图,DNS 我选择了自动获取。
宽带的用户名与密码如果忘记了就打 10086 的宽带服务重置一下就行。
将这项设置完成后,网应该可以正常访问了,如果不行就等 IPv6 设置完成后重启一下路由器。
接着进入【外网 WAN】-【IPv6 协议】。
如果成功的话,这时在当前网络的信息中应该可以看到 IPv6 的地址。
移动的 IPv6 以 2049 开头。
先进入群晖,【控制面板】-【网络】-【网络界面】。
找到 IPv6 的地址,复制 /64
之前的值。
在浏览器访问试一下,在地址栏中输入[IPv6地址]
,正常应该能进入群晖的系统或者系统登录页面。
接着进入阿里云域名解析后台,新增一条 AAAA 的记录:
我的主机记录是 nas,用的二级域名,到时候我的访问地址就是 nas.xxxx.xxx
,比如 nas.baidu.com
。
如果你想用一级域名访问,那就保持其他值不变,分别添加两条主机记录分别为 @
和 www
的记录。
等待解析,如果 IPv6 地址在这段时间内没变动的话,解析完成后输入域名应该就可以访问到群晖。
阿里云进入 AccessKey 管理。
可能会出现这个弹窗,因为我就自己用,正常也不会有什么人来攻击我,所以就选第一个。
如果没有创建过 AccessKey,那就创建一个,我已经创建过了,所以直接点击后面的【查看 Secret】,有个验证,通过之后就可以看到AccessKey ID 和 AccessKey Secret,复制下来备用。
进入群晖系统,开启 ssh,需要确认一下网卡的名称,后续有用到。
开启后,打开终端,输入下面命令然后回车,之后会让你输入账户密码。
ssh 用户名@群晖ip地址
登录成功后继续在终端输入
ifconfig
会出现网卡的信息,根据之前看到的 IP 地址找到网卡名称。
记住网卡名称,或者暂时不要关闭终端。
接着下载 aliddns.sh,下载后可以重新命一下名,去除前缀。
用代码编辑器打开,如果没有推荐使用 VSCode。
将前面 4 项跟据你的实际情况填入,第 20 行的网卡名称需要确认一下,看跟之前终端看到的是否一致,一般都是这个名字,如果不是,就将它改成终端中实际的名字,保存。
将这个脚本上传至群晖,目录可以自己定。
通过文件属性获取文件的地址,复制。
现在终端验证一下脚本是否有效:
在终端输入命令:
sh /volume1/Tit1e/ddns/aliddns.sh
如果运行结果的最后面是“updated record” 或 “added record”,那就说明脚本执行成功了。
最后,在群晖的【控制面板】-【任务计划】面板新增一个任务:
具体设置:
常规中是设置计划名称跟执行的账户,选择有执行权限的账号就行。
运行频率为每隔 5 分钟运行一次。
在文本域填入脚本存放位置。
保存。
到这里所有的步骤就完成了,可以在浏览器验证一下:
可以在阿里云的解析日志中查看脚本的执行结果,如果地址没变,则脚本运行不会产生日志:
我用的脚本来自网上某片文章,但由于这几天看了太多类似的文章,我已经无法知晓来源,如果你是作者可以联系我,我会在文章中注明来源。
]]>我的 ClashX 版本:
官方文件格式 官方已删库跑路
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><array> <string>192.168.0.0/16</string> <string>10.0.0.0/8</string> <string>172.16.0.0/12</string> <string>127.0.0.1</string> <string>localhost</string> <string>*.local</string> <string>*.crashlytics.com</string> <string>要忽略的地址就像上面这样写</string></array></plist>
退出重新打开 ClashX 就能生效。
]]>