小米摄像头4 4K版接入Homekit分享

搬新家之后,买了个小米摄像头做门口的监控。最近闲下来了,刚好绿联也做了HomeAssistant的一些适配,就花了点时间,把摄像头接入到了Homekit中,虽然有点小瑕疵,但已经很够用了,有一些小坑点可以分享一下,帮助大家避坑。

摄像头接入Homekit流程

这里推荐另外一个博主做的分享:

https://www.geekxw.top/3870/

他有很详细的步骤,教大家如何接入Homekit,我这边也是按着他这个视频中的步骤来接入的,只是中间有一些小细节,我想补充一下。

go2rtc获取小米device列表提示401 Unauthorized

这个实际上是触发了小米的登录频控,触发后只要等30分钟,再重新登录就没有这个问题了。

go2rtc转h264

这里要搞成两个视频流才行,不然的话转不了264。这里推荐一个Mac的软件,SmartRTSP,这个软件是内购收费的,但不要紧。这里我们只用他免费的部分,只要这个能够连的上就代表我的RTSP流是h264的,Homekit就肯定能连上,这个App都连不上的话,Homekit就肯定连不上。

Scrypted接入后的摄像头只能预览不能录像

这个是因为需要Homekit家庭里,同时具备家庭中枢、带有运动侦测的摄像头以及支持h264码流的摄像头,才能激活Homekit安全摄像(HSV)。激活后才能录像,这个功能需要Scrypted中的运动检测插件才行。

Scrypted现在已经放弃了PAM-diff的支持了,转而让大家都使用OpenCV插件,这个插件会依赖Python环境。使用Scrypted官方提供的容器是没装pip的,所以一运行这个插件就会失败。

解决方法是要自己在容器里,通过apt-get update更新一下镜像源,再通过apt install -y python3-pip,这样子才能装到python的依赖。装好依赖之后,OpenCV插件就能工作了,这个时候再将Scrypted作为网关加入到Homekit,再连接摄像头,才能激活HSV。

Homekit即使有家庭中枢,但还是无法预览。

这个问题也困扰了我2天,在家里一直能通过局域网连上Homekit里的摄像头,出外了就看不到了。后面试验了一下,终于发现是跟ShadowRocket存在冲突,开了代理就无法访问摄像头,不开的话,无论是Wifi还是流量就都能访问了。(Apple TV、Mac、Watch都是一样的问题,手机不挂代理就能正常看摄像头,开了就看不了,一直转圈圈。)

这个我暂时没研究出什么好的解决办法,我试过将地址添加到直连或者开启ShadowRocket的兼容模式,但是开了流量依旧无法访问,不知道为啥,欢迎有解决该问题的朋友指导一下。

【日常碎碎念】AI取代前端?

临睡前看到Airing发的一篇关于读者去信,询问AI时代下就业相关的博文,我自己也有一些不吐不快的想法想要写一下,真正的碎碎念。

毫无疑问,在一个无限的尺度上,AI最终是会取代前端的工作,但老实说我认为AI最近5年内都无法拿走前端的岗位。特别是组内做了半年的AI 工程化,更加笃定了我的想法。现阶段的AI只是网红FOMO产生的泡沫,跟取代前端岗位半毛钱关系都没有。他只是一些幻觉,让大家以为会被取代。但AI需要工程化恰恰说明了AI现阶段的局限性,AI无法处理现实世界的烂事。

AI与现实之间的交互,一切的这些烂事,最终都需要人类来fix,来充当胶水层。所以不用焦虑,因为这些烂事,AI根本没办法处理,这些不是单纯的技术问题,更多是无尽的行政问题,AI并不能去加速这一块事情。之所以要编写各种skill、rules甚至是MCP就是因为现实中的草台班子,导致根本就没有统一的操作界面,能够让AI去自动化。或许有人会说,我们局部自动化是不是就能被取代了,我认为并不是的。就跟自动挡的汽车一样,部分自动化了,只是减轻了我们的负担,但还是需要有人来把控方向盘。从22年加入公司以来,我就从来没感觉做过前端,天天被owner意识PUA,其实我们的工作中,大部分时间都是在解决现实世界的烂事,开无尽的会,对齐无尽的方案,戴着镣铐跳舞,编码只是其中微不足道的一小部分而已,甚至编码都只是我们晚上加加班就能冲刺出来的东西。

