news 2026/10/10 10:47:09

基于Hono和JWT的Cloudflare Workers API认证实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Hono和JWT的Cloudflare Workers API认证实战

做接口开发的人,基本都绕不开身份认证这道坎。最近我在折腾一个跑在Cloudflare Workers上的内部工具,需要给一组API加上登录校验,想了半天,最后用了Hono框架配合JWT来实现,流程走通之后发现这套组合比想象中顺手,而且踩过的坑也很有代表性,干脆整理成一篇文章分享出来。写这篇东西的时候,我会尽量把原理、代码、以及实际跑的时候遇到的那些问题都讲透,你在自己项目里复现的时候能少走点弯路。

整套方案的核心就三件事:在边缘节点上架起Hono应用,用它处理路由;在登录接口里签发JWT;在受保护的接口前面挂一个中间件,把Token里携带的用户信息解析出来塞进请求上下文。这个模式在传统Node服务里很常见,但放到Cloudflare Workers这个环境里,有几个细节会不一样,比如运行时的API限制、包的选择、密钥的管理方式,这些恰恰是最容易翻车的地方。

1. 为什么选Cloudflare Workers加Hono来做JWT认证

先说选型。如果你只是要做一个轻量API服务,认证逻辑又不复杂,其实用什么框架差别不大。但一旦你想把它部署到边缘节点上,让请求在离用户最近的地方完成校验,那选择范围就窄了很多。Workers的运行环境不是完整Node.js,它跑的是V8引擎加一部分Web标准API,所以像Node里那些依赖crypto模块的库,直接用经常会报错。

Hono这个框架我很早就注意到了,它最大的特点就是轻,整个框架本身就是为边缘运行时设计的,可以用标准的Request和Response对象,在Workers、Deno、Bun这些环境里都能跑。配合官方的hono/jwt中间件,或者直接用底层的jose库,写JWT认证非常自然,不用像在Express里那样去手动兼容Node和Worker两套环境。

这一次我实际用的组合是:

  • Cloudflare Workers作为部署运行环境
  • Hono作为Web框架
  • jose库负责JWT的签名与验签
  • wrangler做本地开发和线上发布

选jose而不是自己手写签名逻辑,是因为JWT虽然看起来就是三段字符串拼起来,但签名和校验的细节太多,标准库更新也频繁,没必要把时间浪费在这些底層实现上。jose是纯Web API实现,在Workers里兼容性很好,Cloudflare官方文档里也经常拿它举例。

2. 先理清楚JWT到底是怎么工作的,以及它和Cookie Session的区别

JWT是JSON Web Token的缩写,说直白一点,就是服务端给客户端发一张带签名的“身份卡”。这张卡由Header、Payload、Signature三部分组成,每部分都做Base64Url编码,然后用点号拼起来。Header声明了签名算法,Payload放业务数据,Signature则是对前两部分的签名。

三个部分分别长这样:

// Header { "alg": "HS256", "typ": "JWT" } // Payload { "sub": "user_123456", "name": "演示用户", "role": "admin", "iat": 1710000000, "exp": 1710086400 } // Signature HMACSHA256( base64url(Header) + "." + base64url(Payload), secret )

整个Token看起来就是:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMzQ1NiIsIm5hbWUiOiLnp43npL7nlKh_1.签名部分

关键的“从token中拿到认证信息”,其实就是把中间那一段Payload取出来,Base64Url解码成JSON,然后读取里面的用户ID、角色这些字段。但这里有个前提:你必须先验证签名是合法的,否则任何人都能自己伪造一段Payload塞给你,你的接口就会被任意伪造身份的人直接调用。所以先验签,后取数据,这个顺序绝对不能反。

那为什么不直接用Cookie Session呢?因为Session方案需要在服务端存一份会话数据,要么放内存,要么放到Redis或者Cloudflare KV。在Workers这种边缘计算场景里,每个请求可能被路由到不同的节点,内存Session基本没法用,引入KV又增加了架构复杂度。JWT是无状态的,服务端只需要持有密钥,不需要存储任何会话记录,请求来了验一下签名就完事。对于API服务来说非常合适。

当然,JWT也有它的短板。最大的问题是Token一旦签发,在有效期内没法主动作废。如果用户改了密码或者账号被冻结,已经签出去的Token在exp之前仍然能用。这在权限要求非常严格的后台系统里可能是个痛点,解决办法要么把Token有效期设短一点,要么引入刷新机制,要么干脆在关键操作里再加一层权限校验。

