Hi there 👋

我是卡斯,欢迎光临我的博客。

Weekly #001 - 开始写点东西

前言 本篇是对 2025-06-23 到 2025-06-29 这周生活的记录与思考。 这篇周报我参考了 pseudoyu 的周报形式,我希望可以跟他一样用文字记录自己的生活。 微信公众号 我有一个建立了超过 10 年的个人博客,但博文不过寥寥十多篇,而且停更已久。我也早在 2018 年就注册了公众号,但未曾发出一篇文章。现在我想重新捡起这个号,争取每周都能写点什么,同步发在博客和公众号上。不出意外的话,这篇会是这个号的第一篇文章。 我曾幻想自己成为一个有旺盛表达欲的人,但事实并非如此,我的表达欲通常在打开编辑框的时候就已经所剩无几。我觉得想要改变别无他法,只能强迫自己多写一点。 运动 本周我尝试了 3 次爬楼梯运动。出乎意料,在边爬边听播客的情况下,这项运动并没有我想象中的那么无聊。我过去的锻炼都是以跑步为主,但是最近重新开始上班之后,工作和通勤使我不能很好地挤出时间来坚持跑步,而且夏天跑步高温和防晒是大难题。经过这周的尝试,我发现爬楼非常经济,走出家门可以马上开始,中途如果有需要的话甚至可以回家补水。除此之外,对我来说爬楼还有个优势,就是可以比较容易得把心率保持在有氧区间,能达到比较好的锻炼效果。 在接近中断了一个月的跑步之后,爬楼似乎让我找回了运动的快乐,让我重新找回了一点对身体的掌控感。 小游戏开发 从上周开始,我尝试学习从头开发一款微信小游戏。客户端框架我选择的是 Cocos Creator 当前最新的 3.8.6 版本。服务端框架选择几经波折。我在看了《TSRPC + Cocos,多人实时对战 So Easy!》 这篇文章之后非常惊喜,对 TSRPC 跃跃欲试。我在 TSRPC 的文档里看到这个框架支持云函数部署,我觉得微信小游戏的服务端用云函数实现可以节省成本。但是经过我的简单尝试之后,我发现事情并不乐观。首先 TSRPC 的文档有点旧了,云函数部署的 example 不论在阿里云还是腾讯云并没有立即成功。其次腾讯云上云函数支持的 Node.js 版本只到 v18.15,拜托现在 Node.js 最新的 LTS 版本都到 v22.17 了好吧,这让我对云函数这个产品的发展非常没有信心。既然不选择云函数部署的话,那服务端的语言我便选择了自己最熟悉的 Go,部署大概率会选择微信云托管的方式。因为是休闲游戏,所以我选择了短连接的方式实现通信。 目前小游戏完成了客户端玩法的大概流程,服务端完成了登陆接口,还没有实现前后端的对接。 Go Web 后端框架选型 上面提到小游戏服务端语言我选择了 Go,框架我最终选择的是 go-nunu/nunu。严格来说 nunu 不是一个框架,是一个 Go 应用脚手架,通过它可以快速搭建一个 Go 的应用的项目结构。我在前司曾经使用过一年多的 go-zero 框架,但是不知道为什么,始终对这个框架没什么兴趣。另外我对 B 站开源的 kratos 框架非常感兴趣,曾经多次尝试用这个框架搭建 Web 后端程序,但是最终都失败了。我感觉 kratos 给我造成了挺重的理解负担,在我看来比较适合搭建大型的微服务应用,在个人项目搭建简单的单体 Web 后端方面,显得非常冗余,写起来不够灵活。最终我选择了更简单的 nunu,它只是一个简单脚手架工具,适合快速搭建一个简单的 Web 后端工程,并且自带分层架构和单元测试的设计。nunu 从各方面来说都非常灵活,用起来感觉很自由轻量,符合 Go 这个语言本身的感觉,相比之下 kratos 总给我一种笨重感。 ...

2025-06-29 · 1 min · 卡斯

Ubuntu 20.04 设置窗口打不开或不显示解决方法

