技术手记

补遗: 12306 和火车票那点事

两年前在北京折腾 12306 时写过一篇 12306 和火车票那点事, 后来想想还列了一堆东西可以写, 不过后来忙忘了 (其实就是懒…), 现在反正都过时了, 再列出来看看当时的一些吐槽现在是不是还有效 (下面说的都是 2013 年 2 月记录的内容, 关于 2013 年春运, 本文写于 2014 年 12 月, 关注 2015 年春运)

当时觉得 12306 可以提升的几点

  • 订票流程中随机步骤验证码插入, 以更多的抵抗机器行为

这个没法更好的插入, 今年 12306 干了个比较狠的事情, 如果你频繁切换 HOST 去查, 他会随机返回一个查询失败并且把你踢出登陆状态, 后面就算查到票要定时你还得重新登陆, 其实就是随机验证码插入

  • 提前显示票仓, 提升透明度

这个无解, 更透明就意味着没法人为操纵, 万一有突发事件了估计都没法周转. 而且现在是网络和电话提前 60 天卖票, 窗口和代售点延后两天, 还要考虑票额分配比例是否要动态化

公众关心的问题

  • 退票成本太低是当前游戏规则里的最大问题, 导致黄牛可以去刷票抢坑, 退票后再买给要转的人

2013 年底的时候退票费改革了一次, 2014 年底跟着预售期延长又改了一次, 某些程度上把退票成本拉高了 (但是还是有很大漏洞, 具体自己研究就好, 点到为止). 更狠的是退票后不马上退回票仓放出来, 这个就会导致退出来的票不一定自己能买到, 黄牛的压力还是变大了的

  • 开放给第三方的问题是没法保证 “公平性”, 而且, 这不就是代售点么?

目前来看还没有第三方敢去接这个明显供不应求的市场机制, 传闻 12306 也找过 BAT 的人, 做过大规模系统脑子正常的人都婉拒了, 阿里在排队系统上给了建议和帮助, 不过也还是没能解决本质上票不够的问题

  • 公知嘴脸: 铁道部亏钱就是浪费国家资产, 铁道部赚钱就是搜刮民脂民膏, 火车坐的人太多票价低是要担社会责任, 坐的人少票价贵是坑爹

过了两年, 现在公知似乎更像是贬义词了? 不过随着动车越来越多, 公知们又可以有不同的喷点

之前说可以参考下印度的机制, 不过从上面这个长微博里提到的情况看, 大部分时候还是我们对其他机制想的太乐观了…

一些抢票建议

  1. 使用 http://dynamic.12306.cn/otsweb/ 登陆
    a) 不要 https, 坑爹的证书不会用就不要乱来嘛, 而且这样之前 github 被拖挂也不存在了
    b) 不要直接从 12306 主站进, 外面套一堆花花绿绿的不烦么

这个地址已经不能用了, 12306 切了一套全新的, 而且现在就算用 http 也会被强跳到 https

  1. 多开浏览器是有用的
    a) 不确定浏览器的缓存机制, 总之 Chrome/Fx/IE 什么的能开的都开上
    b) 不同浏览器建议用不同的策略

这个还是有点用的, 但是用途不大

  1. 车次/车站别限那么死
    a) 春运等高峰期经常出临客, 而且现在 G/C/D/Z/T/K 都有可能是临客
    b) 前后看几站, 如果不是始发终到不一定有票, 而且非始发站有时候也会有少量票可以抢, 时间可能还错开的, 目标站如果不是该车终点, 有票概率也会偏低 (当然也有之前遇到过武昌福州车的硬卧只卖到武夷山的特殊情况…)

现在分车类型刷已经无效了, 都是一次查询出所有结果, 然后前端 JS 来做过滤, 换发到站还行, 但是 HOST 缓存太严重

  1. 刷余票时车型/是否过路/出发时间等组合变换
    a) 避免被缓存, 更大概率刷出来

同上

  1. 12306 的时间不一定是北京时间
    a) 前后一两分钟都是有可能的, 别傻乎乎按自己电脑的时间卡点
    b) 电话网络两手准备

这个还是有用的, 卡点会从提示预售时间变成预定, 但是票额不一定是马上出来的. 另外如果是学生可以考虑成人学生混刷, 据我观察学生票好像被缓存影响的小一点 (也可能是默认大家都在刷成人, 学生票刷的多了也一样坑)

原文的一些修正和补充

  1. 一个人多个证件的坑已经被补上了, 护照注册的帐号只能用护照买票, 其他同理
  2. 精简版其实就是现在各个浏览器助手做的事情, 当然浏览器助手还做了分布式查询余票 sec 等事情
  3. 现在的浏览器助手做的事情还是没跳出两年前说的那些事, 细节变化还挺大
    a) 分布式取余票的 sec. 这个没什么好说的, 现在就是注意会被踢出登陆, 但是查余票又不用登陆, 所以其实还是无影响, 考虑下全网同步强行提交就好
    b) 验证码识别. 这也没什么好说的, 12306 的验证码加强后还是很容易搞定的, 最多就是错一次重试, 或识别概率不高时主动切换一张验证码
    c) 12306 各种暗处的验证码. 昨天看了一下, 还是变了不少地方, 要重做的话还是要去抓一堆包

最后, 特么今年我的票还没搞定, 之前的工具一懒得修二来不及修, 今天用 UC 搞了一把还是挫, 难道要我去下个 360?

双十一技术篇

去年我还没过来, 据说系统最后是没扛住双十一的大压力崩了, 今年提前好久开始准备, 底线是系统一定要扛住, 准备过程中列了下可能的风险点, 主要是

  1. 系统处理能力
  2. 用户流量出口
  3. 淘宝调用流量
  4. DB 写磁盘压力

挨个分析下

  1. 系统处理能力
    这个反倒是小事, 我们自己的吞吐能力从来没出过问题

  2. 用户流量出口
    今年年初开始就把静态内容放到了七牛上, 自己的流量压力小了很多. 不过盘算下到时候峰值可能还是会爆, 决定加带宽, 由于阿里云的奇葩定价策略, 单买 5M 带宽比买一台带 5M 带宽的虚拟机还贵, 于是我们就再继续买机器做分布式处理, 架构支持分布式就是好

  3. 淘宝调用流量
    调淘宝接口居然还算的是外部流量, 这是之前没仔细考虑过的地方. 看了下大头在读写商品详情上, 这个功能到时候估计怎么加机器都会不够, 提前准备了关闭部分功能做降级的开关, 反正也不是核心功能, 到时候看情况关掉好了

  4. DB 写磁盘压力
    其实 DB 本身是没那么弱的, 我们的主库机器把内存选到了阿里云支持的最大内存, 只是阿里云的硬盘实在是太差了, 一旦有大量持久化操作写磁盘, 磁盘 IO 就扛不住了. 这个我们提前对 DB 里的冷数据做持久化, 降低内存里的数据大小, 避免磁盘 IO 被写死

