Redis 内存爆满 / 优化 PHP 代码 / SVG 音频波形动画

上午无意间打开了下自己的微信小程序,发现首页报错“接口数据异常”,用电脑打开对应 API 之后发现是 Redis 出现问题报错 OOM command not allowed when used memory > 'maxmemory',导致 PHP 打印出错误信息的字符串了(敲重点)。

那么这个时候就有两个问题需要解决了,首先是如何修复 Redis 的这个错误,其次是如何在代码中捕获错误,让 PHP 不再以字符串的形式打印错误。

第一个问题大概就是内存满了,默认只有 122M,我看了下其实存的大多数都是「网易云 API」的数据,网上查了一波资料给的建议就是切换数据淘汰算法,删除一些不常使用的数据,这里我选用的是 LFU,删除最不常用的,扩展就不考虑了,毕竟服务器本身内存就小...

vim /usr/local/redis/etc/redis.conf

:/maxmemory
maxmemory-policy allkeys-lfu
:wq

redis-cli shutdown

第二个问题我是使用了 PHP 的 set_exception_handler 函数解决的,函数本身接受一个参数为 Exception 对象的函数,只要 echo 包含错误信息的 JSON,在接口的相关位置调用一下就可以了,就是这代码写的有点烂吧,在这里不做详细讨论了。

解决完问题之后打算给前端做一些细节功能,想到之前 @Innei 给当前播放的歌曲做了一个进度条的效果,遂考虑加入一个展示“正在播放该歌曲”的效果。由于此前的播放按钮使用了 SVG + 伪元素的形式插入(因为那个编号也是 CSS 伪元素做的),于是想着能不能做一个动态的 SVG 并且插入进去。

最后就整了一个这样的 Demo,但是发现经过 SVGO 压缩后 rect 标签被压缩成 path 标签,导致该动画失效,发了个 吐槽,目前还没找到 Nuxt 打包层面可以解决的办法... 不经过打包器的话就只能放在 public 目录下,感觉怪怪的。

详情

专升本群友 / 老同学面试

今天的工作任务我主要在修复 Bug 和优化插件的代码,封装函数提供 TS 类型验证,其中部分代码逻辑需要注入页面,也就是 Inline Script,它们通过 Content Script 去注入,而 Content Script 本身执行这些代码,是拿不到页面内容的,需要通过 window.postMessage 来传递,而我正是二次封装了这个方法。

同事倒是给我出了两个难题,一个是使用了 <input type="file" /> 的文件选择框不会显示成「手」的光标,这是因为他并没有隐藏本体,触发区域刚好在按钮上面了

第二个问题则是涉及到业务设计,插件增加了一个 SidePanel 模式(在浏览器窗口侧边弹出一个面板,也是在 110+ 版本之后增加的),而 SidePanel 想要获取当前页面的内容,可以通过 chrome.tabs.sendMessage 找对应的 Tab 获得。问题在于如果切换了其他页面,SidePanel 的状态也需要更新,理论上只要 Tab 切换就要重新获取,可以在 Background 使用 chrome.tabs.onActivated 检测 Tab 的切换,我尝试了下发现在 SidePanel 里面也是可以的

老群友 @Suemor 突然找我问怎么加入群里的那个 Wakatime 组织(统计群友写代码的时间),结果一打听是因为他闭关备考专升本半年多。要是有能力我也想考虑下专升本,可惜我根本学不下来了,祝他最后考试顺利吧。

老同学今天去面试了两家公司,他说目前对一家游戏公司的「市场专员」岗位有些兴趣,也是面试通过了。就是有一点我觉得挺离谱,他们那里的 HR 居然把其他应聘者的信息给他看了,除了他都是硕士,这是还没入职就给他压力了么== 这个岗位上面写的薪资是实习 120 一天,看上去待遇并不是很高,就这样连硕士都要抢,这也难怪为什么大专这么多公司直接筛掉了。学历严重贬值,学历高却找不到专业对口的工作,这就是现状啊。

宅家的假期第二天

已经是假期第二天了,由于我没去搞澳门的签注,于是今天也没有去哪里玩,结果家里亲戚他们去香港玩了,群里也没一点消息(我们想去叫你干什么?我们自己去玩就不自由了)👈 括号内容瞎自拟的。按家里人的意思来说就是“无声无气”,年轻人们都结婚或者有对象了(除了我),以后这样的集体活动只能越来越少。

下午没啥事干想着弄一下自己微信小程序的备案,结果又要打印材料+按红色手印,既然要出去还要出钱就算了,不如回公司上班的时候让同事帮忙打印一下好了。

玩了下原神的琳妮特邀约任务,看了会《间谍过家家》,还修整了一下小窝新版前台以及后台,感觉小改后的小窝后台页眉好像比之前的稍微好看了一点,可能是审美疲劳了吧...

  • 前台:新增中间件用于跳转外部链接(加群、直播间)
  • 前台:修复 Hydration 和 TS 的错误,增加一些图片的 loading="lazy" 属性
  • 前台:优化页面参数改变后的向上滚动逻辑,去除加载效果
  • 后台:修改页眉风格,同步新首页

一个极其难维护的错误提示组件

今天的工作任务是继续完善字幕翻译功能出错相关提示的展示,最难搞的字幕获取逻辑都整上了,结果收集错误反倒是更困难的...

项目的现状比较离谱,错误展示的组件并没有详细说明一共有多少种类型的错误,甚至这个组件涵盖业务好几种,两套样式,还被挂载到了两个不同的位置(页面某处固定、某处弹窗内),就特别离谱。它们之间虽然也或多或少存在关联(例如 LLM 返回的错误码),但渲染内容还是根据业务情况有些许差异,导致该组件的参数和判断逻辑特别复杂且不易阅读。