我最近又心血来潮,翻出家里的旧笔记本,装上了 Ubuntu 20.04 LTS Desktop 版本,打算用来偶尔写写代码用。至于为什么选Ubuntu 是因为我觉得选择个最不折腾。事实证明我错了,我不应该信任任何 Linux Desktop 发行版。我刚装完系统,然后一通 apt update 和 apt upgrade,再 reboot 一下。然后我就发现一个蛋疼的问题,设置窗口打不开了,确切的说是不显示了。然后就 Google 一通搜,发现碰到这问题的人还真不少,从 Ubuntu 17 时代就有人碰到了。解决方法说啥的都有:什么 reinstall gnome-control-center,甚至有让人重装系统解决问题的,简直离谱。功夫不负有心人,我总算找到了正确的解决方法,现在记录下来,希望下一个碰到这个问题的可怜孩子能看到这篇文章。 症状 打开设置的时候,能看到侧边栏上有设置图标,但是屏幕上看不到设置窗口。在设置图标上右键点「所有窗口」,能看到设置窗口的预览。初步猜测,设置是正常打开的,但是窗口显示到屏幕外面出去了,导致我们看不到。 解决方法 左手按住Ctrl+F7,然后右手把鼠标向左拖动,记住这个时候不需要按鼠标上的任何键,然后你就会看到奇迹发生了:设置窗口被从屏幕右边拖出来了! 看来问题真的是窗口显示到屏幕的外面去了,我们现在把它拖回来了。但是问题并没有真正解决,当你下次再打开设置的时候,窗口依然显示在屏幕外面。 打开设置里面的「显示器」选项,你会看到,不知道在什么时候,「显示模式」这个选项被设置成了「加入显示器(Join Displays)」,现在把它改成「单显示器(Single Display)」。然后一切就正常了。 参考 https://askubuntu.com/a/1111186

2021-01-30 · 1 min · 卡斯

Docker Hub 根据 GitHub Tags 自动构建带版本号镜像

自从去年买了一个蜗牛矿渣当作 NAS 开始,我就在折腾 NAS 这条路上越走越远。在 NAS上折腾软件和服务最方便的就是通过 docker 部署。所以能找到 docker 镜像的软件,我绝对不会自己折腾安装过程。当然有些时候没有办法找到现成的 docker 镜像,那就自己动手构建镜像。 众所周知,我们可以通过 Docker Hub 的自动化构建存放在 GitHub 上某个 branch 的代码。每当我们 git push 代码到 GitHub 就会触发一次 Docker Hub 构建一个新版本。 但是我最近发现一个问题,就是每次构建出来的镜像都是 latest 版本,没有留下历史版本的镜像,这就很不方便。比如我新构建的镜像有问题,但是没办法立刻回退到上一个版本的镜像,除非我把代码改回去再构建一次,这事儿听起来就很不科学。 当然 Docker Hub 也可以根据 GitHub 的特定 Tag 来自动构建镜像。难道我每次在 GitHub 创建一个新的 Tag 都要在 Docker Hub 上创建一条新的构建规则吗?这事儿听起来也挺蠢的。 有没有办法当我在 GitHub 创建一个任意版本号的 Tag 的时候,Docker Hub 这边都自动根据这个 Tag 的版本号,自动构建一个对应版本号的镜像呢?答案当然是有的,Docker Hub 的自动构建规则支持表达式匹配 Tag。 我们可以新增一条如下的自动构建规则: Source Type Source Docker Tag Tag /^v([0-9]+).([0-9]+).([0-9]+)$/ {\1}.{\2}.{\3} 然后当我们在 GitHub 创建一个 v0.1.1 的 Tag 之后, Docker Hub 会自动构建一个版本号为 0.1.1 的镜像。 ...

2020-05-13 · 1 min · 卡斯

碎片 Collection 第1期