双十一前一天, 系统的 UV 大概是平时的两倍, 下午继续慢慢往上涨, 最高的时候到了平时的快四倍, 因为今年我们自己的带宽没卡住, 调淘宝接口也没被限流, 来做设置的卖家搞完就走了, 没发生堵塞. 事实证明如果在你的处理能力范围内, 尽可能快的做完事情把人送走才是正确的解决办法, 把一堆人扣在这里慢慢排队处理只会让事情越来越糟糕, 交通枢纽应该都讲究怎么快的把人疏散走, 而不是把人垒在自己这 (说的就是你, 帝都的各种地铁站什么的)

淘宝官方说的是双十一前一天晚上十点接口限流, 我们吸取去年教训在我们的系统通知里说下午六点就暂停服务, 这也算是一个缓解的思路, 让一些人提前进来处理掉. 不过下午六点后 UV 还是明显在涨, 一群又一群赶着想在双十一捞一把的小白新卖家各种不看公告不管系统通知一定要作死的卡点来做设置. 到晚上快十一点淘宝正式限流, 我们这边也按预案暂停服务, 挂通知安抚

凌晨的时候淘宝限流有放开, 其实当天人就不多了, 坡就偷偷的把服务给开了, 但是暂停服务的通知还挂着. 上午估计一堆人发现了系统还能用, 又塞过来各种调, 淘宝那边也大量返回报错和限流提示, 看了下好像也没有想的那么夸张, 坡一狠心把重试次数加大, 居然就直接扛过去了

按系统通知, 是打算在双十一第二天中午十二点才恢复服务的, 结果双十一当天顺利的出奇, 晚上十二点系统也还开在那, 可惜没想到卖家折腾了一天后发现系统还可用就疯狂的做一键重开恢复平时的活动设置, 瞬间涌进来了平日高峰流量的快五倍那么多人, 系统和流量都没问题, 但是 DB 跪在了阿里云这不靠谱的磁盘上. 只能暂停服务, 继续挂通知, 来的快散的也快, 估计过了十来分钟大家看没戏就很快散了, 然后把服务重新恢复. 第二天白天人一直就不多, 一直观察到十二点也没出现预期的瞬间高峰, 可能还是大家发现系统是可用的, 没有卡点来做恢复操作. 双十一就这么有惊无险的过去了

习惯性总结下

  1. 系统的可扩展性很重要, 关键时刻如果能通过加机器搞定的事情就去加机器搞定好了, 多花的那么点钱完全不是个事
  2. 对自己系统的能力和客户的使用习惯要预估好, 我们最后还是托大了下, 没预料到双十一结束后的那个瞬时高峰
  3. 阿里云还是要给力才行, 据说今年会全面换成 SSD, 到时候先开个 DB 从库上去当小白鼠, 靠谱了就全切

当然, 每年都有各种二逼卖家改错价找上门哭诉或要挟的, 这种作死行为完全拦不住啊, 不看通知不看公告不看系统提示你们真的是来做生意的么, 然后每年继续还有二缺天猫卖家双十一当天跑过来说你们怎么不能用我要给你们差评, 啊叻天猫早就通知了双十一当天只有官方工具生效的好吧, 更二缺的天猫卖家发现双十一当天普通折扣不生效了就去改原价, 然后晚上不及时改过来第二天凌晨到早上被人超低价狂买然后跑来说你们软件有问题乱改价我亏了你们怎么赔我, 切爱谁谁吧, 淘宝城在文一西路上过去拉横幅要跳楼找马总解决吧

百度这贱人

前阵子家喵在笔记本上装了个什么考试题库, 莫名其妙被装上了个百度卫士, 也不知道是题库自带的, 还是喵没事点软件自动更新被别的东西带上的, 反正我看到后很快卸载掉, 当时也没发现有什么特别的, 只是感慨最近百度这种捆绑太厉害了, 公司有几台客服机也莫名其妙的被装上过

又 Mac OS X 下不能用招行专业版, 虚拟机也容易出问题, 有时候要在网络上转钱只能用家喵跑 Windows 7 的笔记本插着招行的 U 盾操作, 但发现 IE 很诡异的打开就变成了 hao123, 应该还是被哪强奸了. 按常规检查思路, 先查看 Internet 选项, 里面设的是空白页, 除了第一次开 IE 后面开新 Tab 也是空白页. 然后看快捷方式是不是被加了小尾巴, 不过看了半天也没发现啥不对, 倒是我把 IE 图标从任务栏解锁后开始菜单和桌面上的 IE 图标都出错了, 用腾讯电脑管家修复掉

怀疑是之前的考试题库软件在搞鬼, 确认喵考过暂时不用了就把一堆无用的都卸掉并清理, 重启后还是一样. 仔细观察 IE 发现在打开的一瞬间地址栏是 ***.paoba.com*** 一串, 然后瞬间跳到 hao123, 放 Google 搜 “IE paoba” 发现果然是有人在里面搞鬼

IE主页被恶意篡改为hao123的解决办法 这里面提到的, 就是百度卫士在搞鬼, 写了个注册表键值在 baidu/baiduprotect/lockiestartpage 锁定 IE 主页为 http://www.paoba.com.cn/tn.php?k=2, 啧啧, 就这还好意思叫卫士, 而且干这事的软件躲在 C:\Program Files (x86)\Common Files\Baidu\ 下, 注意中间那层目录是 Common Files, 不算在正常的已安装软件里, 难怪常规卸载都卸不到他, 上次明明也记得卸载了, 但是后来清文件的时候提示正在使用就没管他以为重启后就会好的

找到问题根源了就好办, 开机按 F8 引导 Windows 进安全模式, 百度还算没有彻底良心泯灭, 留了个卸载程序, 在安全模式下卸载, 并把整个目录都删除, 这下没提示文件还在使用了, 重启后正常