在我看来,AI只能帮我办我们把这些编码处理掉,但始终需要人来跟人之间交互。D2C?别说笑话了,你就看有没有设计认可你的D2C设计吧,根本就没人想用那一套自动生成的UI。代码生成UI只是工程师陷入技术牛角尖的又一力作而已。

我的观点始终是,AI只是让前端不再稀缺,并不能取代前端。AI需要系统的解决软件工程的问题,才能真正的说,取代这个岗位。什么时候可以把产品AI化、项目经理AI化、测试AI化、设计AI化,到那个时候,万事俱备的时候,前端才能被取代。因为到那一刻,就不需要再有人类来处理现实的烂事了。

另外,我其实也有一些产品角度的见解。很多前端真的是陷入的技术困境中:就跟“手拿铁锤的人,看什么都像钉子。”说的一样,AI当然是很全能,但他并不是银弹。一个产品的成功,并不是单单一个软件而已,他是一个系统工程,需要全体合力才有机会成功的项目。很多场景下,根本就不需要用AI,别TM什么都幻想着用AI,人明明能解决问题的,非得跟那破AI沾沾边,把Loading改成thinking,就幻想自己变得多高科技一样。老板之前开了一次全体大会,解释了为啥不买AI Token,有一点我是很认可的。就是产品如果已经是进入稳定期了,用不用AI其实都没区别,因为真的没那么多的问题需要被解决。

最后就是职业规划的事情。我觉得踏踏实实干好本职工作就好了,老板想把你炒掉的更大原因,可能只是想招新人来免税而已,跟AI半毛钱关系没有,毕竟AI也不见得就能比员工便宜。而且这么多大公司,现金流充沛,也不见得现金流充沛就不裁人。真要努努力的话,多做一些向上管理,比卷技术来的有用多了。

补充一些小的观察。上半年我去参加了几场关于OpenClaw相关的路演,给我的感觉是各种投机分子的表演大会,论吹牛逼,我绝对比不过台上的演讲者,每个人都有一套基于OpenClaw的皮肤预设,一个比一个牛逼,概念越来越玄乎,最典的是这个“略·术·果推演法”,来来去去就是一个概念使劲换皮。一问实际解决了什么问题,一个屁都放不出来。

【日常碎碎念】效率提升工具

三月份都在忙工作,没啥动力做酒评分享,所以来跟风做一篇自己的效率提升工具集合的分享文。

软件

终端类

Austin 一直支持跨平台同步的历史记录工具,支持直接搜索历史命令。跨项目开发的神器,经常在pnpm和yarn之间来回切,有些命令只记得一小部分,就可以用这个工具的模糊搜索找回来。

Warp 增强版zsh,支持tmux分屏,原生支持鼠标选中编辑位置。(不过最近更新的AI功能太多了,很容易用成AI模式,差评。)

效率软件

Raycast 替代Spotlight的软件,Swift原生,支持插件,AI plan很好用。我现在把Shift + Z绑定成AI模式的唤醒键,搭配 Command + Space效率提升非常多,有一些不懂的就丢给AI分析,还能把一些固定的prompt整理成AI Command重复使用。

Sequel Ace Mac 数据库管理软件,免费,Swift原生,支持跳板机连接,有中文。不过公司不让用跳板机连接数据库,现在只能降级回手工输入SQL了。

Magnet 窗口管理快捷工具,Swift原生,只要记住及格固定的快捷键,就可以单纯依赖键盘,管理桌面窗口了,搭配Raycast实用,效率提升200%。使用Raycast中的VSCode project manager插件,快速打开cursor,然后一下Control + Option + Enter直接全屏编辑,立刻就能进入某个项目的开发状态了。

Snipaste 桌面截图工具。这个截图工具最好用的是多桌面的截图标签,可以把截图后贴在桌面最顶层,结合鼠标滚轮可以放大缩小,整理项目关系,数据比对的时候很好用。开通Pro版本后的二维码识别更是效率神器,不用再掏出手机做识别,截图就能把二维码解析出来了。