分享每周收集的碎片化知识和资讯。 技术 Ubuntu 18.04 设置静态 IP 的方法 在 Ubuntu 17.10 包括之前版本中可以通过修改 /etc/network/interfaces 设置静态 IP。但是在18.04 之后 Ubuntu 采用了新的 Netplan 程序管理网络配置,那么设置静态 IP 的方法就有些不同了。 sudo vim /etc/netplan/50-cloud-init.yaml network: ethernets: enp0s3: #dhep4: true dhcp4: false addresses:[192.168.2.90/24] gateway4: 192.168.2.1 nameservers: addresses:[8.8.8.8, 1.1.1.1] version: 2 sudo netplan apply 参考:Setting up your static IP address since Ubuntu 18.04 命令行代理设置 export ALL_PROXY=socks5://127.0.0.1:1080 export ALL_PROXY=http://127.0.0.1:8118 unset ALL_PROXY nginx warn:a client request body is buffered to a temporary file ...

2020-03-29 · 1 min · 卡斯

LeetCode 029 — Divide Two Integers

Divide Two Integers https://leetcode.com/problems/divide-two-integers/ 题目要求不用乘法、除法和取模运算符,实现整型除法运算。 既然不能用乘法和除法运算符,那么基本思路就是用减法来代替。不过如果用每次循环减一次被除数这种方式,是肯定会超时的。所以要想办法加速运算。 Solution class Solution { public: int divide(int dividend, int divisor) { long long quotient = 0; int flag = 1; auto dividend1 = (long long)fabs(dividend); auto divisor1 = (long long)fabs(divisor); if(dividend < 0) { flag = -1; } if(divisor < 0) { flag = flag == 1 ? -1 : 1; } std::vector<std::pair<long long, long long> > vec; long long times = 1; while (dividend1 >= divisor1) { vec.emplace_back(divisor1, times); dividend1 -= divisor1; quotient += times; divisor1 += divisor1; times += times; } for (int i = vec.size() - 1; i >= 0; --i) { while(dividend1 >= vec[i].first) { quotient += vec[i].second; dividend1 -= vec[i].first; } } if(flag == -1) { quotient = -quotient; } if(quotient > INT32_MAX) { return INT32_MAX; } else if (quotient < INT32_MIN) { return INT32_MIN; } return quotient; } }; 首先,我们用两个 long long 的 dividend1, divisor1 来把传进来的参数转成绝对值,用 flag 记下符号,去掉负号方便后面的运算。 ...

2019-05-11 · 2 min · 卡斯

LeetCode 011 — Container With Most Water

Container With Most Water https://leetcode.com/problems/container-with-most-water/ 给定一个整型数组,表示坐标轴上的一组相应长度的垂直于 x 轴的线段,要求找出两条线跟 x 轴围起来的容器,能装最多的水。 这题一开始我想用最暴力的方法做两层循环O(n^2)的解法提交试一下,可惜超时了,果然 Medium 难度的题目不会这么轻松放我过去的。 既然 O(n^2) 的解法没有办法通过,那就要想办法能不能找出 O(n) 的解法,一遍循环弄出来。我绞尽脑汁想了很久,尝试了好几种方案,始终没有办法解决。我最终不得不求助于前人的经验,看到别人的解法之后,顿时觉得豁然开朗。 Solution class Solution { public: int maxArea(vector<int>& height) { int max_area = 0; int area = 0; int i = 0; int j = height.size() - 1; while(i < j) { if(height[i] > height[j]) { area = (j - i) * height[j]; --j; } else { area = (j - i) * height[i]; ++i; } if(area > max_area) { max_area = area; } } return max_area; } }; 其实这个解法的关键是用两个指针,分别从左右两边向中间开始遍历,每次算出当前两个指针所指的线围成的面积,并比较是不是当前最大的记到 max_area 里,然后每次排除较短的一条线段,继续遍历。 ...

2019-04-27 · 1 min · 卡斯

ARTS 打卡 第一周