很少公开骂人, 但是度厂最近的捆绑安装流氓驻留死不让卸载做的有点过分了, 而且这种强奸浏览器首页的手段贱的简直不可理喻, hao123 现在都要这么导流了? 再一个那个所谓的安全卫士, 人家都明确不要了就不要死赖在那了吧, 居然藏了起来, 还能自动更新. 一直远离 360, 没见过 360 怎么耍流氓, 现在倒是看到前东家 “放下身段接地气” 的行为, 啧啧, 唉

二缺运营配上苦逼程序员

前段时间情怀锤在假货淘上出了点事, 关注了的人应该知道大概是怎么回事, 为了方便笨狗吐槽, 再把事情简单说下

锤子手机在天猫上做预售, 让买家去预定, 页面上会显示一个当前预定人数
有好事者发现这个预定人数无论在什么时候都能被 3 整除, 而且新点一下预定, 页面上的数字会 +3 而不是 +1
然后有人翻页面代码发现是前端的 js 里做了这个 (order_num)*3 的操作

天猫那边开始调整, 中间还出现了 6xxxx.5 个人预定的情况, 被吐槽半个人是怎么来的, 应该是 *2.5 了
而且还有 *0 变成前端显示 NULL 人预定的情况, 最后这个地方变成了 (order_num)*1
发现事情闹大了盖不住, 天猫官方微博帐号发声明说后端丢数据, 程序员为了让数字看起来正常点自己乘了三

被愤怒的程序员吐槽扯鸡巴蛋并且翻出来金立等品牌这个地方也有乘系数等操作, 明显看是个模板配置
再然后有内部人士表示这个地方其实就是个可配置的公式, 运营就可以操作, 不需要过程序员和走上线操作
最后终于某高管私人微博帐号发个靠谱点的公告处罚了一堆人, 并且向程序员道歉, 当然还嘴硬只是显示问题

其实懂的人一看就明白是怎么回事了, 运营想获得一个更好看的数字, 就要求程序员在后端数字基础上做一个放大, 但是不同的时候放大系数不一样, 技术部门为了省事, 运营部门也想要更大的控制权, 就把显示的地方弄成一个可配置的公式

按阿里系运营才是老大的风格, 这事多半还是运营部门主动要求技术部门去做而且放开所有控制的. 不过阿里的技术那边也没做好, 比如居然能出现小数, 好歹也取个整吧, 比如乘 0 后前端显示异常, 这个也弄个最小值限制吧. 执行的运营也比较二, 乘三这个太容易被发现了, 你看看人金立, 乘个六后面还加个常数, 这边要是乘三加一或加二, 估计一下还没人能看出来, 当然, 乘七再加常数就更好了. 对运营的智商做乐观一点的判断, 可能他们还是考虑过为什么乘三的, 因为乘 2/4/5/6 更容易发现, 不过因为他们的智商也就只能数到六, 没有再往下试下 7, 也不知道在后面加个常数

这事被爆出来, 很大的原因应该是这次是给锤子做预购, 老罗一直满天下吹牛逼, 吹的很多人都路人转黑, 碰到出糗的时候大家都很兴奋, 虽然事后罗永浩说他们不知情是躺枪, 但是我觉得他们还是脱不了干系, 这事做成这样不可能不知道, 至于罗永浩本人是否提前知晓这个就不好说了, 反正作为一个喜欢看说大话的人出糗看热闹不嫌事大的笨狗, 表示老罗能吹出那么大的牛, 各方面也还是要做到无可挑剔才行. 至于金立什么的那才完全是莫名躺枪, 本来就是行业内的潜规则, 这下没得玩了

这一次的事情, 应该会让更多技术线的人, 在选择工作时会偏向非运营主导的团队. 因为在运营主导的团队里, 技术人员完全没地位, 运营说做什么就做什么, 说做成什么样子就做成什么样子, 长此以往, 技术人员自己也会变傻变钝, 比如这次如果技术还有点自己的思想, 就应该知道在满足运营要求的同时, 自己也不要对运营人员的智商有太高期望, 取整和校验还是要做下的

想起来以前在某家的时候的一次大事故, 也是运营配错个东西, 导致线上所有流量没有广告. 当时几乎整个公司相关的技术团队全部投进去查问题了, 还算快的响应, 半小时后技术这边查到是有 PM 配全局关键词黑名单的时候写了个 *, 就是所有的词都不行. 事后该 PM 也很牛气的说这个事情是我做错了, 责任都我来承担, 不过你们技术也有问题, 系统上也不做下校验就把我这么放过去了. 看看, 技术从来都这样好事轮不上, 出事要背锅, 当然了, 这次事故里技术那边做这个黑名单系统的时候加一些校验也还是有必要的

类似的需求一开始的时候多半是临时需求, 说做完以后就不用了, 所以先短平快搞个能用的. 等后面发现临时需求变成长期需求时, 现在系统能用, 再去完善的工作算不进 KPI, 那也没人有兴趣去搞, 反正之前的需求方自己也知道细节, 不会出事. 等需求方那边换人后有一些细节可能没交接好, 那就要出事了, 比如公式里一定要全部是整数操作等

扯了这么多笨狗想说啥? 一是团队里技术要有发言权要有地位, 而且这个地位要自己不断用事前正确判断来证明自身争取而来, 自己不去想只管做傻事那也别怪自己没地位; 二是不合适的 KPI 文化会害死人, 比如技术因为不算 KPI 不会去完善临时转长期的系统, 比如运营会为了 KPI 数字毫无底线的造假; 三是阿里系和锤子的品德还是有问题, 脑残粉会说做到这样已经不容易了你看看行业内其他家做的更脏, 笨狗的观点是你自己说成什么样就要做成什么样, 别管别人脏不脏

最后来个段子吧

上天猫, 乘以三, 就够了

内容文件的分发保障

曾经我在知乎上问过这么个问题 为什么很多网站的内容储存用别的域名?有什么好处? 答案里不少提到做 CDN 和 cookieless, 果然这些事在做大了后总会碰到一些, 小记一下我们在这方面走的路, 以及坑爹之处如何绕过

问题主要是怎么靠谱的分发内容文件 (我的叫法, 就是算成 content 的那些, 比如 .js .css 什么的, 以及图片)

首先是怎么保证客户浏览器里不要把我们已更新过的内容文件还读本地缓存里的老版本, 这个还比较简单, 在 html 等引用这些文件的地方加一个无实际用途的参数, 一般选版本号或时间戳, 这样有更新后, 浏览器看到的引用链接参数变了, 就不再读缓存而是去服务器取

