1. 账号生成规则先想清楚“账号”到底是什么我做了几年的账号体系接手过从零搭建的用户中心也重构过混乱不堪的老系统。每次聊到“账号生成规则”很多人的第一反应是“不就是生成一个唯一ID吗”。但真正落到系统上你会发现这个问题远比你想象的值钱账号规则直接决定了你会不会踩到并发撞ID、遍历用户、无法扩容这些坑。这篇文章想跟你把账号生成规则、哈希、密钥这三个关键词一起拉通讲透因为它们根本就是一套完整账号安全体系的三块拼图。先说清楚了这里讲的“账号”不单指用户注册的登录名还包括系统内部发放的用户ID、API访问的身份标识、邀请码、订单号这类唯一编号。不同场景对账号生成规则的诉求完全不同。比如内部账号可以是连续的、人可读的工号但对外开放的业务账号如果做成连续自增别人只要注册两个账号就能推算出平台总用户量这里面的风险可比你想象的大得多。1.1 账号生成时绕不开的三个核心问题第一是唯一性。这是底线不允许碰撞。单机数据库自增ID看起来简单但一旦上分布式、上微服务多个服务节点同时取号就会出现重复。有人会说用数据库主键唯一性约束兜底可一旦约束直接报错了对用户来说就是“注册失败”对系统来说就是一次事故。所以唯一性必须在生成层尽量解决数据库约束只是最后一道保险而不是主防线。第二是不可预测性。用户注册出来的ID如果可以被轻易枚举比如“1001、1002、1003”这样递增那么攻击者分分钟可以通过注册一个账号摸清你的业务量再配合别处的数据做撞库。不可预测性并不是要求ID完全随机而是让别人无法根据已有的ID推测出下一个ID。UUID天然满足这一点但它不满足第三个问题。第三是可使用性。这包括长度、字符集、可读性、排序性。UUID是36个字符的字符串当URL参数用很长当数据库主键还影响索引性能雪花ID是64位整数方便存储也带时间信息但如果你要把ID展示给用户看那串数字又长又难记。邀请码、优惠券这类需要人工录入或口播的场景就要求ID尽量短、字符集排除易混淆字符比如去掉0和O、1和l。这三个问题经常互相打架。不可预测性越强往往长度越长可使用性越差。所以账号生成规则没有银弹一定是根据业务场景做取舍。1.2 几套常见生成方案的实际选型对比我建议你把常见的方案全部列出来然后对照自己的业务场景打分。下面这个表是我在多个项目里总结出来的可以直接拿去用。方案唯一性不可预测性可读性/可用性适用场景数据库自增ID单机好很差非常好内部系统、非敏感业务UUID/GUID极好极好差36字符分布式环境、不暴露给用户雪花IDSnowflake极好中等一般19位数字高并发分布式系统、需排序随机短码Base62需要查重/唯一索引好很好邀请码、优惠码、短链接分段业务编码依赖规则差很好工号、订单号等人工管理场景举个例子我做过一个积分商城用户需要在线下活动中凭码兑换礼品当时就直接用了连续数字ID结果工作人员口播时还好用户手动输入也还好但后来被人发现规律后批量尝试兑换活动刚开半小时礼品就被刷走了。这就是典型的“可使用性”没做好因为完全没有随机性。后来改成Base62编码的8位随机码丢失率明显下降。如果你需要自己实现短码生成一个很稳的思路是先取一个128位随机数然后用Base62编码截取前8到10位。这里有个关键细节随机截取导致的碰撞概率并不低10位Base62的空间是62的10次方但如果你发了几百万个码生日悖论会告诉你碰撞概率已经高到不能忽略。所以必须要配合数据库唯一索引遇到冲突就重新生成或者直接查一次重再落库。别嫌多一次查询这个代价是值得的。1.3 别小看“防枚举”设计这是账号安全的隐性刚需你可能觉得账号ID不对外展示就没事但在很多系统里用户ID就是API接口的参数甚至出现在日志里、订单记录里。只要你有任何一个对外暴露查询接口攻击者就可以遍历。最典型的漏洞就是“通过用户ID查询资料”接口是 /user/profile?id1001把id改成1002、1003挨个试资料全出来了。这是OWASP排名靠前的越权访问漏洞类别根因就是你给了攻击者可预测的ID。防枚举的核心思路是让真实ID不可预测或者至少让外部ID和内部ID分离。我在实际项目中更推荐“内部主键 外部公开ID”的双层设计数据库自增主键只存内部使用所有对外接口通通使用一个随机生成的公开账号ID比如UUID短码或哈希截断值。这样内部关联查询依然高效外部又无法遍历两头都不耽误。下面会讲到哈希在这里可以帮上大忙因为你是通过哈希来生成一个不可逆的对外标识。2. 哈希账号安全的地基但哈希不是随便一算就完事哈希这个词被用烂了但很多人对它的理解停留在“MD5加密密码”这个层面。严格说哈希不是加密因为加密是可以解密的哈希是单向的你不可能从哈希结果反推出原始输入。哈希函数做的事情是任意长度的数据进来经过计算得到一个固定长度的输出而且只要输入稍微变化一点输出就会完全不同。我用厨房里的搅拌机来类比你把任何食材丢进去出来的都是一杯看不出原材料的糊糊而且搅拌过程不可逆你没法把糊糊变回完整的苹果和香蕉。但正因为哈希是单向的、确定的它也被拿来做了很多不该做的事。其中最典型的就是把用户密码直接哈希后存到数据库里。早期系统确实这么干后来人们发现不行因为两件事一是鸡生蛋的问题如果用户密码是常见的弱密码比如“123456”攻击者拿到哈希表后一查彩虹表就能还原出原文二是同一密码哈希值永远相同攻击者一眼就能看出哪些用户用了同一个密码撞库效率极高。2.1 密码为什么不能存明文也不能只做普通哈希明文存储的危害不用多说了数据库一旦泄露几百万个密码直接裸奔。普通哈希比如MD5、SHA-1的问题在于它计算速度太快攻击者可以用GPU每秒跑几百亿次拿字典和彩虹表来匹配。即使你加了盐如果是固定盐等于没加。谷歌在2019年就宣布实现了SHA-1的碰撞攻击微软也在推进彻底禁用MD5/SHA-1的使用。结论只有一个密码存储绝对不能再用快速哈希。正确的做法是使用专门为密码设计的“慢哈希”算法核心特点就是故意让计算变慢通过增加迭代次数或占用大量内存让攻击者的暴力破解成本指数级上升。业内常用的方案有PBKDF2、bcrypt、scrypt、Argon2。其中Argon2是2015年密码哈希竞赛的冠军很多新项目已经转向它但PBKDF2因为被各大语言标准库内置使用门槛更低至今仍是主流。下面我用C#和Python各写一个标准示例都是可以直接抄进项目的写法。2.2 C#加盐哈希存储的标准写法在.NET环境下推荐用Rfc2898DeriveBytes来实现PBKDF2。这里最关键的两件事一是盐必须每个用户独立长度至少16字节二是迭代次数不能太低OWASP目前建议至少60万次但也要结合服务端CPU性能来做权衡。不要把盐写死成常量也不要把盐和哈希拼成一个固定格式的字符串然后丢一边不管。下面这段代码是完整的加盐哈希生成和验证逻辑。我特意把盐生成、哈希计算、验证分成三个方法方便集成到你的用户服务里。using System.Security.Cryptography; public static class PasswordHasher { private const int SaltSize 16; // 128位盐 private const int HashSize 32; // 256位哈希 private const int Iterations 600000; public static string HashPassword(string password) { byte[] salt RandomNumberGenerator.GetBytes(SaltSize); byte[] hash Rfc2898DeriveBytes.Pbkdf2( Encoding.UTF8.GetBytes(password), salt, Iterations, HashAlgorithmName.SHA256, HashSize); // 存储格式: {迭代次数}.{盐}.{哈希}全部Base64编码 return ${Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}; } public static bool VerifyPassword(string password, string storedHash) { var parts storedHash.Split(., 3); if (parts.Length ! 3) return false; int iterations int.Parse(parts[0]); byte[] salt Convert.FromBase64String(parts[1]); byte[] expected Convert.FromBase64String(parts[2]); byte[] actual Rfc2898DeriveBytes.Pbkdf2( Encoding.UTF8.GetBytes(password), salt, iterations, HashAlgorithmName.SHA256, expected.Length); return CryptographicOperations.FixedTimeEquals(actual, expected); } }看到没我特意在验证时用了FixedTimeEquals而不是普通的字节数组比较。原因是普通比较一旦第一个字节不相等就立刻返回攻击者可以通过精确测量响应时间来判断哈希前缀从而逐步猜出完整哈希。恒定时间比较会固定遍历完所有字节这在密码验证场景里非常重要。2.3 Python哈希算法的另一种选择从hashlib到bcryptPython标准库里的hashlib提供了pbkdf2_hmac用法也很直接。但如果你问我个人意见生产环境我更推荐直接用bcrypt这个第三方库因为它在内部已经自动管理盐和迭代次数而且每次计算的代价天然就是慢的。下面两个版本你都可以参考。用标准库实现PBKDF2也是完全可行的import hashlib import secrets import hmac def hash_password(password: str, iterations: int 600000) - str: salt secrets.token_bytes(16) hash_bytes hashlib.pbkdf2_hmac( sha256, password.encode(utf-8), salt, iterations ) return f{iterations}.{salt.hex()}.{hash_bytes.hex()} def verify_password(password: str, stored: str) - bool: iterations, salt_hex, hash_hex stored.split(.) actual hashlib.pbkdf2_hmac( sha256, password.encode(utf-8), bytes.fromhex(salt_hex), int(iterations) ) expected bytes.fromhex(hash_hex) return hmac.compare_digest(actual, expected)使用bcrypt的话代码会更简洁而且你不用自己处理盐的格式库里已经内置了import bcrypt def hash_password(password: str) - str: return bcrypt.hashpw(password.encode(utf-8), bcrypt.gensalt(rounds12)).decode(utf-8) def verify_password(password: str, stored: str) - bool: return bcrypt.checkpw(password.encode(utf-8), stored.encode(utf-8))这里有个细节值得说bcrypt只接受72字节以内的密码超过部分会被静默截断。所以如果你允许很长的密码建议在上层先做一个额外的哈希处理或者直接限制密码长度不应超过72字符。我在项目里通常限制为64字符既满足bcrypt限制也避免用户填一篇文章进密码框导致各种奇怪问题。3. 密钥哈希的搭档负责加密与身份认证很多人容易把哈希和密钥搞混包括我自己刚入行时也糊涂过。简单区分哈希用来验证完整性和存储密码是不可逆的密钥用来加密数据和做身份验证是不可或缺的“门钥匙”。在账号体系里密钥至少有三个应用场景登录后的会话Token签名、API接口的请求签名、敏感字段的加密解密。这三者如果处理不好即使密码哈希再强也照样会被打穿。3.1 对称密钥和非对称密钥各管一摊对称密钥就是加密和解密使用同一个密钥比如AES就属于对称加密。它的优点是速度快适合加密大量数据。非对称密钥则是一对钥匙公钥加密私钥解密或者私钥签名公钥验签。RSA和Ed25519都属于这一类适合分发和认证但性能比对称加密慢得多。在账号系统里典型的组合拳是用非对称密钥做身份验证比如SSH登录、JWT签名用对称密钥做数据加密比如加密用户的手机号、邮箱。如果你需要给外部系统开放API又要做签名校验我会推荐用非对称密钥因为你可以把公钥发给对方私钥绝对不出本地一旦怀疑私钥泄露只需要在服务器上更换密钥对不需要让所有接入方跟着改。说到SSH密钥很多人第一次接触是在配置Git平台的部署密钥时。通过ssh-keygen生成密钥对后把公钥添加到平台以后就能免密推送代码。这里重点提醒一个坑生成的私钥文件权限过大会导致SSH客户端直接拒绝使用Linux下必须设置为600。所以建议养成习惯生成密钥后立刻执行chmod 600 ~/.ssh/id_ed25519。3.2 密钥生成与格式选择的实操心得首推Ed25519因为它短、快、安全强度高。生成命令如下ssh-keygen -t ed25519 -C deployexample.com -f ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key如果你还是要兼容老系统使用RSA那么至少密钥位数不要低于4096位openssl genrsa -out private.pem 4096 openssl rsa -in private.pem -pubout -out public.pem密钥的存储是个大学问。我从不建议把私钥直接放到环境变量里更不建议写进代码仓库。因为环境变量会被日志打印、会被进程管理器泄露代码仓库那就更别提了扫描机器人每天都在扫GitHub上暴露的密钥。我目前比较推荐的做法分三档小项目用环境变量或单独的密钥文件但文件必须从版本库排除中大型项目使用密钥管理服务云厂商的KMS或开源VaultJava项目常用Keytool构建密钥库通过别名和密码保护私钥条目。用Keytool生成自签名证书或导出公钥也是常见操作我在测试环境经常这么干keytool -genkeypair -alias mykey -keyalg RSA -keysize 2048 -validity 365 -keystore keystore.jks keytool -exportcert -alias mykey -keystore keystore.jks -rfc -file public.crt这类工具把密钥封装成库文件比直接在代码目录丢一个pem要安全得多至少访问库文件还需要库密码。3.3 密钥轮换不轮换就等于把大门钥匙一把用到老在真正负责一个系统的安全运维时我才意识到密钥轮换不是可选项而是必选项。长期不变更的密钥就意味着一旦泄露你根本不知道攻击者已经用它多久了而且员工离职、接入方调整、硬件退役都需要让对应密钥失效。业界给出的经验值是对称密钥至少每年换一次非对称签名密钥建议90天到180天换一次如果遇到泄露事件必须立刻吊销并重新签发。轮换最怕的就是一刀切比如你把私钥换了但旧的会话Token还带着旧签名用户瞬间被踢下线。成熟的方案是“双密钥交替期”新旧密钥同时并行一段时间新签发的请求用新密钥旧请求在宽限期内仍然能被旧公钥验证。等老的Token自然过期后再彻底把旧密钥下线。这个思路和蓝绿发布是一个道理。如果你做API网关更建议直接把密钥版本号放到Token的Header里验签时根据版本号选择对应的公钥这样轮换可以完全平滑用户无感知。4. 账号生成规则、哈希、密钥如何配合成一套完整流程光有规则没有流程等于把零件堆在地上没组装。真实场景里用户注册、登录、调用API这三个环节会把前面说的三块内容全部串起来。我建议你照着下面这个注册流程框架来搭已经是我在多个项目里验证过的结构。4.1 注册流程一个从输入到落库的完整链路第一步接收用户名。这里要做两层校验格式合法性是否包含非法字符、唯一性检查用户名或邮箱是否已注册。第二步生成账号ID。我上面说过对外公开ID用随机短码或哈希截断值对内主键可以用自增或雪花ID。第三步密码哈希。调用前面写的HashPassword方法得到带盐的哈希字符串。这时候要注意绝对不要在日志里把完整密码哈希打出来打日志时只打用户ID或用户名。第四步为账号初始化密钥。你可以为用户生成一对API密钥或者生成一个访问Token的签名密钥标识。下面这个表是我在项目中常用的“账号体系各环节职责速查表”每次做评审都拿出来对照环节使用的技术关键目标用户ID生成雪花ID或随机短码唯一、不可预测密码存储PBKDF2 / bcrypt / Argon2抗彩虹表、抗暴力破解会话TokenHMAC签名对称密钥防篡改、可验证API签名RSA/Ed25519身份认证、防抵赖敏感字段AES加密数据保密4.2 登录与后续请求Hash和密钥各司其职登录的时候服务端拿到用户提交的密码调用VerifyPassword方法从库里取出存储的哈希字符串重新计算PBKDF2然后用恒定时间比较判断是否一致。验证通过后生成一个签名Token。这时候就用到密钥了如果你的Token是JWT格式一般用HMAC-SHA256加上一个密钥来签名用户后续每次请求都带着这个Token网关验签成功才放行。我在实际项目里最常被问到的另一个问题是为什么要单独搞API密钥而不是直接用用户名密码做接口鉴权原因很简单密码是长期有效的凭证走网络传输的风险太大API密钥则是短期的、可撤销的、允许指定权限范围的独立凭据。你完全可以在用户创建密钥时给它限定只读权限或仅限某个IP网段这样即使某个密钥泄露影响面也被限制住了。4.3 哈希表这种数据结构为什么在账号查询里无处不在既然聊到哈希还有一个热词绕不开就是哈希表。哈希表是“键值存储”的一种数据结构它通过计算键的哈希值来定位存储位置实现理论上的O(1)查询。放在账号场景里就是当你想根据用户名快速找到用户对象时如果再走数据库全表扫描会非常慢但如果内存里维护了一个字典用户名, 用户对象一次哈希定位就能命中。实际上数据库索引也大量用到哈希思想。MySQL等关系型数据库虽然默认用B树索引但也提供哈希索引选项Redis的字典底层就是哈希表。你把用户的session信息放进Redis键就是sessionID值就是用户对象查的时候直接O(1)取走靠的也是哈希映射。所以我一直跟团队说理解哈希表不要停在刷算法题要在真实项目里看到它缓存、数据库索引、路由表、消息队列的消息去重到处都有它。5. 常见问题与排查技巧实录最后这部分全是实战踩坑记录。我按账号生成、密码哈希、密钥管理三块来整理每一条背后都是真实事故换来的经验。5.1 账号生成环节的典型坑并发下生成重复ID测试环境还测不出来。解决思路是给数据库加唯一索引兜底同时生成规则里加入足够的随机位。注意随机源一定用密码学安全随机数生成器Python里是secretsC#里是RandomNumberGenerator不要用普通随机数那东西是可以被预测的否则“不可预测性”就是空话。短码冲突后直接给用户报错。我见过一个项目邀请码生成冲突后直接提示“系统繁忙”用户体验很差。正确做法是捕获冲突异常后自动重新生成最多重试三到五次如果还冲突说明随机空间太小需要扩大码长。记住要让用户看到的是平滑重试而不是崩溃日志。字符集没有排除易混淆字符。邀请码、卡密发放场景一定要去掉容易看错的字符比如大写字母I、小写字母l、数字1大写O和数字0。这个细节能直接减少客服咨询量。5.2 密码哈希的常见误解用了SHA-256加盐就以为安全了。这是个高频误区。SHA-256是快速哈希每秒能算几亿次攻击者拿到你的数据库后可以高速离线爆破。必须用慢哈希慢是设计目标不是缺陷。如果你在选型我用一句话总结新项目直接上Argon2id或者bcrypt至少别再用SHA-512裸哈希。盐固定、盐太短、盐可预测。这些都是减分项。盐必须是每个用户唯一的随机值建议至少128位。有些老系统用用户名当盐攻击者知道用户名就能提前计算彩虹表等于白加盐。迭代次数常年不变。摩尔定律告诉我们硬件性能会不断提升你的迭代次数应该随着服务器的性能调整。比如每年把PBKDF2的迭代次数往上调一档同时把老用户的哈希在登录成功时自动升级到新参数。这个叫“哈希迁移”很多团队漏掉这个机制几年后密码存储强度就变得过时了。5.3 密钥管理最容易翻车的几个瞬间私钥进了Git仓库。这个是真的会出事的。GitHub等平台有专门的扫描机器人只要检测到私钥特征就会全平台通报泄露。如果你发现密钥已经提交过仓库唯一正确的做法就是立刻生成新密钥对、吊销旧密钥不要试图“删掉提交记录”来补救因为已经有了副本的仓库就没有秘密了。权限没设对导致SSH登录失败。这是非常经典的困扰。私钥文件权限如果是0644OpenSSH会直接拒绝使用报bad permissions。简单一句话私钥文件的权限必须是600公钥文件可以是644。环境变量里的密钥被日志打印出来。很多框架会dump环境变量到调试日志一旦密钥在里面日志一泄露所有环境的安全都归零。我的习惯是日志系统里必须对密钥、密码、Token这类字段做脱敏并且定期扫描日志库用正则匹配可能的密钥格式发现就报警。再说一个我常被问到的细节到底要不要把密钥放在代码仓库的.env.example里可以放占位符但真实的密钥值绝对不能放。env.example的作用是告诉别人需要配置哪些变量里面填的值一律用your_key_here这种假值这样团队协作时既方便又不危险。最后分享一点我的真实体会做账号体系这么多年我最大的感触是安全不是一个孤立的点而是一条链账号生成规则、哈希、密钥都是这条链上的环节。你可以在某个环节做得非常极致但只要有一个环节拉胯整条链就断了。比如密码哈希做得再强如果对外公开的账号ID可以被枚举攻击者照样能批量撞库比如密钥管理得再严格如果密码用了快速哈希一次数据库泄露就会变成灾难。从设计角度说我建议把这三块独立成模块分别做单测和维护但设计和评审时永远放在一起考量。账号生成规则决定的是“你是谁”密码哈希决定的是“拿什么证明你是你”密钥决定的是“你后续能干什么”。只有把这三件事都打到同一个水平你的账号系统才能在真实世界里站得住脚。最后分享一个小技巧你可以在注册接口里对“用户名是否存在”的响应做统一化处理让攻击者无法通过提示文字判断某个账号是否已注册。这个细节很小但配合上不可预测的账号ID、加盐哈希密码和规范的密钥体系你的系统会比你想象中坚硬很多。希望这些经验能帮你在做账号体系时少走几个我当年绕过的弯路。
企业数字化 ERP 产品动态
相关推荐
MikroORM 5.x 常见问题(FAQ)实战指南:从 Schema 同步到类型推断陷阱的完整解答 后端 【免费下载链接】mikro-orm TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases. 项目地址: https://gitcode.com/gh_mir… · 2026/9/25 4:21:44
蒙特雷律所加入Andersen Global:近岸外包下法律国际化新路径 老实说,这类“某地某律所加入某某全球组织”的消息,在新闻流里很容易被一眼略过。但如果你在专业服务这个圈子里泡得够久,就会知道这类消息的分量,它往往比那些动辄“xx律所并购xx律所”的新闻更能说明行业风向。这周末我刷到这条… · 2026/9/25 4:21:44
CUPP社工字典生成指南:从信息收集到口令爆破的实战应用 1. CUPP解决什么问题:从公开信息到口令猜测的链路1.1 为什么“社工字典”比通用字典更高效做安全评估的老手都有体会:通用的弱口令字典,比如rockyou、常用top1000,在对抗真实系统时效果往往很不稳定。原因很简单——通用字典覆盖的… · 2026/9/25 4:21:44
边缘AI芯片选型:从场景约束反推技术方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:35
高频变压器三明治绕法:原理、实操与EMI/效率优化 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:35
网盘搜索引擎原理与实战:找资源不再靠运气 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:35
Django与协同过滤实战:动漫推荐系统从算法到部署 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:35
STM32开源项目交付指南:代码、原理图与仿真全解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:35
无驱动IP打印实战:ZPL指令与Python直连Zebra打印机 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:55:29
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37