3. Workers环境和本地开发环境的搭建

在写认证逻辑之前,先把基础环境跑通。我用的是wrangler作为命令行工具,项目结构很简单:

my-worker/ ├── src/ │ ├── index.ts // Hono入口 │ ├── auth.ts // 认证相关逻辑 │ └── middleware.ts // JWT校验中间件 ├── wrangler.toml // Workers配置文件 ├── package.json └── tsconfig.json

初始化项目用下面两条命令:

npm create cloudflare@latest my-worker -- --type=typescript cd my-worker npm install hono jose

这里有个细节值得说一下,jose和hono/jwt的关系。Hono从某个版本开始内置了jwt中间件,直接import { jwt } from 'hono/jwt'就能用。但实际经验告诉我,在生产项目里最好还是直接依赖jose,自己写一个适配中间件。原因后面第四章我会详细讲,简单的说就是Hono内置中间件的返回结构在不同版本里有变化,而且它能做的事情比较死板,不如手写灵活。

wrangler.toml里我会先占一个密钥的坑位,本地开发时可以直接写在[vars]里,线上则用wrangler secret put单独设置:

name = "my-worker" main = "src/index.ts" compatibility_date = "2024-09-01" [vars] JWT_SECRET = "dev-secret-local-only"

注意这个JWT_SECRET本地开发可以放配置文件里图方便,但线上部署绝对不能这么干,Cloudflare提供了Secrets机制,就是专门给这类敏感配置用的。线上操作命令:

wrangler secret put JWT_SECRET

这条命令会提示你输入密钥内容,输入完之后会自动加密存储。之后Worker代码里再引用env.JWT_SECRET就能读到真实值,而wrangler.toml里写的那一版会被覆盖掉。

4. 核心代码实现:登录签发Token、中间件校验、解析认证信息

这一步是整个项目的重头戏。我按照实际开发的顺序来走:先做登录接口,签发Token;再做校验中间件,把认证信息塞进请求上下文;最后在受保护的路由里读取。

4.1 签发Token:写一个登录接口

由于这是一个演示性质的项目,我就用最简单的用户校验方式:假设请求体里传了用户名和密码,我先和一个写死的字典比对,通过之后签发Token。企业项目里这一步会换成查数据库或者调一个用户服务,但签发环节的代码是一样的。

// src/auth.ts import { SignJWT } from "jose"; const secret = new TextEncoder().encode(env.JWT_SECRET); export async function generateToken(user: { id: string; name: string; role: string; }) { return await new SignJWT({ name: user.name, role: user.role, }) .setProtectedHeader({ alg: "HS256", typ: "JWT" }) .setSubject(user.id) .setIssuedAt() .setExpirationTime("2h") .sign(secret); }

这段代码里有几个点要解释一下。

第一,secret为什么要用TextEncoder转成Uint8Array?因为jose的API要求签名密钥要么是Uint8Array,要么是KeyLike类型。直接把字符串传进去会报TypeError,这是很多人第一次用jose会踩的坑。

第二,setProtectedHeader里的alg要和签名算法匹配,这里用的HS256就是对称签名,签发和校验用的是同一个Secret。

第三,setExpirationTime("2h")这里的过期时间单位是相对时间,jose内部会基于iat自动计算出exp,最终Payload里存的是Unix时间戳(秒)。很多人在写JWT的时候容易把时间弄成毫秒,但标准里规定exp和iat都是秒。你可以自己打印Token到jwt.io上去验证,如果时间不对,校验那边就会报expired。

登录接口的长这样:

// src/index.ts import { Hono } from "hono"; import { generateToken } from "./auth"; type Bindings = { JWT_SECRET: string; }; const app = new Hono<{ Bindings: Bindings }>(); app.post("/api/auth/login", async (c) => { const { username, password } = await c.req.json(); // 演示用的写死用户,实际项目查数据库 if (username === "admin" && password === "admin123") { const token = await generateToken( { id: "user_123456", name: "演示用户", role: "admin" }, c.env.JWT_SECRET ); return c.json({ token, tokenType: "Bearer", expiresIn: "2h" }); } return c.json({ message: "用户名或密码错误" }, 401); });

这个接口的逻辑不复杂,接收JSON,比对用户名密码,通过之后就返回Token。

4.2 手写一个JWT校验中间件