然后伞破觉得我们的内容文件都走聚石塔 (淘宝开放平台版的阿里云) 太吃带宽, 而且阿里云的带宽又一点也不便宜, 想办法把这一堆给弄到了七牛上做 CDN 分发, 算是把自己服务器的带宽压力给降了下来

但是国内各色小运营商花样作死总会中枪, 春节期间江西移动自己做了个坑爹缓存, 取七牛上的文件完全搞错了, 客户又过来闹怎么不能用啊, 没办法过年期间平安最重要, 反正用户量不大, 带宽没压力就先切回了自己服务器

期间还碰到搜狗浏览器自己的那个极速模式也有坑爹缓存, 也可能取错, 这时候只能在客户过来反馈为什么用不了的时候让用户从极速模式切换到兼容模式, 这种间歇性抽风完全没法防

年后想着还是要把七牛那个利用起来, 如果用户取不到再降级到自己服务器, 于是在用户 session 里加了个参数用来标记用户取七牛文件是否正常, 默认为真, 用这个值来填充页面模板返回不同的内容文件地址. 然后在页面上写了留了个切换 session 的链接, 再在 css 里把这个元素设定为 display: none, 这样如果取 css 正常, 那前面说的那个切换链接和相关说明文字不可见, 对用户也没影响, 要取不到页面显示就乱掉但是那个提示会很明显, 用户点一下就在 session 里切换过来

本来这样应该就完了, 结果 360 这个奇葩在自己浏览器里不知道怎么想的把我们部属在七牛上的一个 lib.min.js 给判成有风险脚本不给加载, 这样客户那边有不少操作没法响应, 但是 .css 是能取到的又不会提示切换, 最后想了个更猥琐的办法, 在 .js 里都加了个没意义的空函数, 然后在首页写一段页内 js 来检测那个空函数是否存在, 如果不存在说明没加载到也还是有问题, 提示客户跳

其实提示客户跳还可以变成自动帮客户跳, 但是奇葩 IE8 (更低版本 IE 我们直接不支持了) 和用 IE 内核的比如搜狗浏览器执行 js 总是会跟正常浏览器不一样, 可能会死循环 (当然也可能是我们代码写挫了), 然后客户那边就不停的自动刷刷刷, 这事最后反正还是搞定了, 不过具体怎么搞定的我也没去跟进了解前端的事…

这样安心过了大半个月, 突然又有客户过来说你们怎么又打不开, 让对面截图发现是 avast 认为页面里有脚本无限循环直接阻止访问页面访问, 这个… 我们好歹也换参数了这个不算死循环吧, 但是还是没办法, 只能让用户关掉这些水土不服自以为是的安全软件

故事到这里暂时没有更坑爹的事情出现, 记录下来看能不能帮到后来人

也说 Mac 的不好

年后在新团队弄到 MacBook Pro w/ retina 一台, 开始了从 Windows 阵营投奔 OS X 的路程, 到现在用了也有三个多月了, 也说下 Mac (包括 OS X, rMBP 等) 的一些奇葩问题和解决方案

字体渲染

我没用 Mac 前就老看到不同的地方有各种果粉吹 Mac 下的字体渲染, 比如 Windows 的 TureType 会导致缺笔画啦, 微软雅黑其实是破坏了字的逻辑结构啦, 但是曾经借到一台 MacBook Pro 怎么看这字都很糊啊, 换字体也一样, 难道是睡眠不足导致视线模糊?

今年拿到 retina 屏的新本, 顿时觉得世界美好了好多, 插上外接显示器, 果然字又糊的一塌糊涂. 这时才明白, 苹果的字形确实如果粉说的是尊重字的本身, 但是这只有普通分辨率下的大号字体或高分屏下才能看的出来, 普分下字糊成那样果粉们你们都眼瞎了么

Windows 那套渲染技术和微软雅黑等字体是为液晶显示器专门优化过的, 保证在普通分辨率下也能做到笔画清楚, 不知道果粉们的优越感是不是建立在那个年代微软还没出雅黑以及 Mac 渲染技术在 CRT 显示器下可能确实更好的基础上. 没接过 CRT 显示器也没再回到以前的 Windows 系统, 反正现在用外接的 U2312 或 U2412, 从 Mac 切到 Win 下, 都觉得字体锐利清晰了好多

解决办法: 无解, 只能换 4K 显示器看看在能到 retina 级别的分辨率下的表现吧, 不过这样的话 4K 显示器也还是只能放 2K 显示器能容纳的内容了

MBP 笔记本键盘缺物理按键

这里指的是 delete (非 backspace), PageUp/Down, Home/End, PrintScreen 等, 用组合键可以实现但是真的很麻烦. 现在越来越多的笔记本都学这个, 真是好的不学坏的学挺快

解决办法: 用各种组合按键, 或外接键盘来实现

  • Fn+Backspace: Delete (Mac 键盘上 Backspace 处印的是 delete, 但逻辑是 Bs)
  • Fn+Up: PageUp
  • Fn+Down: PageDown
  • Fn+Left: Home
  • Fn+Right: End
  • Cmd+Shift+3: 截全屏并保存截图文件到桌面, 如果有多个显示器则每个显示器一张图
  • Cmd+Shift+4: 截屏幕上选定区域并保存截图文件到桌面

关于截图, 如果同时按下 Ctrl 键, 则不保存到桌面只保存到剪贴板, 可以在其他地方粘出来. 不过实测 Cmd+Ctrl+Shift+3 保存到剪贴板的只有笔记本上的主屏, 外接屏没有, Windows 下 PrintScreen 多屏的话会默认成拼接的大图

另外 Windows 有 Alt+PrintScreen 截当前活动窗口图的快捷键, Mac 下我没找到原生的快捷键, 用 Cmd+Shift+4 的方式还要自己小心拖动, 而且一松手就截出去了, 没有调整空间. 所以不是截全屏的话我一般还是用 QQ 等自带的截图工具, 会识别活动窗口自动适应大小, 选完框还能拖动调整大小, 能加框加箭头等标注

奇怪的 Home/End 键逻辑

外接键盘下 Home/End 键逻辑和 PC 的不一样, 我想的是移动到行首或行尾, Mac 默认的逻辑是移动到可编辑区域的开头和结尾 (比如一个大文本框, 是移动到最开始和最末尾, 而非当前行的开始和结尾). 一般用到这个是我写东西的时候要从当前光标选到行首或行尾, 就算用触摸板连续单击选词或整行这个效果很赞, 但依然不是我要的效果