interface ErrorProps {
  error: string
  type?: string
  summaryName?: string
  setError?: (error: string) => void
  setLoading?: (loading: boolean) => void
  onRetryCallBack?: () => void
  translateRetry?: () => void
  translate?: boolean
  mode?: ProviderType
}
就问你能看懂这些参数的意思么,组件渲染内容的判断条件,除了 error,没想到 translate 也是地位很高的吧,它是 true 的时候无视 props 其他什么东西,甚至 onRetryCallBacktranslateRetry 还是同级别的关系只是名字不同

我目前想到的方案就是尽可能的收集已知的错误类型,将弹窗独立成一个挂载在页面上的组件,错误内容则是根据业务的不同拆分多个组件,分别去做判断和展示,统一使用 postMessage 传送错误码到弹窗。即便它们的错误信息可能有重叠,但现状看上去适当的冗余还是更有利于后期维护的。

小窝新版 Nuxt 前端上线!

  • 中午完成了崩铁的世界任务“天才群星闪耀时”,总算解锁了新周本

    • 哪个不断分身的虫子确实难打,但主要攻击还是最大那只才行
  • 下午的工作,把支持多种 LLM 和多语言 Prompt 的字幕翻译功能整了个测试版

    • 还专门针对处理了下可能出错的场景,增加一定的回退机制(变成机翻)
  • 晚上按照约定清理了一下周同学的电脑,各种应用在电脑上拉屎的行为也很常见

    • 它们大多数是软件自动更新后残留在 C 盘用户文件夹下,时间跨度都有一年多,只能手动清除
    • 果然重装系统才是对抗残留垃圾最好的解决方案啊
  • 紧接着快速推进小窝新版 Nuxt 前端的上线计划

    • 主要是切换目录,调整 Nginx 配置,清除缓存,以及替换多个项目里面的一些硬编码
    • 其中 Nginx 配置我目前是将原先 /upload 和 后台的地址设定了另外的 root 目录
    • 也就是旧版网站的文件夹路径,这样不用 Nuxt 转发请求,理论上效率是更高的
    • 以及 acme.sh 脚本的路径需要注意一下,小窝之前调整过 root 目录
    • 因此脚本生成 .well-known 的路径也需要做修改,防止 SSL 证书续签失败
    • 当然,鉴于历史遗留原因,你也依旧可以访问 旧版网站
详情

工作与生活都在折腾项目

  • 今天的工作是尝试用 LLM 翻译字幕

    • 在同事的指导下拿了一个现成封装好的方法调用
    • 看 LLM 返回的结果非常不稳定,只能靠数据处理的方式尽量清洗,直到基本可用
  • 晚上直播把小窝 Nuxt 基本页面迁移完毕了,还有一些细节和组件需要再做优化

    • 例如评论,代码高亮什么的,从结果上来看迁移遇到的问题并不算太多
    • 就是一些代码用 JSX 写看上去不够“干净”,这或许是 Nuxt 自己应该解决的问题吧
  • 想借此机会弄一下 Jenkins 自动化,和此前 CSR 的逻辑还是有区别的,需要在服务器里执行部署

    • 主要实现是 SSH 命令一句话执行命令,结果一番操作下来报错找不到对应的程序
    • 我发现是 $PATH 环境变量不一致导致的
    • 问了下 @提莫 他说是因为 SSH 一句话执行是 Fork 子进程,和交互式给 Shell 不一样
    • 一句话执行改成执行 .sh 文件,代码改成绝对路径可能行
    • 结果我试了下依旧失败,报错 No such file or directory,明天继续研究
  • 日常原神和崩铁

写 Nuxt 遇到的两个小问题

今天在公司做的事情是继续看 Glarity 项目的代码,以及研究谷歌翻译 API 的调用和对现有插件逻辑的接入,前者直接参考竞品的请求数据就完了,后者还需要重构业务代码,因为之前他们写的逻辑是面向过程单一的获取 YouTube 字幕,其中部分逻辑需要提炼出来复用。

晚上写了一阵子 Nuxt,主要遇到两个问题。一个是原先代码设计的问题,不能用 React 思维写 Vue,服务器执行一次 defineComponent 函数之后客户端就不会再执行了,如果数据需要转换,要么修改 useFetch 增加 transform 调整,要么使用 computed 格式化数据。

const state = reactive({
  data: [],
  months: [],
});

const { data } = await useFetch<API.Response<API.Note.INoteTimelineData[]>>(`/api/note/timeline`, {
  query,
});

// 👎 只会在服务器执行一次
if (data.value) {
  const [notes, months] = parseTimeline(data.value.data);

  state.data = notes;
  state.months = months;
}

// 👍 客户端和服务端都会正常执行渲染
const computedData = computed<IComputedData>(() => (
  data.value ? parseTimeline(data.value.data) : [[], []]
));

第二个问题是改为 Nuxt 渲染之后才出现的问题,发现我日记页动态插入的“时间轴”链接按钮消失了。

onBeforeMount is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.

主要是组件变成了 async 的,需要等待服务器端获取数据才渲染组件,这个警告的意思就是遇到 await 的 Hooks 之前就得先执行这个 Hooks,否则 onBeforeMount 的内容就不会执行。把代码改成下面的次序就可以解决问题。

// 我封装的 Hooks,里面有一个 onBeforeMount 的调用
useAction();

// Nuxt 的数据请求 Hooks
const { data } = await useFetch();
1
2
3
奇趣音乐盒技术源于 Kico Player
Emmm,这里是歌词君