Hono的中间件本质就是一个函数,接收Context和Next,处理完业务后调用await next()往下走。我写了一个authMiddleware,任务有三个:从请求头里拿Token、用jwtVerify验签、把Payload里的用户信息挂到c.set("user")上。

// src/middleware.ts import { jwtVerify } from "jose"; import type { MiddlewareHandler } from "hono"; type JwtPayload = { sub: string; name: string; role: string; iat: number; exp: number; }; export const authMiddleware: MiddlewareHandler = async (c, next) => { try { const header = c.req.header("Authorization"); if (!header || !header.startsWith("Bearer ")) { return c.json({ code: 401, message: "缺少认证Token" }, 401); } const token = header.slice(7); // 去掉 "Bearer " const secret = new TextEncoder().encode(c.env.JWT_SECRET); const { payload } = await jwtVerify(token, secret, { algorithms: ["HS256"], }); // 说明验签成功,把用户信息放到上下文里 c.set("user", { userId: payload.sub, name: payload.name, role: payload.role, }); await next(); } catch (error) { // 这里能捕获到的错误包括:Token过期、签名不匹配、格式不对 return c.json({ code: 401, message: "Token无效或已过期" }, 401); } };

使用方式很简单,在需要保护的接口上挂这个中间件:

// src/index.ts import { authMiddleware } from "./middleware"; const app = new Hono<{ Bindings: Bindings }>(); app.post("/api/auth/login", async (c) => { /* 上面写过 */ }); // 受保护的路由 app.get("/api/user/profile", authMiddleware, async (c) => { const user = c.get("user"); return c.json({ userId: user.userId, name: user.name, role: user.role, }); });

这里有个地方值得展开:为什么中间件里自己手写验签逻辑,而不是直接用Hono的jwt中间件?我刚做的时候其实也是直接用import { jwt } from 'hono/jwt',写起来确实简单,三行代码搞定。但我需要的不只是验证Token有没有问题,我还要把Token里面的用户信息取出来用。在旧版Hono里,jwt中间件会把验证结果挂到c.get("jwtPayload"),但不同版本这个字段名不稳定,而且Payload的类型需要自己二次断言。在多框架维护的团队里,这种隐式的挂载方式很容易造成上下文类型混乱。

手写中间件的好处是:第一,jwtVerify返回的payload是受类型控制的,我能明确知道里面有哪些字段;第二,挂在c.set("user")上,后续Handler的c.get("user")能直接拿到类型提示,写起来舒服很多;第三,不依赖框架版本更新带来的行为变化。

4.3 从Token中拿到认证信息,到底拿的是什么

这一节专门回应标题里的后半句。很多刚开始用JWT的同学会有一个错误认知,以为Token里面加密了用户信息,要用某种“解密”手段才能读出来。实际上JWT的Payload只是做了Base64Url编码,并没有加密,任何拿到Token的人搜索一下就能看到里面的字段。所以这里所谓的“从token中拿到认证信息”,指的是通过验签之后,把Payload里携带的声明(Claims)拿出来使用。

实际操作中,我会在中间件里把关键信息抽取到一个结构化的对象里,而不是直接把原始Payload传给后面的业务代码。原因有这样几个:

一是干净。下游接口只关心业务需要的字段,比如userId、name、role,没必要让它看到iat、exp这些技术字段。

二是隔离。如果将来改了Token结构,比如把sub从用户ID改成别的标识,只需要改中间件这一层,下游代码完全不受影响。

三是安全。Payload里如果有role或者权限相关字段,除了验签之外,可能还要做二次校验。把抽取逻辑收敛到一个地方,以后加逻辑方便。

在某些架构里,团队甚至会把用户角色、权限点全部放进Token,业务代码直接从c.get("user")里取。但我个人的建议是:Token里只放必要的、低频变更的信息,比如用户ID、姓名、角色。如果一个Token塞了几十K的权限数据,先把签出的Token体积搞大,网络传输成本上去了;其次权限一变还需要重新签发Token,很麻烦。真正精细的权限判断,应该在业务层查库或者调权限服务完成。

5. 踩坑实录:几个真正会让你卡半天的细节问题

这一节我打算把实际操作中遇到过的坑集中列出来。Internet上关于JWT的教程不少,但很多都是复制粘贴出来的,不实际跑一遍根本发现不了问题。以下这些是我这次折腾过程中实际踩过的,给你的排查过程节省一点时间。

5.1 Token过期了但报错信息是“Token无效”