微信输入法 这个输入法最大的卖点,在我看来就是支持多平台的粘贴板同步,我在安卓测试机上安装上微信输入法,结合电脑的微信输入法,以及Mac跟iPhone间的粘贴板同步,我就可以做到三个设备间无缝粘贴的能力了,在做项目测试,需要频繁粘贴链接或者数据的时候,绝对是一大利器。

Cursor 我最近一年来用的最多的AI IDE了,做程序员这行的肯定都懂这玩意儿的厉害之处,搭配最新的Opus 4.6模型,需求随便做,根本无所畏惧,代码分析、Review、实现功能模块、Bug分析、图片分析样样精通,离开了AI我都不会编程了。这种自动挡的体验,感受过就回不去了。

Whistle 网页流量代理工具。这里Whistle本质上就是一个Proxy,但它最大的用途不是代理,而是劫持流量做请求分析,对项目开发而言非常好用,结合浏览器上的切换插件(比如:whstile-firefox等),在做开发的时候,可以直接在调试页面切换到所需的代理规则,为开发和验收作出了不少的效率提升贡献。

实用工具

FontMin 字体文件裁剪器,可以将ttf裁剪成很小的文件,原生,免费。这个工具主要是前端开发,做优化的时候能用到。在做一些活动项目的时候,如果设计要求使用艺术字体,就可以使用这个工具,直接把项目中会使用的艺术字体裁剪出来,将font文件压缩成一个专属版本,可以有效减包,一般一个1-200KB的ttf,可以裁剪到只有1-2KB。(因为裁剪后,整个字体包就只剩下项目中,会用到的字体了。)

PerfDog 可以评估真机的内存占用。对机器做性能测试比较有用,可以分析机器是否存在内存泄漏的问题。但现在需要按量计费,有点昂贵,如果不是公司报销,我个人是用不起的了。

MonitorControl 多显示器亮度同步工具。如果同时使用多个屏幕的时候,可以安装这个软件,使得所有的分屏都映射到相同标准下的亮度量程中,避免出现手动控制的一边亮一边暗的情况。

DarkModeBuddy 根据Mac亮度自动切换浅色/深色模式的工具。这个工具在办公室午休的时候,绝对是一大利器。因为腾讯中午午休的时候是会关灯的,如果中午需要处理事务的时候,难免会被浅色模式的皮肤闪瞎眼睛。这个工具此时就能派上用场,他会使用mac的亮度计,识别环境的亮度,达到特定阈值后就会切换至浅色/深色模式。避免在暗光环境下被闪瞎眼,在亮光场景下,被深色皮肤限制了文字的可读性。

硬件

MX-Vertical 罗技的垂直鼠标。买这个鼠标纯粹是为了预防鼠标手,之前一直在用Master 3S,手腕经常会酸疼,换成垂直鼠标后,确实缓解了不少,现在已经很少会出现手腕酸疼的情况了。罗技这款鼠标的垂直度数有限,所以习惯一下就能从之前的Master切换过来,算是在健康和易用之间,取到了一个甜蜜点了。缺点也是有的,Vertical的快捷键没有Master 3这么多,也没有了水平滚轮(做Cocos动画的时候,K帧很好用),也没有了无极滚轮,需要一点时间来调整一下工作流。还有就是手汗大的话,鼠标上的硅胶依旧容易融化,我的Vertical上已经是千疮百孔了。

Macbook Pro M1Pro 32+1T 苹果电脑。这个是公司发的电脑,本来给我配属的是一台intel芯片的老款电脑(触屏touchbar也是很难用,经常过热),但是老款电脑的散热设计有很大的缺陷,经常过热,会导致芯片过热降频。后面切到Cocos开发后,这个弊端直接就影响到我的开发效率了,于是怒写三份邮件给老板们申请换电脑。换到M1Pro之后,让本地原来需要15分钟构建时间的项目,缩短到只需要3分钟,效率直接提升5倍。原来还要加班才能做得完的工作,现在直接提前做完,效率拉满。这是我第一次具像化的认识到Pro的威力所在,当他真的能让你的工作快一倍以上的时候,你就应该花钱买它,真的非常好用。