ARTS 是由陈皓(网名「左耳朵耗子」)在极客时间上发起的一个每周打卡计划,详情查看 https://www.zhihu.com/question/301150832 。 Algorithm LeetCode 009 — Palindrome Number Review How to think like a programmer — lessons in problem solving 这是 Medium 上的一篇文章,名字翻译成中文是《怎样像程序员一样思考》。 文章的开头说「像程序员一样思考」实际上是指一种更有效的解决问题的方法。 我们每个人总是碰到各种各样的问题,或大或小。我们处理这些问题的方式,从某种程度上来说,非常随机。一般人们总是这样来解决一个问题:首先尝试一种解决方法;如果这种方式不起作用,那么就尝试另外一种;如果依然不起作用,那么重复第二个步骤,直到你足够幸运解决了问题。有的时候,你足够幸运,但是这是解决问题最差的方式。 解决一个问题最好的方式包括两项:a) 找到一个处理问题的通用框架 b) 不断训练 通用框架 1. 理解问题 理解问题的本质。大多数困难的问题之所以困难是因为你没有理解问题的本质。 2. 做计划 不要在没有做好解决方案的计划之前一头扎进去。要写下解决问题的具体步骤。 3. 分解问题 这是最重要的一个步骤。 不要尝试解决一个巨大的问题,要把它分解成几个子问题。然后依次解决们每一个子问题。最后把这些子问题串联起来,你将会得到原始问题的解决方案。 4. 卡住了? 当你被卡住了,甚至解决不了一个子问题的时候,首先深呼吸,然后你要知道,这是很正常的。 下面是几个你可以尝试的方法: 调试:一步步审视你的解决方案找出你错在哪里。 重新评估问题:从另一个角度观察问题,看看是不是有某些东西能抽象成更通用的途径。 搜索:从搜索引擎寻找解决问题的方式。不管你的问题是什么,很可能别人已经解决过了。 5. 训练 不要期望自己在仅仅一周之后就变得很厉害。如果你希望成为一个很厉害的解决问题的人,那么唯一的途径就是解决很多问题。 不断训练。 Tip ES6 中遍历数组键值对的方法: ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。 for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b" http://es6.ruanyifeng.com/#docs/array ...

2019-04-21 · 1 min · 卡斯

LeetCode 009 — Palindrome Number

Palindrome Number https://leetcode.com/problems/palindrome-number/ 给定一个整型数,判断这个数是否为回文数 我们根据例子很容易得出,负数不是回文数,一位正整数肯定是回文数。 Solution1 首先用最简单的方法,把数字转换成字符串,从两边向中间依次判断对应位的数字是否相等。 class Solution { public: bool isPalindrome(int x) { if (x < 0) { return false; } if( x == 0) { return true; } std::string str = to_string(x); int middle = str.length()/2; for (int i = 0; i < middle; ++i) { if(str[i] != str[str.length() - 1 - i]) { return false; } } return true; } }; Solution2 题目的最后问能否不通过转换成字符串的方式解决这个问题,那就尝试一下吧。不通过字符串,其实我们还是要从两边到中间依次比较对应位的数字。那么问题来了,如何依次取得左右两边的数字呢? 我们以 12341 这个数字为例,想要获得最低位数字 1 很简单只要12341 % 10就得到了。那么如何获得最高位数字 1 呢?我们发现12345/10000 -> 1,也就是说用这个数除以 10^4 就行了,其中的 4 是 12341 的位数 5 减去 1。我们可以通过循环 / 10 的方式取得一个数字的位数。 ...

2019-04-20 · 2 min · 卡斯

LeetCode 008 — String to Integer