这是我第一次跑通流程时最容易困惑的场景。假设用户拿着过期Token请求接口,jwtVerify抛出的错误是JWTExpired,类型是Error的子类,但如果你在外层只是笼统地catch(error)然后返回401,前端根本分不清Token到底是过期了还是被篡改了。

调试阶段我建议把错误类型打出来看:

catch (error) { if (error?.code === "ERR_JWT_EXPIRED") { return c.json({ code: 401, message: "Token已过期" }, 401); } if (error?.code === "ERR_JWS_SIGNATURE_MISMATCH") { return c.json({ code: 401, message: "签名无效" }, 401); } return c.json({ code: 401, message: "Token无效" }, 401); }

jose的每个业务错误都带一个code字段,用这个字段区分原因,排查问题会快很多。前端也能根据不同的提示做不同处理,比如过期就跳登录页,签名无效就提示数据异常。

5.2 密钥类型不匹配,HS256对应的Secret容易写错

前面提到jose的签名密钥需要Uint8Array。还有另一个细节,jwtVerify在指定algorithms: ["HS256"]的同时,如果密钥类型不匹配也会报错。

我第一次实现时犯过这样一个错:本地用new TextEncoder().encode(env.JWT_SECRET)编码的字符串作为Secret,Token签发出来没问题;但到了生产环境,因为线上Secret是通过wrangler secret put设置的,内容前面不小心多了一个空格,结果所有请求都验签失败。别笑,这种问题在真实环境里真的很常见。排查方法是在代码里临时加一句日志,打出一段Hash对比一下两边密钥是否一致。

5.3 Hono的c.env在本地和线上行为不一样

Hono的Context.env字段在Worker运行时里会自动注入环境变量,但如果你用的是app.get("/path", authMiddleware, handler)这种写法,authMiddleware里拿c.env没问题。可一旦中间件逻辑被抽成一个独立的函数,不在Hono的请求链路里,那就拿不到env了。

我之前踩过的一个坑是:把签发Token的工具函数从auth.ts导出,然后在登录接口里调用。函数签名写的是(user, env),但是调用的时候忘了把c.env传进去,结果代码里env是undefined,TextEncoder直接编码失败。这种问题排查起来要花不少时间,因为这个错误提示不是很明确,会一直导向“库有问题”的方向。

建议是,凡是需要用到环境变量的函数,要么显式传入env参数,要么用Hono的Context上下文绑定,不要依赖全局变量。

5.4Authorization头和Bearer前缀的大小写

HTTP Header的字段名是大小写不敏感的,但Bearer前缀的大小写是敏感的。标准写法是Bearer xxxxx,B是大写。如果客户端传了bearer xxxxx,你直接用header.slice(7)会得到一个错误的结果,因为它比标准多了6个字符。

更稳的做法是不用slice,而是用正则或者split:

// 用 split 更健壮 const parts = header.split(" "); if (parts.length !== 2 || parts[0] !== "Bearer") { return c.json({ code: 401, message: "认证格式错误" }, 401); } const token = parts[1];

这个小改动看着不起眼,但能省掉很多来自客户端大小写不规范的问题。

5.5wrangler dev热更新和jose的兼容性

如果你在用wrangler dev做本地调试,改了代码之后有时候会遇到模块解析错误,尤其是在配合jose库的时候。这个不是代码逻辑的问题,而是Workers本地环境的模块缓存没有完全刷新。我遇到过的现象是:第一次启动正常,改完auth.ts之后保存,控制台报Could not resolve "jose",重启wrangler dev又恢复正常。

解决方案很简单:遇到这种诡异问题先重启wrangler dev,不要花太多时间在代码里找原因。如果你用的是npm版本较新的项目,可以顺便升级一下wrangler到最新版,新版的模块解析逻辑会好很多。

6. 对比一下HS256、RS256和ES256,到底该用哪个

在做JWT签名算法选型的时候,我看到很多人直接无脑用HS256,但实际在不同场景下,选择应该不一样。我这次用的是HS256,因为它最简单,适合个人项目和内部工具,但如果你做的是面向大量用户的公共服务,建议直接上非对称算法。

三者的核心区别用一张表格就能说清楚:

算法签名密钥验证密钥优点缺点
HS256同一个Secret同一个Secret实现简单,计算快无法公开验证,密钥分发麻烦
RS256私钥公钥公钥可分发给多个服务验证计算较慢,Key长度大
ES256私钥公钥签名短,计算快,安全性高实现稍复杂,需要密钥管理