倍立正 护腰坐垫。一开始我是很抵触这个概念的,我总是感觉这就是一个类似背背佳那样子的智商税产品。后面无意跟朋友开了个玩笑,他就当作生日礼物送给我了,非常感谢我的朋友。刚开始用的时候,腰背确实会很酸,用着用着就会感觉不酸了。直到有一段时间,腰背真的不酸之后,我才意识到之前的酸痛来源,就是来自于自己的错误坐姿导致的,这个小塑料板是真的可以把我们的腰椎调整到一个合适的姿态上,保护好自己的健康的。

思考

写这篇文章,主要是3月真的没啥好写的了。工作日基本都是在做基建或者学习AI做游戏,周末有时间我就出去游泳、滑雪、逛街吃饭喝酒,真的不再有周末自己在家捣鼓代码的想法。我总是感觉AI出来之后,很多之前的想法已经变得不再迫切,很多人通过AI就能够大幅提升效率和自己的能力边界,我们再去古法编程显得有点逆势而为,没有必要再花大量的时间去开发那解决小痛点的产品。我反而会想着说,出去逛一下,学习更多知识之后,看看别的场景下有没有办法做一些代码的结合,帮助更多场景提效,或许是程序员更好的出路。

Cocos 2.X/2.4如何通过命令行指定自定义引擎路径

最近转型成了无情的流水线工程师,天天就是帮组里的同事编写构建流水线。

需求

目前有个需求就是希望可以在流水线构建的过程中,指定自定义路径的引擎。

困境

翻遍了整个Cocos文档和论坛,在Google上找了2-3个月都没找到2.X的项目,究竟要怎么样才能在命令行构建的时候指定自定义引擎路径。3.X官方倒是给命令行新增了参数,用于指定自定义引擎的位置。

3.x可以通过–engine参数来手动指定引擎路径 https://docs.cocos.com/creator/3.8/manual/zh/editor/publish/publish-in-command-line.html

解决

今天在翻看定制化引擎的时候,偶然发现了这个技巧,超级无敌简单。就是在项目的根目录下创建一个local文件夹,然后在里面设置一个settings.json,就可以轻松指定项目所使用的自定义引擎路径了。

我这里还写了个nodejs的脚本来辅助创建,分享给大家使用:

保存为customEnginePath.js

const fs = require('fs');
const path = require('path');

// Parse command line arguments
const args = process.argv.slice(2);
let customEnginePath = null;

// Parse --path argument
for (let i = 0; i < args.length; i++) {
    if (args[i] === '--path' && i + 1 < args.length) {
        customEnginePath = args[i + 1];
        break;
    }
}

// Get current working directory absolute path
const currentDir = process.cwd();

if (!customEnginePath) {
  console.error('❌ Custom engine path required!');
}

// Build engine path - use custom path if provided, otherwise use default
const enginePath = path.resolve(customEnginePath);

// settings.json file path
const settingsPath = path.join(currentDir, 'local', 'settings.json');

try {
    // Ensure local directory exists
    const localDir = path.dirname(settingsPath);
    if (!fs.existsSync(localDir)) {
        fs.mkdirSync(localDir, { recursive: true });
        console.log('📁 Created local directory');
    }
    
    // Create complete settings configuration object
    const settings = {
        "use-global-engine-setting": false,
        "use-default-js-engine": false,
        "js-engine-path": enginePath,
        "use-default-cpp-engine": true,
        "cpp-engine-path": ""
    };
    
    // Write configuration to file with beautiful formatting
    const settingsContent = JSON.stringify(settings, null, 2);
    fs.writeFileSync(settingsPath, settingsContent, 'utf8');
    
    console.log('✅ Successfully generated/updated local/settings.json');
    console.log(`📁 js-engine-path set to: ${enginePath}`);

    console.log('🎯 Using custom engine path specified via --path argument');
    
    // Show whether file was newly created
    if (!fs.existsSync(settingsPath)) {
        console.log('🆕 Created new settings.json file');
    } else {
        console.log('🔄 Updated existing settings.json file');
    }
    
} catch (error) {
    console.error('❌ Operation failed:', error.message);
    console.log('💡 Please check file permissions or disk space');
} 

