【案例】github actions不同yml复用job,减少费用和重复配置

使用github actions已经有些年头了,虽然并不一定会用到什么高深的用法,但是每天都有稳定的使用量。自从知道github actions使用运行时长限制的,比如免费用户是2000分钟,付费用户可以达到3000分钟,企业用户可以达到50000分钟。 背景 同一个账号或者组织的所有项目共享这个运行分钟数的额度,当时为了避免问题,还特意新建了几个组织分散项目。可是随着项目的使用频率提升,终究这2000分钟的额度不够用了,于是对账号进行了升级。因为一个组织下有多个账号,升级组织时,旗下的每个账号都会升级,每个账号4美元/月,此时账号多了反而花费更高,所以无奈只有把其它用处不大的账号从组织里面移除了。 当时新建组织以及小号的原因是希望查看github workflow时方便看到各个触发来源使用量!!! 为了降低升级费用,这个办法不适用了,还是得回归到使用一个账号触发了,这样又无法区分各个触发来源使用量了。 案例实战 既然无法通知触发账号来区分各个触发来源使用量,那就相同的功能使用不同的yml文件名来区分吧。 也就原来用账号A运行workflow.yml,账号B运行workflow.yml,改成账号A运行workflow.yml,账号A运行workflow_B.yml,其中workflow_B.yml和workflow.yml内容一模一样 如果只有一两个workflow_B.yml还好,如果有N个,全部复制粘贴改名,后期维护也会比较麻烦,能否提取出公共的的部分呢,比如job_common.yml,然后新建workflow_A.yml,workflow_B.yml… workflow_N.yml,后期有功能变化只用更改job_common.yml即可。 重点是在yml里面声明workflow_call表示它可以被重用 name: workflow_common on: workflow_call: # 声明为可重用 # 声明期望接收的 inputs inputs: input_a: description: 'input_a description' required: true default: 'input_a' type: string input_b: description: 'input_b description' required: true default: 'input_b' type: string jobs: job_common: runs-on: ubuntu-24.04 …其它配置此处略过 那workflow_A.yml怎么调用这个workflow_common.yml呢 Read more…

接入大模型,前端怎么调用流式输出接口