在Workers环境里,如果你有多个服务需要验证同一份Token,用RS256或者ES256会很方便:签发方持有私钥,其他服务只要配置了公钥就能验签,不用把同一个Secret分享来分享去。这在微服务架构里非常常见。

但是非对称算法也有代价,首先是密钥管理复杂了很多,要生成密钥对、定期轮换私钥、把公钥安全地下发到各个服务;其次jose里使用私钥做签名需要用到importJWK或者importPKCS8,代码会复杂一个等级。

个人经验供参考:如果你只是在Workers上加一个简单的API认证,用户量不大,HS256完全够用;如果你有多个后端服务需要验证同一个JWT,用ES256更合适,签出来的Token短,计算还快;如果你们公司已经有一套基于RS256的登录体系,那就保持RS256别乱换。

我在这个项目里最终选了HS256,因为整个服务只有Worker一个入口,不存在密钥分发的需求。如果以后要把认证开放给更多子服务,我会迁移到ES256。

7. 生产环境的JWT认证还应该注意什么

把能跑通的Demo变成能上生产的系统,中间还差不少东西。我的建议是至少把下面这几件事做了。

7.1 密钥轮换机制

JWT用的是对称密钥,意味着持有Secret的任何人都能签发合法Token。一旦Secret泄露,攻击者可以直接伪造管理员Token。所以在生产环境,你需要有密钥轮换的预案。

一种常见的做法是支持多密钥验证:在中间件里维护一个密钥列表,新密钥负责签发,旧密钥在缓冲期内依然可以验证。等到旧密钥对应的Token都过期了,再彻底移除旧密钥。

用jose实现多密钥验证其实很简单,jwtVerify的第二个参数可以传一个Uint8Array数组,它会按顺序尝试,直到有一个成功:

const secrets = [newSecret, oldSecret].map((s) => new TextEncoder().encode(s)); const { payload } = await jwtVerify(token, secrets, { algorithms: ["HS256"], });

这样即使换了密钥,已经签发的Token在有效期内依然能正常使用,不会出现用户被迫重新登录的情况。

7.2 Token有效期设计

Token有效期设多久,取决于你的业务场景。内部工具我一般设2小时,客户端应用通常7天,涉及支付的系统会更短。

但小技巧在于:短的AccessToken配合refresh_token刷新机制,是兼顾安全和体验的好方案。RefreshToken的有效期可以设7天到30天,存储在HttpOnly的Cookie里;AccessToken有效期只有十几分钟,每次请求都带。这样即使AccessToken被截获了,攻击者也只有很短的窗口期可以使用。

我这次做的内部工具考虑到复杂性,没有做刷新机制,但还是把AccessToken的有效期设成了2小时,2小时之后需要重新登录。这个折中对内部工具来说够用了。

7.3 HTTPS是底线,别再做明文传输了

抛开技术选型不谈,不管你的Token签发得多安全,如果客户端和服务端之间走的是明文HTTP,这些Token在传输过程中可以被任何人截获。Cloudflare Workers默认都会提供HTTPS,这一层已经由平台保障了。但如果你在本地调试或者有自己的域名配置,一定要确认线上ACME证书配置正常,别让HTTP到HTTPS的重定向出了问题。

7.4 限流和防暴力破解

接口暴露在公网,难免会遇到有人拿脚本不断尝试登录。虽然JWT本身不解决这个问题,但你在设计登录API的时候,最好在前面挂一个限流规则。Cloudflare平台自带WAF和Rate Limiting能力,可以在仪表盘里配置,也可以直接用Workers Rules。如果没有这些,也可以在应用层做简单的IP频率计数,存到KV里。

我在这个项目里用的是平台自带的Rate Limiting规则,只对/api/auth/login设置了一个比较严格的限制:同一个IP在一分钟内最多尝试5次登录。实测下来噪音少了很多。

8. 一次完整的调用链路演示和日志观察思路

把前面的代码串起来之后,整个调用链路其实非常清晰,我从用户角度梳理一遍,方便你对照自己的实现。

  1. 用户调用POST /api/auth/login,传用户名密码
  2. 登录逻辑校验通过,调用generateToken,返回JWT
  3. 用户把JWT放在请求头里:Authorization: Bearer eyJhbGciOi...
  4. 请求进入受保护路由,先过authMiddleware
  5. 中间件用jwtVerify验签,从Payload里提取sub、name、role
  6. 中间件挂载user对象,await next()放行
  7. 业务Handler读取c.get("user"),返回真实数据