使用方法:

在项目根目录下运行这个脚本

node customEnginePath.js --path=<Your Custom Engine Path>

使用CocosCreator构建的时候,就会自动使用你指定的引擎进行构建。

CocosCreator内看不到这个设置是正常的,构建的时候是会使用自定义引擎进行构建的。

如果运行完脚本,但是Creator内构建的时候会提示“The first argument muse be of type string or an instance of buffer xxxx”的,就是Creator的bug,要手动进自定义引擎的面板,勾选一下全局JS引擎,然后再取消勾选全局JS引擎,这样子Creator内构建才不会报错。

rem与px之间的转换

背景

日常开发的过程中,经常会遇到设计稿仅提供px值的情况,仅能保证IOS设备下的显示正确,无法保证在多端设备中的元素比例正常。

问题

因此在日常开发中,我都会选择使用rem代替px。但是这个转换过程心智负担太重了,经常忘记要如何转换,所以我整理成了两段代码,平时转换的时候只要直接用就好了,不需要反复学习记忆其转换规则。

px转rem

<你要转换的px数> / parseFloat(getComputedStyle(document.documentElement).fontSize)

rem转px

parseFloat(getComputedStyle(document.documentElement).fontSize) * <你要转换的rem数>

补充

我比较建议你把这段代码存到自己的模板代码里,方便自己使用,不然每次都打开我这个博客就太麻烦了。

Cocos 2.4.7 Building Assets卡住问题

关键词:构建资源、Building Assets、资源刷新、Prefab

背景

下午开发的时候想要构建一下游戏产物,但是一直会卡在资源打包上,白思不得其解,搜了一下官方论坛也没找到相关的问题的解决方法。

定位

reset大法一个个回滚到指定的commit上跑一下构建,直到找到会卡住的那个commit再做分析

// 命令行构建
/Applications/CocosCreator/Creator/2.4.7/CocosCreator.app/Contents/MacOS/CocosCreator --path /Users/7gugu/Documents/gitlab/genshinImpact --build
  1. 切换到项目的根目录
  2. 修改CocosCreator的路径
  3. 修改–path路径(该路径是用于指定在哪个位置创建一个build文件夹并且输出构建产物)
  4. 在命令行中运行代码,看构建是否会卡住

问题

最后定位到是因为有一个图片的meta文件不是被直接删除,而是被覆写,怀疑是这里覆写的时候Cocos没监听到文件变化导致Prefab没更新到,最后导致构建卡住。

解决

彻底删除这个被覆写的meta文件,重新添加图片文件即可修复问题。

延伸

翻查了一下git status,显示的是有一个文件被R052了,后面搜了一下stackoverflow才知道原来这个含义是覆写了52%。

Untitled

https://stackoverflow.com/questions/53056942/git-diff-name-status-what-does-r100-mean

如何解决Merge主分支代码导致本地代码被删除?

背景

之前需求发布的时候,把代码发布到了预发布分支上,但是产品突然发现一个体验问题阻塞了发布,需要先revert掉,等改动完成后再继续发布。

现状

改动完成后,需要同步一下主分支上的代码,在本地解决冲突后再合入主分支。此时会发现Merged后将会把本地代码给删掉。

复盘定位

如图所示,其实问题的根源就是:主分支上的revert没有被撤销,导致合入开发分支的时候本地代码都被revert掉了。

解决办法

简而言之就是,撤销之前的主分支上的revert就可以让主分支上的代码合入到开发分支上又不会把本地代码删掉。相当于告诉git,主分支上这批代码不用被删掉了。具体步骤如下:

  1. 从最新的主分支上创建一个临时分支
  2. 切换到这个临时分支
  3. 在临时分支上revert(撤销)掉之前的revert提交
  4. 将临时分支合入到开发分支上,同步最新主分支代码
  5. 解决问题

如何判断一个JS方法的兼容性?

背景

由于业务的原因,我们的H5页面必须兼容一些比较老的webview版本,因此在开发中难免会遇到不支持的JS语法,因此在这里总结了一个切实可行的方法来提高自己确认接口兼容性的效率。

方法

通过caniuse.com查询方法兼容性