传统情况下我们使用axios调用接口,将response转换成json后就可以直接使用了。 但是使用大模型的时候,因为以下几种原因,传统模式已经不再试用了。 对应响应内容还有多少服务器端自己也不清楚 响应内容可能很多,用户不可能长期等待服务端的响应,所以需要有多少数据就传输多少数据。 这种边生成数据边给客户端输出的这种形式成为流式输出。 实测发现使用axios即使将相应设置成responseType: 'stream'也没什么效果,使用原生的fetch可以轻松应对。 async function streamDeepSeekResponse(prompt) { const response = await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', body: JSON.stringify({ token: 'token', }) }); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let result = ''; Read more…

大文件的文本内容替换方案——流式替换

常规的文本替换很简单,直接replace替换即可,但是遇到大文件就麻烦了,直接获取全部的文本内容不现实,太占用内存甚至可能导致程序崩溃,这个时候就需要流式读取文件内容,也就是一次读取文件的一部分,跟蚂蚁搬家似的,那样每次占用内容的空间就不大了,程序能一直正常处理。 流式处理的话需要将每次读取一定长度(必须需要大于待替换的文本长度)的数据块,可以取一个固定值,比如1M,足够了。 流式处理可能存在的问题 采用分块处理会带来一个问题,如果取出的数据的开头或结尾刚好把一个待替换的文本切开了怎么,如图所示 第三个“待替换文本”就刚好一部分在“数据块1”,另一部分在“数据块2”,在处理“数据块1”的文本替换的时候就只能找到两个符合条件的文本,也就只能替换掉两个,在处理“数据块2(假设和数据1类似)”的文本替换的时候也只能找到并替换到两个符合条件的文本,但实际上就是数据1和数据2整体而言,少替换了一个“待替换文本”——“待替换文本3”。 解决问题思路 这种场景我们应该怎么处理呢? 在处理“数据块1”的时候,我们可以确定的是先把前面两个好处理的替换文本给替换掉,然后余下不够分的部分和后面的“数据块2”合并在一起成为“新的数据块2”,然后跟处理“数据块1”的方法一样,对“新的数据块2”进行替换操作,此时“待替换文本3”就能被找到并替换了,如果也有余下的部分,我们也将它合并到下面的“新的数据块3”,以此类推即可。 解决问题实现 具体的实现方式如下 const fs = require('fs'); const { Transform } = require('stream'); class ReplaceTransform extends Transform { constructor(searchRegex, replaceString, options) { super(options); this.searchRegex = searchRegex; this.replaceString = replaceString; this.tailPiece = ''; this.replaceTailPieceLength = Math.max(…[…searchRegex.toString().matchAll(/\((?!\?)/g)].map(match => match.index)) || 0; } _transform(chunk, encoding, callback) Read more…

研究学习华为IAP严谨的支付过程

大家都知道支付跟钱有关,严谨是必要的,但是没有想到可以做到这么严谨。 首先要记住一个宗旨——1.不要相信客户端,2.支付数据要解码验签 因为用户客户端是支付发起的源头,它就存在被篡改的可能性,而支付的终点是自家服务器,中间一定会经过网络传输过程,网络传输也存在被挟持篡改的可能性,服务器作为自家设备,相对而已就可信得多了,所以网络应用可以做的比单机应用安全的多了。 支付流程 我们先看下华为IAP的流程是什么样子的 如下业务流程对于单机应用同样适用。在单机应用中,应用服务器和应用客户端的交互放在应用客户端完成,应用服务器和IAP服务器交互的部分可不处理。 在整个支付过程中涉及华为的那部分安全性我们无需关心这个由华为方保证,我们只需要保证用户支付后我们客户端和我们服务器之间的支付安全性,也就是收到支付结果PurchaseData后怎么处理,也就是购买结果确认。 购买结果确认 这个还要分是交由客户端接收支付结果还是服务端接收支付结果PurchaseData。 方式一:通过客户端接收购买结果 首先看如果是客户端接收支付结果的场景 用户购买成功时,IAP Kit返回包含订单信息的PurchaseData数据。 应用客户端向应用服务器上报PurchaseData数据。 这样做的目的为了做日志等支付留痕使用。 应用服务器需对PurchaseData.jwsPurchaseOrder进行解码验签,成功后可得到PurchaseOrderPayload的JSON字符串。 PurchaseData数据是加密的,解码验签后才能拿到里面的原始数据 方式二:通过服务器接收购买结果 尽管客户端后收到购买结果,我们为了安全性可不予理会,改用服务器接收购买结果。 开发者可以接入服务端关键事件通知,在用户购买成功时,IAP服务器将发送订单关键事件通知。 用户完成支付后我们的服务器就就可以也收到一份购买结果 应用服务器可从NotificationPayload.NotificationMetaData中解析出purchaseToken和purchaseOrderId信息,并通过服务端订单状态查询接口向IAP服务器查询最新的订单信息,进一步确认订单的准确性。 因为IAP服务器会发送多次关键事件通知,应用服务器需要查询自行查询是否已经发放过购买权益了,避免重复发货,同时也要将这一结果告知给IAP服务器,这样它就不会重发同类事件通知了。 IAP服务器返回订单信息jwsPurchaseOrder。 应用服务器需对jwsPurchaseOrder进行解码验签,成功后可得到PurchaseOrderPayload的JSON字符串。 华为发送给应用服务器的数据也是加密的,同样需要解码验签后才能拿到里面的原始数据 权益发放 待我们确认用户完成了支付,我们就应该发放购买的结果了 检查当前PurchaseOrderPayload是否已发放权益,未发放则发放相关权益,并记录对应的订单信息(PurchaseOrderPayload),用于后续检查权益发放状态。 应用客户端向应用服务器查询订单的发货状态。 这说明非单机应用的话,发货过程应该尽量交由服务器完成。 应用服务器返回对应的发货状态以及订单信息(PurchaseOrderPayload)。 发货成功后应用客户端向IAP Kit发送finishPurchase请求,以此通知IAP服务器更新商品的发货状态,完成购买流程。 尽管应用服务器发货成功后已经告知IAP服务器了,但是应用客户端的IAP Kit并不知道,它需要由我们的应用客户端告知发货状态 应用成功执行此步骤后,IAP服务器会将相应商品标记为已发货状态。对于消耗型商品,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。对于非消耗型商品,用户购买后永久拥有,无法再次购买该商品。 充分了解了这一支付流程,后面我们就好进行实际的功能开发了,这个下次再讲。

markdown+NextJS搭建个人博客

如果你平时不喜欢在排版上花时间,不在乎花里胡哨的样式,觉得markdown编辑文件效果就够用?你还想基于markdown搭建一个博客?那NextJS就可以满足你的需求了 NextJS可以使用React,引用antdesion组件库把容易前端界面的起来,然后也能很轻松的生成静态资源,如果还需要几个后端接口的话,NextJS也不在话下,前后端都自己实现,网站就差不多了,这些相对静态的部分就完成了。 动态的部分呢,那就是网站的内容,也就是自己写的这些博文了,可以用markdown写文章,网站的url就直接根据markdown文件的目录结构,简直不要太惬意。 平时写完markdown直接上传到网站(或者git提交+github pages生成),新的博文就生成了,这一切能这么轻松搞定源于NextJS支持动态路由,创建这样一个文件src/[..slug]/page.js。 我们访问站点任何未被明确配置page.js的页面室,这个文件就都能感知到了。 代码如下 import React from 'react'; import Nav from '@/components/Nav'; import AppContentBox from '@/components/common/AppContentBox'; import AppContent from '@/components/common/AppContent'; import ServerBottom from '@/components/server/ServerBottom'; import { getFileData } from '@/lib/posts-md'; import { generateCommonMetaData } from '@/app/siteConfig'; export default async function SlugPage({ params }) { const data = Read more…

可拖拽div实现细节示例与解析

最近有个后台的项目需要做一个翻译功能,有一个翻译框,后面要求这个翻译框可以随意拖动,于是想起之前在客户端上也做过一个类似的,本来打算直接超过来交差的,结果发现这里面的一些细节有点记不清了,导致抄完之后跟客户端的不太一样,有bug(向下拖动的时候会“跳走”),认真看代码后发现原来是有个细节漏了,这次特意写出来总结下。 为了简单省事,我就直接用项目里面的部分代码了(Vue版本) <div class="translate_area" v-if="showTranslate" @dragstart="handleDragStart" @dragend="handleDragEnd" :style="translateBoxStyle"> <div class="translate_box"> <div class="translate_box_header" :style="{paddingBottom: paddingBottom}"> <div class="close_icon" @click="onTranslateClose"></div> </div> <div class="translate_box_body"> 这里是主体部分 </div> </div> </div> { data() { showMask: false, translateBoxStyle: { left: '200px', top: '200px', }, paddingBottom: 0, translateBoxStartX: 0, translateBoxStartY: 0, }, methods: { updateDragStyle() { }, handleDragStart(e) { Read more…

用IntersectionObserver实现深层级dom瀑布流加载

瀑布流加载现在已经不是什么新鲜技术了,比如pinterest,这类图片网站使用瀑布流加载不仅能提升首屏渲染效果,还能看起来特别酷。 一般是多个图片卡片,根据布局选择在有空位的地方插入先加的图片卡片,本质上将就是在一个div容器内,有N个同类型的子div在里面按需加载,可将其细分为平铺dom瀑布流加载,但是我现在遇到一个场景是dom元素很多,他们一般每个div只有一个子div,但是它们的层级嵌套很深,类似这种 当每一个回合就会至少嵌套一层,层级嵌套过深之后,浏览器的渲染就会出现卡顿,现在需要针对这个问题进行优化。 现在唯一知道的是每一个回合都在一个特殊标识的div容器,可以找到页面上所有的回合,根据这一个共性我们还是可以借鉴瀑布流的思想来进行瀑布流加载,我们将它认为是深层级dom瀑布流加载吧 我们可以先找到最近的回合N,然后找到里面的回合N-1,将它的内容替换成占位符(可以是一句话或者一个空白的div),同时记录占位符N和被它替换掉的原始数据的映射关系,页面仅展示内容N + 占位符N-1,当占位符进入可视区域后,根据占位符N找到原始数据,然后先将原始数据的按同样的操作替换成内容N-1 + 占位符N-2,最后再将它们替换掉占位符N-1,后面以此类推。 解析dom可以用node-html-parser,监听占位符是否在可视区域可以用IntersectionObserver。

【转载】RQrun运动记录也能导出了

本文转载自运动健康转换工具的《RQrun运动记录导出(需模拟登录)》 目前没有官方途径将数据导出,目前运动健康转换工具可以通过非官方模拟登录拿到更多数据,基本都有GPS、心率、配速、步频、步幅、功率和分段信息,已经很丰富了。 RQrun app内效果 转换效果 总结 RQrun的运动记录虽然没有官方途径导出,典型的只需进不许出 目前可以通过模拟登录能“导出”运动数据,数据细节足够丰富

利用nodejs whistle实现HarmonyOS NEXT微信38版本

基于B站的视频鸿蒙NEXT 微信38最新解决无法下载教程进行了简化 1.安装Nodejs 可直接参考官网安装 Windows下载 https://nodejs.org/dist/v20.18.0/node-v20.18.0-x64.msi Mac/Linux用nvm下载 # installs nvm (Node Version Manager) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash # download and install Node.js (you may need to restart the terminal) nvm install 20 # verifies the right Node.js version is in the environment node -v # should print `v20.18.0` # Read more…

Github actions如何让某个step自动重试

最近有个github action项目中的某个步骤step需要执行下载文件,但是近期开始出现下载超时的情况,并且还有点频繁。自己除了找服务器的原因以外,还想通过优化github actions来尽量避免下载超时的问题。 在网上搜了下,github actions官方没有这样的功能,但是好在第三方的actions里面有,那就省事多了。 有个叫nick-fields/retry@v3的,我们可以直接利用它来实现重试功能,参数配置也很简单。 截取里面几个有用的配置 Inputs: timeout_minutes Required Minutes to wait before attempt times out. Must only specify either minutes or seconds timeout_seconds Required Seconds to wait before attempt times out. Must only specify either minutes or seconds max_attempts Required Number of attempts to make before failing the Read more…