解决方法: 自己重新做键映射, 我参考的 http://mwholt.blogspot.com/2012/09/fix-home-and-end-keys-on-mac-os-x.html 这篇文章, 摘录主要部分如下

编辑 ~/Library/KeyBindings/DefaultKeyBinding.dict 这个文件 (如果没有就新建一个, 如果目录都没有就连目录也新建), 添加如下代码 (我只选了这几个我要的), 保存后重启

{
    /* Remap Home / End to be correct :-) */
    "\UF729"  = "moveToBeginningOfLine:";                   /* Home         */
    "\UF72B"  = "moveToEndOfLine:";                         /* End          */
    "$\UF729" = "moveToBeginningOfLineAndModifySelection:"; /* Shift + Home */
    "$\UF72B" = "moveToEndOfLineAndModifySelection:";       /* Shift + End  */
}

过于霸道的全局快捷键

比如 F11, F12 这种正常码农多半会配成 Vim 或 Screen/Tmux 快捷键的功能键, 居然都有系统默认全局快捷键映射上去, 一开始还以为我在 Mac 下配置文件工作不正常, 怎么就识别不出来 F11 和 F12 呢

解决办法: 改掉就是了. 在 系统偏好设置键盘 里, 把 快捷键 标签页里的所有设置都浏览一遍, 觉得有用但是不会的去学一下, 觉得有用但是想改按键的自己改下 (比如可以把截全屏保存到剪贴板的快捷键改成 PrintScreen, 也就是 F13), 觉得没用且会影响自己的关掉 (比如 F11 显示桌面, F12 显示 Dashboard), 其他没用的留着也行 (比如 F14/F15 调屏幕亮度, 实际这两个键是 PC 键盘上的 ScrollLock 和 PauseBreak)

缺失的全局快捷键

习惯了 Win+L 锁屏结果在 Mac 上没找到

从 Win7 开始习惯了按 Win+Left/Right 来对窗口分屏, 结果 Mac 也没有相关实现, 搜的时候还看到知乎上有脑残果粉说为什么要分屏啊, 苹果使用的多桌面模式是多么优雅一定是你不会用而且要真的好苹果为什么这么多年还不跟进呢, 我去你二大爷的知乎脑残果粉, 你到底用了多久的 Mac 啊, 你试过外接一个超过 1440 分辨率宽的显示器么, 你试过码农至少要三个窗口才能好好干活么 (一个放搜索结果或参考资料用于抄, 一个是自己写的, 还有一个是运行和看结果的), 这样把外接显示器左右对半各放一个窗口连抄带写, 笔记本原生屏幕放运行环境和查看结果的浏览器才最好吧

找类似 Win+Left/Right 把窗口调整成占据屏幕一半的第三方软件, 多倒是很多, 不过要么收费 (我都没去找破解尝试), 要么在有外接显示器的情况下工作不正常

解决办法: 锁屏的快捷键上一篇博文 Mac 锁屏的各种方法已经搞定了, 一句话解决就是 Ctrl+Shift+Power

分屏的软件最后通过 Spectacle 很好达到我要的效果, 下载地址在 http://spectacleapp.com/, 作者官方开源在 https://github.com/eczarny/spectacle

软件清理