调试的时候我习惯在authMiddleware里加一行日志,看下Payload到底解析出了什么。不过注意,日志里不要打印完整Token,只打印关键字段,避免敏感信息泄漏。

用wrangler tail可以实时查看线上日志:

wrangler tail my-worker

它会输出Worker捕获到的所有日志,包括中间件里的console.log。这个在排查线上问题的时候非常好用,尤其适合验证线上Secret是否和本地一致。

9. 实际部署时的一些建议和心得体会

最后说一点我在这个项目上折腾完之后的整体感受。Cloudflare Workers上跑Hono,配合JWT做认证,这套组合真的很适合中小型API服务,尤其是那些希望部署简单、没有服务器运维负担的团队。你把认证逻辑写清楚之后,整个项目结构非常干净:入口路由、认证中间件、业务Handler各司其职,没有多余的状态管理。

如果要扩展的话,后面可以做的方向还有不少。比如把登录记录和Token黑名单放到Cloudflare KV或者D1里,实现Token提前作废;或者叠加Cloudflare Access,在网关层再做一道保护;再或者把登录从单用户体系改造成OAuth2授权码模式,接入第三方账号体系。这些都是在现有基础上的扩展,不需要推翻重来。

我个人在实际操作中最大的体会是,JWT这个东西,原理并不复杂,但真正落地的时候,细节非常多。密钥管理要小心,时间戳别搞错单位,算法选型要考虑业务场景,Payload只放必要信息,保存好你的私钥和Secret。只要这些基本点都踩对了,剩下的事情都会很顺。

如果你正准备在Workers上做自己的API认证,按这篇文章里的思路先把最小链路跑通,然后再慢慢补安全和运维层面的东西,应该会让你少踩不少坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 10:45:46

S/4HANA邮件发送监控:ABAP Cloud与SAPconnect排错实战

前阵子帮一家制造企业排查S/4HANA邮件问题&#xff0c;说起来挺荒诞&#xff1a;ABAP Cloud模式开发的项目导入导出都正常&#xff0c;系统日志也明明白白写着“邮件已调用发送”&#xff0c;但业务那边就是收不到发票邮件。业务追着IT问&#xff0c;IT去查代码&#xff0c;代码…

作者头像 李华
网站建设 2026/10/10 10:43:35

从删库到跑路:Redis 这 8 个命令,按下 FLUSHALL 我人都麻了

文章目录客户端连接Redis安装的相关程序介绍客户端程序redis-cliRedis常用命令infoselectKEYSbgsaveDBSIZEFLUSHDBFLUSHALLSHUTDOWN客户端连接Redis # 语法 # redis-cli -h IP/HOSTNAME -p PORT -a PASSWORD&#xff08;redis-cli -h host -p port -a password&#xff09;&am…

作者头像 李华
网站建设 2026/10/10 10:43:29

Windows快捷键失效真相:三层诊断与动态验证法

1. 为什么“快捷键大全”类文章正在集体失效你点开过多少篇标题带“Windows终极快捷键大全”“99%人不知道的Win10隐藏快捷键”的文章&#xff1f;我数过——过去三年&#xff0c;光是收藏夹里就存了17个不同版本。它们排版精美、分类清晰、动辄列上百条组合键&#xff0c;可真…

作者头像 李华
网站建设 2026/10/10 10:41:43

Python调用C++动态库:ctypes与pybind11从入门到实战

1. 项目概述与核心思路拆解1.1 为什么非要把C打包成动态库给Python用我经常被问到一个问题&#xff1a;Python写得好好的&#xff0c;为什么非要绕一圈把C代码编译成动态库&#xff1f;直接pip install一个库不香吗&#xff1f;先说一个我遇到的真实案例。前段时间做一个量化回…

作者头像 李华
网站建设 2026/10/10 10:41:01

CadSoftTools CAD VCL v10.2在Delphi 12.3的安装与实战指南

简介&#xff1a;面向 Delphi 开发者的专业 CAD 控件包 CadSoftTools CAD VCL v10.2 Enterprise&#xff0c;覆盖 Delphi 10 至 Delphi 12&#xff08;含 Athens&#xff09;平台&#xff0c;为 Windows 环境下需要集成 CAD 能力的应用程序提供可直接使用的解决方案。控件集基于…

作者头像 李华