例子

假如需要使用Proxy方法来统一代理所有的接口,但是我不确定系统兼容性,怎么办?

1.访问caniuse.com

2.查询对应的方法名

3.查询iOS的兼容性

4.查询Android的兼容性

5.如果查询到的最低支持版本在需要的版本内则可以使用,另外在使用前最好在方法外套一层try...catch避免出现不支持的意外情况

如何在Apple Watch上添加Loading动画?

背景

去年11月的时候,买了人生第一支Apple Watch,因为内心涌动的软工魂,我又开始琢磨开发一个属于自己的iOS App。之后顺理成章的推出了自己首个WatchApp,《wTodo》一个用于在Apple Watch上管理微软待办事项的工具应用。

无原生组件怎么办?

由于App需要经常性联网,因此不免需要引入Loading态来告知用户当前的运行状态。不巧的是watchOS中提供的组件是阉割后的版本(主要是为用户的续航考虑,如果直接套用手机端那一套组件,watch的续航会受不了的),导致watch端并没有原生的组件能够实现Loading动画,这也让我苦恼了很久。后面下班摸鱼的时候,偶然在stackoverflow上看到了解决办法。

帧动画

如标题所述,在watchOS上只能通过“帧动画”来曲线救国,实现Loading动画。在这里给大家推荐一个来自Stackoverflow高赞中推荐的动画库。

https://github.com/mikeswanson/JBWatchActivityIndicator

该动画库提供了两种帧动画样式,第一种是类原生的Loading帧动画,如果个人比较偏爱原生动画的话,可以使用这个,直接点开“common image”就可以找到不同尺寸,不同帧率的帧动画了;第二种则是通过作者提供的工具,自定义动画的样式,如果想要自定义则需要clone项目到本地,然后通过Xcode打开项目运行到Simulator中即可,调整完参数后,点击生成即可获得符合自己要求的帧动画素材。

如何使用?

这里用类原生动画举例子:

1.从Common Image中选择自己需要的尺寸和帧率

这里我选择Normal Size 15帧的帧动画图片

2. 将帧序列图片拖到WatchKit App的Assets.xcassets文件夹中

3.在StoryBoard中引入Image组件

4.绑定Image组件到Controller上

5.设置Image组件参数

使用startAnimationWithImages后就会开始播放帧序列动画了,因此可以用来看作是动画开始函数。

6.停止播放帧序列动画

总结

至此只要在需要使用Loading动画的地方配置对应的Image组件即可给页面加上Loading态了。实现方法非常简单易用,就是国内文档太少了,只能去社区里找略麻烦了一点。如果希望自己去寻找更多内容的话,可以使用“loading indicator”去搜索,感觉会比Loading Animation说的更准确一些。

PS:

不知不觉就到8月底了,8月的碎碎念我还没来得及写,上周优化了wTodo加入了LoadingIndicator,这周就把加载动画的技术文章尝试沉淀下来了。如果下周有时间的话,我再花时间把碎碎念给肝出来吧。工作日加班真的消耗完我写代码的精力了,到了周末感觉都不想再碰电脑了,只想出门逛一下散散心。(真的不是我咕咕咕啦!)

[SQL] 如何获取昨日每个小时的记录数量?

方法

利用左连接来实现逐小时查询(终于把数据库原理学到的知识用上了😂)

select a.click_date,ifnull(b.total,0) as total from (
SELECT date_format(date_sub(curdate(), interval 1 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 2 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 3 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 4 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 5 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 7 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 8 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 9 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 10 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 11 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 12 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 13 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 14 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 15 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 16 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 17 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 18 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 19 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 20 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 21 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 22 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 23 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 24 HOUR),'%Y-%m-%d-%H') as click_date
) a 
left join (
select date_format(createTime,'%Y-%m-%d-%H') as datetime, count(*) as total from `order` where  `status` = 2 group by date_format(createTime,'%Y-%m-%d-%H')
) b 
on a.click_date = b.datetime

infull用于字段的补0,在无数据的时候可以补上0。

dateSub用于减去时间戳的时间

a主要是用来生成逐小时的时间字段

b主要是查询数据

on是用来执行left join的条件