OS X 那种把单个应用都打包到一个 .app 里的设定在安装和卸载时确实很方便, 但也有一些奇葩残留, 比如右键菜单里的打开方式经常会有重复的, 比如删除个 Office 太麻烦了以至于我彻底断了去装个盗版的念头 (微软官方的删除 Office 流程: http://support.microsoft.com/kb/2398768/zh-cn)

解决办法: 能不乱装的就别乱装吧, Mac 上我还不想用类似 360 或腾讯电脑管家之类的东西去清理顺带被这些流氓不知道在电脑上干了什么. 至于右键菜单里打开方式有重复, 隔断时间清理一下, 参考自 http://www.zhihu.com/question/20599306, 摘录如下

终端下执行如下命令, 然后重启 Finder (Ctrl+Opt 点 Finder 图标)

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local\ -domain system -domain user

漏电

新 MBP 拿回来时手放笔记本上各种酥麻, 尤其是靠近电源接口那个地方, 当时心里就骂都漏成这样了这接口还好意思叫 MagSafe?

试过把电源适配器插头换成延长线的三脚插头, 无效, 试过在不同的地方用避免电压波动过大或接地不好的情况, 无效. 搜解决方案, 答案除了前面两个我试过的方法, 就是劝还是默默忍受好了, 当然也不排除我这样的果黑反讽 “这是苹果为了让用户保持清醒故意弄的” 或 “这么高大上的按摩你们居然还嫌弃”

解决办法: 无解. 不过我用了几个月后好像几乎没漏电了, 还是一样的在家和在办公室用, 不知道跟我把线卷来卷去卷多了有关

一些个人推荐的好东西

X-mirage

将 Mac 当作一个 AirPlay 的 Server, 有时候想把手机上的内容播放到大屏幕上这货就有用了 (最近看舌尖上的中国我就是先在手机爱奇艺上离线, 然后投到显示器上看, 一是没广告, 二是屏幕大看着才爽吧), 或者要给人演示手机上的功能或录像, 在手机上操作太蛋疼, 还是投到大显示器或投影仪上看才好

搜类似解决方案之前很多建议都是 AirServer, 但是这货要钱, 我又想矜持点不用盗版, 试过免费实现又各种不靠谱, 然后才发现 X-mirage 这货也挺好用, 官网是 http://www.x-mirage.com/index.html. 在 stacksocial 上偶尔有限免, 我是上次限免直接免费弄到的, 最近又有, 还有三天结束, 欲购从速: https://stacksocial.com/sales/the-mac-freebie-bundle-3-0

Alfred

据说一定要用带 Powerpack 的 2.x 才爽, 不过自己找破解试了下对 Powerpack 好像也没太大需求, 就还是用的免费版. 此等神器的各种教程网上一搜一大把, 我就不班门弄斧了

看到不少地方有组织几个人一起团 family license 省点钱, 但实际上非 family 成员使用 family license 还是违反协议的, 只是大家觉得自己付钱了相对心安理得一点吧

Chrome 的豆瓣电台应用

懒得再开个浏览器窗口, 用的也很方便, 安装地址 https://chrome.google.com/webstore/detail/%E8%B1%86%E7%93%A3%E7%94%B5%E5%8F%B0/pildlfoeifnhlckepgfiphlnaphcfhfh

Mac 锁屏的各种方法

2018-06 更新:从 macOS 10.13.x 开始,苹果官方提供了 Ctrl+Cmd+Q 的快速锁屏快捷键,可以通过点菜单栏左上角苹果小按钮,在下拉菜单关机下面看到


之前用 Windows, 习惯了 Win+L 的快捷键锁屏, 暂时离开电脑时可以很方便的锁定机器避免被人恶搞, 最主要还是一个安全的习惯. 但是换 MacBook 后遇到的大问题之一就是没找到锁屏快捷键… Cmd+L 啥都不是, 尝试过的方法包括

合上盖子

如同 Windows 的笔记本, 合上盖子实际上并不是锁定 (Lock screen), 而是进入睡眠 (Sleep), 这样会断网, 直接后果就是下载会断, 远程连接也会断掉, 如果有持续跑的程序也会挂掉. 我在 Windows 下会把合上盖子的默认操作换成什么都不做, 就算是去开会或临时把本带到别的地方, 一合盖子就走到了打开还是之前的状态, 如果需要锁着电脑跑程序或下载, 就按 Win+L 再合盖, 方便快捷

合盖子的另一个问题是, 如果开机状态下接了外接显示器, 合上盖子只是把笔记本自己的屏幕输出给关掉, 其他什么都没影响, 完全没起到锁屏的作用, 不管是 Mac 还是 Win 都一样

短按电源键 (Power)

去搜索 OS X 怎么锁屏, 很多地方会提到短按电源键, 可惜这个也是睡眠, 问题同上

设置触发角进入屏保

不少教程也是教的这个, 大致流程如下

  1. 进入 系统偏好设置
  2. 安全性与隐私 设定页, 通用 标签卡里将 进入睡眠或开始屏幕保护程序后要求输入密码 的时间改成 立即
  3. 桌面与屏幕保护程序 设定页, 屏幕保护程序 标签卡右下角设定 触发角, 行为选 将显示器置入睡眠状态

注:

  • 第 2 步也可以在 Mission Control 设定页的左下角找到 触发角 的设置
  • 第 3 步的触发行为也可以选 启动屏幕保护程序, 但是这样就不是我想要的完全黑屏那种锁法

设定好后, 把鼠标移动到屏幕上对应的角落, 停留一下就会触发设定的行为. 不过触发角的误触发概率远高于正常期望行为, 超级坑. 如果设定触发行为时按住 Cmd, 让触发行为变成鼠标停留角落且按 Cmd 才起作用, 这样似乎又有点反应迟钝. 反正我是试了下就放弃了触发角的方式

使用钥匙串

这个方法的出场率一点也不低于触发角

钥匙串访问 的偏好设置中, 通用 标签卡里将 在菜单栏中显示钥匙串状态, 然后在菜单栏就可以看到一把锁的图标, 点击那个锁就有 锁定屏幕 的选项

这个方式相比较触发角确实误操作率下降了, 但是要用鼠标移动到一个特定位置再点击, 复杂度又上去了, 不爽

用脚本/命令

在各种搜索 “lock mac” 的过程中居然还找到可以用脚本锁屏, 大体参见 http://apple.stackexchange.com/questions/73995/how-do-i-lock-the-screen-using-a-keyboard-shortcut-on-os-x-mountain-lion-with-a 里的说法, 我试过脚本可以运行, 但是触发出来是 Windows 下快速切换用户而不是锁屏的效果, 即屏幕还有显示且是选择登陆用户的界面. 不过怎么触发脚本运行太麻烦了, 没继续尝试

http://apple.stackexchange.com/questions/111485/how-to-lock-screen-on-macbook-air 里还提到了另一个在 OS X 10.9 下的 pmset displaysleepnow 命令, 亲测可用且是锁屏, 不过还是触发麻烦的问题没法解决

用 Alfred

Alfred 这个神器默认带了 Lock 这个系统指令, 唤起 Alfred 后输 Lock 就可以在 PC 键盘上也唤起锁屏了, 可惜这个一是操作略麻烦, 二是实际效果还是快速切换用户而不是锁屏

如果有土豪买了 Alfred 的 Powerpack, 可以试试看在 Powerpack 里设置用快捷键触发上面的脚本或命令

真正的快捷键

http://apple.stackexchange.com/questions/28164/keyboard-shortcut-to-sleep-a-mac 这个帖里比较完整的提到了各种快捷键 (老键盘上把 Power 换成 Eject 键):

  • Ctrl+Shift+Power: 关闭屏幕
  • Cmd+Opt+Power: 睡眠 (sleep)
  • Cmd+Ctrl+Power: 重启 (restart)
  • Cmd+Ctrl+Opt+Power: 关机 (shutdown)

通过 Ctrl+Shift+Power 终于搞定了怎么键盘快速锁屏, 不过三个键还是不如 Windows 下两个键来的方便, 而且 Mac 键盘没有右 Ctrl, 不像 Windows 就算键盘没有右 Win 键, 手掌张开拇指和小指一个按左 Win 一个按 L, 左右手也都不会很麻烦

映射键

如果用外接键盘, 一般是没有 Power 键的 PC 键盘布局, 不过没关系, 我们有一个在差不多位置但是几乎没人用的 Pause/Break 键, 用 KeyRemap4MacBook (下载地址: https://pqrs.org/macosx/keyremap4macbook/) 里搜 “Pause/Break”, 把这个映射成 Power 就可以了

为了给菜单栏 (menu bar, 也有叫状态栏 status bar 的) 省点空间, 在设置的 MenuBar 选项页把 Show icon in menu bar 关掉就行, 以后想用了再通过各种唤起应用的方式把 KeyRemap4MacBook 找出来就是

OS X 支持 NTFS 读写

苹果的 OS X 明明已经支持 NTFS 分区读写, 但是默认情况还是按只读挂载, 查了些资料小修改了下, 就可以开启原生读写了

# 用 root 身份做如下操作 (高危! 请切记自己在干什么)
sudo -s

cd /sbin
# 将系统自带的挂载程序改名
mv mount_ntfs mount_ntfs_orig
# 新建我们要的挂载脚本并编辑
vim mount_ntfs
#!/bin/sh
/sbin/mount_ntfs_orig -o rw "$@"
# 保存退出后改一下权限
chmod a+x mount_ntfs
# 都搞定了, 退出 root 身份
exit

不过这个方法还有几个小问题要注意

  1. 分区最好有卷标, 默认的 “未命名磁盘” 可能无法挂载. 如遇无法自动挂载可以先在终端下改个名再试
# 获取对应分区的 DiskIdentifier (类似 disk1s1 这样的)
diskutil list
# 分区重命名
diskutil rename disk1s1 newname
  1. 网络上其他方法经常会让把脚本里的挂载参数加上 nobrowse, 这个参数就让挂载的分区不显示成新的移动磁盘, 然后又有一堆方法教怎么在 finder 侧边栏能快速访问这样挂载的 NTFS 分区. 其实 man mount 看明白 -o 参数后面的设定就明白了, 去掉那个画蛇添足的 nobrowse 吧

// 最后这个 nobrowse 的参数, 似乎加上后又是只能在 finder 显示但是不能写, 搜了下也没有合理的解释, 如果不行还是先加回去吧
// 为了方便访问, 可以在 finder 里用 cmd+shift+G 打开跳转, 输 /Volumes 进入所有磁盘目录, 然后在用 cmd+shift+T 将 /Volumes 保存到边栏

Windows 7 USB 设备插入后识别过慢的解决

最近给新公司弄了一批客服机, 键鼠或 USB Hub 插上去后要非常久才能用, 看了下是因为在从 Windows Update 找驱动, 而杭州联通连微软服务器延迟高速度慢 (还是最近海底光缆问题?), 这个过程只会慢的让人想死

在公司首席 IT 工程师伞破驴的指导下, 关闭默认找驱动就可以了, 具体步骤如下

1. [计算机] -> (右键) -> [属性]
2. 左侧 [高级系统设置] -> (点击打开) -> 上方 [硬件] 标签
3. [设备安装设置] -> (点击打开) -> 选 [否, 让我选择要执行的操作]
4. 选 第二个 [在我的计算机上找不到驱动程序软件时从 Windows Update 安装] 或 第三个 [从不安装来自 Windows Update 的驱动程序软件] -> [保存更改]

应该其他 Windows 版本也有类似问题, 在遇上中美网络连接出问题的时候也可以改下. 平常改掉也可以让插 U 盘/移动硬盘/鼠标等设备时更快用起来

饭团性能优化记

缘起

前年冬天在人人时, 为了方便组里一起吃饭的同学们互相算账, 参考以前度厂的饭团设置, 在团队里拉起一个饭团, 然后写了个小系统来记账

设计

一开始的想法是这样的

1. 饭团设置一个团长, 团长管饭团的钱, 出去吃饭时由团长付钱
2. 每顿饭按人均消费额, 扣除参团人的余额
3. 每个人把钱交给团长, 余额不足时由团长催促交钱

所以设计的数据模型是

    人 {
        id,
        姓名
    }
    饭 {
        id,
        付款人,    # 外键, 多对一
        参与人,    # 外键, 多对多
        消费额
    }

吃饭就在饭那个表里加一条记录, 充值也算一顿特殊的饭. 每顿饭后的账面和最终余额按时间遍历所有记录实时算, 这样一是省了记每顿饭后余额的存储开销, 二是避免有历史修改而需要更新余额表一堆数据的麻烦事. 考虑到饭团也就十来个人, 在可预见的未来数据量人最多到百级, 饭撑死也就是千级, 每次遍历的代价应该也不大 (事实上在我写本篇文章的时候, 饭团历史总人数不到 20, 算上充值转账等总共也不到 500 顿饭)

这个余额实时计算的思路和 BitCoin 的余额判断方法也挺像的, 正是因为我写饭团踩了不少坑, 所以我觉得 BitCoin 某些方面还是有很大问题的, 这个回头另外讨论

另外为了统计方便和可追查, 希望记录每顿饭是哪天在哪吃的, 新增和修改数据

    饭 {
        ...
        日期时间,
        店        # 外键, 多对多
    }
    店 {
        id,
        饭店名
    }

后来考虑未来可能有人因为转岗或离职离开饭团而饭团里还有余额需要退款, 新增两个特殊的店来记录充值退款操作, 自此数据模型设计完毕. 最终的 sqlite schema 如下

    CREATE TABLE "ft_people" (
        "id" integer NOT NULL PRIMARY KEY,
        "name" varchar(200) NOT NULL
    );

    CREATE TABLE "ft_deal" (
        "id" integer NOT NULL PRIMARY KEY,
        "restaurant_id" integer NOT NULL REFERENCES "ft_restaurant" ("id"),
        "pay_people_id" integer NOT NULL REFERENCES "ft_people" ("id"),
        "deal_date" datetime NOT NULL,
        "charge" real NOT NULL
    );
    CREATE INDEX "ft_deal_75ae3b0c" ON "ft_deal" ("pay_people_id");
    CREATE INDEX "ft_deal_be4c8f84" ON "ft_deal" ("restaurant_id");

    CREATE TABLE "ft_deal_peoples" (
        "id" integer NOT NULL PRIMARY KEY,
        "deal_id" integer NOT NULL,
        "people_id" integer NOT NULL REFERENCES "ft_people" ("id"),
        UNIQUE ("deal_id", "people_id")
    );
    CREATE INDEX "ft_deal_peoples_1a9336ea" ON "ft_deal_peoples" ("deal_id");
    CREATE INDEX "ft_deal_peoples_3cff102f" ON "ft_deal_peoples" ("people_id");

    CREATE TABLE "ft_restaurant" (
        "id" integer NOT NULL PRIMARY KEY,
        "name" varchar(200) NOT NULL
    );

因为懒得自己去管理数据的写和更新操作, 刚好那段时间看了下 django, 感觉自带 ORM 和 admin 组件的 django 会是开发的好选择, 于是对着 tutorial 学过去后就开工了. 很快写完, 框架用的 django1.5, 数据库用 sqlite, 页面是裸写的 html, 没有任何 javascript, 仅有的一点 css 也硬编码在 html 文件里了

功能和美化

用了一段时间后发现离一开始的设定有一些变化, 比如团长不一定每顿饭都出席, 那需要有另外的人付账, 然后团长又要给付账的人团费, 还不如直接让付账人的钱直接进饭团余额. 这个功能用最初的功能也可以做到, 只是让团费的作用没那么清晰了. 用到后来, 发现其实是不需要有饭团团长这个设定的, 每顿饭谁付钱就算谁的, 反正饭团记录的是每个人的帐户余额, 团费其实就是团长的帐户余额. 需要交团费或互相转账时直接添加一顿转出人付款, 参与人只有收款人的特殊虚拟饭就可以了, 于是又加了个叫转账的虚拟店来记录转账操作, 自此充值和退款两个虚拟店就变得毫无用处了

一开始所有饭团记录都只有一页, 后来应大家的统计需求, 按参与人/付款人/店分别做了个过滤器, 这个实现的很简单, 就是对不符合过滤器的记录, 只计算不输出就行了

当饭团运作了半年多后, 单页的饭团太长, 又将默认页面改成只看最近一个月的, 另外提供了个翻页的按钮和查看全部的选项. 另一个问题是饭团成立时的团员有人转去了其他团队不再一起吃饭, 这些人最近的记录都是空的, 放着一是不好看, 二是人多了页面宽度超过很多人显示器的大小, 于是给人加了一个 “是否活跃” 的属性, 默认不显示那些不活跃的人

前不久回头去看饭团的前端, 觉得虽然算不上丑死人, 但是也没好看到哪去, 刚好就用 bootstrap 套了下, 并把各种过滤器提供表单输入的功能弄成一个查询表单. 本打算把表单直接塞导航栏, 结果发现 bootstrap 原生的 select 什么的真心太丑, 放导航栏严重破坏美感, 后来找了个 bootstrap-select 的插件来支持, 这个就很赞了

用上 bootstrap 时一嫌自己管理 css/js 麻烦, 二怕又扯上被人盗用跑流量的狗血, 直接用了国内大公司的 cdn 内容. 后来用 bootstrap-select 时, 发现国外的 cdn 太慢, 国内又没找到靠谱的, 就只能在自己项目里拷贝了一份, 结果测试环境都 OK, 在线上的 fastcgi 环境里总显示有问题, 提示找不到文件, 怒了在 nginx 里对自己的 static 文件夹又加了一条 alias 才行. 后来想这么弱智的事情不会是 django 的问题, 就去找官方文档, 在 https://docs.djangoproject.com/en/dev/howto/static-files/ 里来回看了几次才发现最后有一段关于怎么 Deployment 的, 原来还要收集一次, 也还是要加 static alias 的嘛, 只是解决了为什么之前要给 static/admin 单加一条的问题. 从这个角度来说, django 还是略复杂蛋疼, flask 就简单的多, 完全交给你自己去弄, 而且 templates 和 static 都汇集放好管理, 或许 django 是为了给每个 app 单独的分发权?

性能优化

饭团弄好后先是架在了公司我跑 Ubuntu Server 的台式机上, 直接就用 runserver 的模式跑的. 后来因为台式机偶尔会掉电, 饭团没设开机自动启动, 偶尔也会忘了开, 加上内网 IP 不一定固定, 用起来还是有点小烦, 于是迁移到我的 VPS 上

我贪便宜 15$/yr 买的 buyvm VPS, 内存只有 128M, 之前曾经写过一篇各种压榨内存的优化记录, 饭团丢上去就发现这货居然还是内存大户, 搜了下改成用 flup 以 fastcgi 的模式跑, 并把实例压到只有一个, 反正访问也不频繁, 不用处理啥并发. 用了小半年后觉得偶尔有点卡, 不过一直认为是 buyvm 的机器烂加上服务器在美国多半是网络延迟, 就没再管他

等到去年冬天的时候, 发现这慢的已经完全不成样子了, 而且有报页面超过返回大小, 将默认页面改成只看最近一个月这也是个主要原因, 当时还以为是网络的问题导致卡 (我那个 vps 走联通线路只有不到 50KB/s 的速度)

今年过完年, 在想在新团队是不是也能搭个这货玩, 把之前的数据拷贝到本地去测试了下各项功能, 发现打开首页需要接近 10 秒, 这都是本地了, 不能再赖网络, 于是加各种 Debug 信息去看到底慢到哪里. 实时的余额计算流程大概是这样

    遍历所有饭:
        获取饭的信息, 包括关联的餐厅和付款人等
        遍历所有人
            判断是否参加了本顿饭, 如果没有
                直接沿用上条记录
            如果有
                判断是否是付款人, 如果是
                    增加本顿饭总额扣掉自己那份的进余额
                如果不是
                    从余额里扣掉本顿饭钱
        添加输出信息

这里面的参团判断是用 O(n^2) 遍历实现的, 一开始就十来个人, 就算是平方复杂度也能慢到哪里去, 结果一堆 debug 信息放下去那个地方还真是特别慢. 仔细想了下估计是那个遍历所有人做判断的地方, 每次都新做了一次 SQL 查询, 好吧, 把判断用的表先遍历一次提出来做个 dict, 果然快了一些. 经过这步后耗时从 10s 降到 1s, 感觉再快也快不过跨太平洋的网络耗时, 就没再继续压榨性能

上一个改动做完没几天笨狗折腾了个 digitalocean 的 VPS 玩, 这个延迟又低速度又快, 于是有想着对那个 1s 的性能做优化, 按说这么点数据要 0.1 秒都不正常. 继续琢磨, 猜是每次取一顿饭, 都做了若干次 SQL 查询去取外键数据, 于是把人和餐厅的数据都预先提取出来构建 dict, 然后查询的时候使用就好, 这样又能快一点. 再回头去看那个多出来的辅助表, 猜是那个表每遍历一顿饭又去做了一次查询, 干脆自己把 view 里的查询都裸写, 每次页面请求都把四个表数据都 select * 出来, 然后自己去拼, 反正数据也不复杂, 这样一次页面请求只用四次 SQL 查询, 果然速度就降到了 0.01s 内. 因为数据量不大, 对内存压力也几乎没有, 而且 digitalocean 的内存有 512M, 也不用那么抠内存

问题感想

我中间曾经想要不要换 flask 重写一次, 自己管数据库, 后来找到性能瓶颈后还是留在了 django 那, 能用就懒得去动, 而且自己写个 admin 还是略麻烦

跟熊吐槽 django 的 ORM 怎么这么烂, 深度插件控的熊表示你这个一定有合适插件来帮你干这事而不是靠自己裸写 SQL 的, 不过笨狗表示有找插件和配置的时间, 我裸写的东西早搞完了. 果然笨狗还是又笨又懒, 还好目前看也还没太多篓子

对了, 饭团的 github 开源地址在: https://github.com/whusnoopy/fantuan, 欢迎 fork 帮忙优化

最后挂个 DigitalOcean 的邀请链接: https://www.digitalocean.com/?refcode=8a3c1464993e 如果你通过这个注册并付款, 我会有返点支持我继续用 DO