String to Integer (atoi) https://leetcode.com/problems/string-to-integer-atoi/ 题目大意就是自己实现一个 atoi 函数把字符串转换成一个整型数字 字符可能以多个空格开始,处理的时候要舍弃左边的空格,从第一个不为空格的字符开始处理; 数值字符串可能以+-号开头,要正确处理; 数值字符串后可能带有多余的非数字字符,这些字符不会对结果造成影响,处理的时候要忽略; 第一个非空格字符不是合法的数字字符或者+-号的时候,这个字符串不是合法的数值字符串,要返回 0; 结果大于 INT_MAX (2^31 − 1) 的时候返回 INT_MAX,结果小于 INT_MIN (−2^31) 的时候返回 INT_MIN 。 这题目看起来并不难,我上手一通操作提交之后,连错了三次,尴尬。。。 以下是可能碰到的部分容易出错的情况: 越界,这个字符串数值可能远远超过了32位整型所能表示的大小,甚至超过了64位。所以不要试图用 long long 来存储最终算出来的数值再跟 INT_MAX 比较,应该在计算过程中随时跟 INT_MAX 发现超过了,就可以停止计算,直接返回相应结果,这样既节省时间,又不会造成 long long 越界; 字符串以多个+-号开头,比如 “+–1234”。从第二个符号开始,并不是数字,那么这个字符串就是非法的,返回 0; 数值字符串和非数字字符中间可能并没有空格,不要被 Example 误导了,比如 “12AB”结果是 12; +-号出现在字符串的中间,比如 “34+1”。这里的+号只能当做普通的非数字字符处理,因此结果返回 34; Solution class Solution { public: int myAtoi(string str) { str.erase(0, str.find_first_not_of(' ')); int last_pos = 0; for(last_pos = 0; last_pos < str.length(); ++last_pos) { if (last_pos == 0) { if (str[last_pos] != '+' && str[last_pos] != '-' && (!(str[last_pos] >= '0' && str[last_pos] <= '9'))) { break; } } else { if (!(str[last_pos] >= '0' && str[last_pos] <= '9')) { break; } } } str.erase(last_pos); //cout<<"_"<<str<<"_"<<endl; if (str.empty()) { return 0; } if ((str[0] == '+' || str[0] == '-')) { if (str.length() == 1) { return 0; } } long long res = 0; bool is_negative = false; int i = 0; if (str[i] == '+' || str[i] == '-') { if (str[i] == '-') { is_negative = true; } ++i; } for (;i < str.length(); ++i) { res *= 10; res += str[i] - '0'; if (res > INT32_MAX) { return is_negative ? INT32_MIN : INT32_MAX; } } return is_negative ? -res : res; } }; 首先用 string::erase 配合 string::find_first_not_of(' ') 去掉字符串开头的空格; 接下来去掉数值字符串后面的多余字符,仅保留要处理的数值字符串; 这个时候可能只剩下一个空字符串或者单个+或者-字符,这里就直接返回结果; 到了真正处理数值字符串的时候了,先判断第一个字符是不是+-符号并记下正负标记; 接下来从前往后遍历,并处理数值,中途要是发现数值已经超过了32位整型就可以根据正负标记直接返回相应结果; 最后根据正负标记返回最终结果,完成! 总结 从这题的 14.6% 的通过率来看 Medium 难度不是浪得虚名,要仔细研究题目里说的可能发生的每一种情况,并做好相应的处理,不然想一次 Accept 还是有难度的。 ...

2019-04-14 · 2 min · 卡斯

Docker 部署 pyspider 踩坑实录

最近在倒腾 pyspider,不得不说这个爬虫框架用起来真的很方便。从编写调试到部署一条龙服务, 对于我这种 Scrapy 苦手来说,简直就是救星。另外 pyspider 还提供了 Docker 镜像,通过 Docker 部署省去了安装依赖的麻烦。不过我实际根据官方文档尝试 Docker Compose 部署的时候还是遇到了一点小麻烦,搜索折腾了一番之后总算弄好了,这里做个记录。 ImportError: No module named MySQLdb 执行 docker-compose up 的时候,报了这个错误,解决方法是安装 MySQL-python。 # Dockerfile FROM binux/pyspider:latest RUN pip install MySQL-python 这里大家可以自己通过上面的 Dockerfile build 一个新的镜像,或者也可以直接使用我上传的镜像kuma1430/pyspider。然后把官方的 docker-compose.yml 里的 binux/pyspider 换成你创建的镜像名或者 kuma1430/pyspider。 Authentication plugin ‘caching_sha2_password’ cannot be loaded 通过上面安装 MySQL-python,我们解决了找不到 MySQLdb 的问题,但是新的问题出现了: sqlalchemy.exc.OperationalError: (_mysql_exceptions.OperationalError) (2059, "Authentication plugin 'caching_sha2_password' cannot be loaded: /usr/lib/mysql/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory") (Background on this error at: http://sqlalche.me/e/e3q8) 这里只要把 docker run --name mysql -d -v /data/mysql:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:latest 里的 mysql:latest 改为 mysql:5.7 就行了。 ...

2019-03-04 · 1 min · 卡斯