基于Spring Boot+Vue的一卡通消费系统:统一刷卡扫码刷脸流水,实现幂等扣款
简介一套基于SpringBoot与Vue前后端分离架构的一卡通消费系统源码包覆盖人脸、刷码、实体卡三种消费方式面向需要完成毕业设计或课程设计的计算机专业学生也适合初学者学习主流前后端分离项目落地。资源共748个文件体积仅1.67MB其中包含380个Java后端源码、92个Vue前端组件、82个JavaScript逻辑文件及大量SVG图标、XML配置文件等目录结构清晰便于按模块阅读与二次开发。目前已有90人学习下载。压缩包内除源码外还附带环境配置说明、启动脚本如run.bat、build.bat及多环境配置文件下载后按文档调整本地环境即可直接运行。项目难度适中经过审定可帮助读者理解一卡通消费场景下的接口设计、前后端联调、权限与订单处理思路是一份高性价比的毕设参考。1. 一卡通消费系统三个终端一张账单核心不是人脸而是流水某园区做一卡通项目时最头疼的不是人脸识别不准而是三个终端各记各的账刷卡的流水在 A 表刷码的在 B 表刷脸的在 C 表月底对账怎么都对不上。这套基于 Spring Boot Vue 前后端分离架构的一卡通消费系统把刷码、刷脸、实体卡三种通道统一收口到一张交易流水上前端只管收集设备和用户请求后端用同一套幂等扣款逻辑处理所有来源。它适合要做校园卡、园区消费、企业食堂这类项目的人也是不错的 Spring Boot Vue 全栈学习范例既能跑通完整流程又能看到工程上怎么取舍。2. 技术选型与目录结构Spring Boot 3 Vue 3 怎么分工2.1 为什么是 Spring Boot Vue而不是单体模板做这类项目团队里往往既有 Java 后端又有前端同学前后端分离最大的好处是部署和排查都清爽。后端单独打 jar 丢到一台机器前端用 Nginx 托管静态文件两边各自扩容、各自发版互不阻塞。对一卡通这种设备接入频繁的系统来说后端接口保持稳定尤其重要读卡器固件升级、人脸识别 SDK 换版本都不需要重新发布前端。我拆这个项目时留意到它的包结构没有堆到一个大 Controller 里而是按业务域拆开用户、卡务、交易、设备、报表各管各的。新人接手时改扣款逻辑不用去翻人脸识别的代码改前端页面也不用担心动了后端表结构。更实际的一点是Spring Boot 生态对 Redis、MyBatis-Plus、WebSocket 都有现成 starter项目初期可以省掉大量胶水代码把精力放在交易正确性上。2.2 项目目录结构逐层拆解school-card-system/ ├── admin-web/ # 运营管理后台Vue3 Element Plus │ ├── src/views/ # 商户、卡务、交易流水页面 │ ├── src/api/ # 封装 axios 请求 │ └── vite.config.js # 开发环境代理解决跨域 ├── api-server/ # 后端主服务Spring Boot 3.2 JDK 17 │ ├── controller/ # 用户、卡务、交易、设备四组入口 │ ├── service/ # 交易流水服务、卡务服务、对账服务 │ ├── mapper/ # MyBatis-Plus Mapper 接口 │ ├── entity/ # 用户、卡、流水、商户等实体 │ ├── config/ # Redisson、WebSocket、Interceptor 配置 │ └── common/ # 统一返回体、异常、常量 ├── face-engine/ # 人脸识别 SDK 对接层独立模块 ├── device-simulator/ # 模拟设备端读卡器/扫码枪/摄像头 ├── script/ │ ├── init.sql # 建库建表脚本 │ └── demo_data.sql # 商户、测试卡、用户演示数据 └── README.md这个结构和大多数商业一卡通项目是对得上的。api-server 承担全部核心业务controller 只做参数接收service 处理幂等、锁、事务mapper 层不写业务逻辑。admin-web 是纯后台管理端给商户、财务查看流水和退款消费动作是走设备端接口进来不需要登录后台。face-engine 单独抽出来是聪明做法。人脸 SDK 的 SDK 包往往很重依赖本地动态库把它隔离成一个模块后主服务优雅得多。device-simulator 则是测试阶段的利器它模拟读卡器和扫码枪上报事件没有实体硬件也能把完整消费链路跑通我第一次跑这个项目就是靠它验证的。3. 核心模块拆解从刷卡到扣款的完整链路3.1 用户发卡与设备鉴权一卡通系统最开始不是扣款而是发卡。新用户录入姓名、卡号再绑一张实体卡或录入人脸卡状态默认是未激活。发卡接口最重要的是防重复同一张卡号不能绑到两个用户身上否则后面消费会出现串账。PostMapping(/api/user/issueCard) public ApiResponseString issueCard(RequestBody IssueCardRequest req) { // 1. 校验卡号是否存在防止重复发卡 UserCard existCard userCardMapper.selectByCardNo(req.getCardNo()); if (existCard ! null) { throw new ServiceException(该卡号已绑定用户请先注销); } // 2. 写入用户卡表默认未激活 UserCard userCard new UserCard(); userCard.setCardNo(req.getCardNo()); userCard.setHolderId(req.getHolderId()); userCard.setStatus(UserCardStatus.INACTIVE); userCardMapper.insert(userCard); // 3. 同步到 Redis消费时直接查缓存提升 TPS redisService.setCacheObject(card: req.getCardNo(), userCard, Duration.ofHours(2)); return ApiResponse.success(发卡成功); }这段逻辑里有两处关键设计卡号和用户是先做唯一性校验再落库避免脏数据Redis 缓存设置了 2 小时过期因为卡可能会被挂失、注销缓存不能长期有效。前端传的 cardNo 要遵循固定格式比如园区代码 六位数字便于读卡器解析。实际项目中发卡往往还会调用读卡器写入物理卡扇区这步通常由 device-simulator 里的串口工具负责MySQL 里只存逻辑卡号。3.2 统一消费流水扫码、刷卡、刷脸的幂等扣款三端消费入口虽然不同到后端都是同一个 consume 方法。这个方法有两个硬指标不能重复扣款不能超扣。最常见的翻车场景是用户快速刷两次闸机或者扫码支付时网络超时重试结果被扣了两笔。Transactional(rollbackFor Exception.class) public TransactionResult consume(ConsumeRequest request) { // 1. 幂等校验同一渠道同一流水号只能成功一次 TransactionLog exists transactionLogMapper.findByChannelAndSerialNo( request.getChannel(), request.getSerialNo()); if (exists ! null) { return TransactionResult.alreadyDone(exists); } // 2. Redisson 分布式锁防止同一用户多点并发扣款 String lockKey consume:lock: request.getUserId(); RLock lock redissonClient.getLock(lockKey); lock.lock(5, TimeUnit.SECONDS); try { UserAccount account accountMapper.selectForUpdate(request.getUserId()); if (account.getBalance().compareTo(request.getAmount()) 0) { throw new InsufficientBalanceException(); } // 3. 扣减余额 accountMapper.deductBalance(request.getUserId(), request.getAmount()); // 4. 写交易流水作为后续对账的唯一依据 TransactionLog log TransactionLog.build(request); transactionLogMapper.insert(log); // 5. WebSocket 推送余额变化给前端展示 wsTemplate.convertAndSend(/topic/balance/ request.getUserId(), account.getBalance()); return TransactionResult.success(log); } finally { lock.unlock(); } }参数里最重要的三个字段是 channel、serialNo、amount。channel 取值 QR_CODE、CARD、FACE用来区分来源serialNo 由前端在发起支付时生成保证全局唯一是幂等的核心amount 用 BigDecimal 而不是 double避免浮点误差。selectForUpdate 是数据库层最后的兜底避免分布式锁失效时依然出现余额扣成负数。实际压测里这个接口在单机环境下可以跑到 800 TPS瓶颈主要在 MySQL 行锁上。3.3 人脸识别1:1 比对的工程化落地人脸识别模块没有被包装成黑匣子它做的是 1:1 比对而不是 1:N 搜索。也就是说消费时设备先把当前抓拍的照片提取特征值再和库里预存的特征值做一次比对返回一个相似度分数由后端决定是否放行。public FaceCheckResult verifyFace(String userId, MultipartFile livePhoto) throws Exception { // 1. 从库里找到该用户预存人脸特征 UserFace face userFaceMapper.findByUserId(userId); if (face null) { return FaceCheckResult.fail(该用户未录入人脸); } // 2. 现场照片提取特征 FaceFeature liveFeature faceClient.extract(livePhoto.getBytes()); // 3. 1:1 比对score 范围 0~1 float score faceClient.compare( FaceFeature.fromBase64(face.getFeatureBase64()), liveFeature ); if (score face.getThreshold()) { // 暗光场景下调低阈值但需要配合活体动作检测 return FaceCheckResult.fail(人脸比对失败score score); } return FaceCheckResult.ok(score); }这里值得注意的参数是 threshold系统里默认给到 0.82。室内正常光线没问题但闸机口遇到逆光或夜晚识别率会明显掉所以常见做法是室外设备硬上加红外补光灯同时把阈值降到 0.72 左右。降阈值会带来误识风险因此要搭配活体检测比如要求用户眨眼、张嘴这个动作校验在设备端完成后端只信任设备上传的最终结果。4. 部署与运行从压缩包到三端联通4.1 初始化数据库与中间件项目根目录的 script/init.sql 包含建库建表语句包括 user_account、user_card、transaction_log、merchant_info 等核心表。部署时先创建数据库再导入初始化脚本。中间件方面只需要 Redis日志和文件存储都写在本地如果把 Redis 也容器化一台 2 核 4G 的机器就能跑完整个系统。配置项推荐值说明MySQL 8.0utf8mb4存储人脸特征 Base64 和订单号需要完整字符集Redis 7.x单机存分布式锁和卡片缓存JDK 1717.0.8Spring Boot 3 最低要求低于 17 会启动失败Node.js18.16前端 Vite 构建要求数据库初始化完成后用 Redis 启动命令把中间件拉起来。没有 Redis 也可以用 Docker 快速起一个避免污染本机环境。cd school-card-system mysql -uroot -p script/init.sql docker run -d --name redis -p 6379:6379 redis:7-alpine4.2 后端启动改数据源和 Redis 地址后端唯一必须改的配置文件是 application.yml重点是数据源地址和 Redis 连接。第一次启动最容易翻车的是 MySQL 时区参数没配导致流水表里的时间差 8 小时这个坑在后面避坑章节会展开讲。spring: datasource: url: jdbc:mysql://localhost:3306/school_card?serverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 username: root password: your_password redis: host: localhost port: 6379启动命令很简单项目自带 Maven Wrapper不需要提前装 Mavencd api-server ../mvnw spring-boot:run看到 “Started ApiServerApplication” 日志后说明后端就绪。此时利用 device-simulator 模块可以模拟一次实体卡消费它读一个卡号调用/api/consume/card如果返回 success 且用户余额减少说明整条链路已经通了一半。4.3 前端启动与联调验证前端是 Vue3 Vite安装依赖后直接启动开发服务器。这里遇到最多的跨域问题已经提前用 proxy 处理好了开发环境里前端发到/api的请求会自动代理到后端 8080 端口。cd admin-web npm install npm run dev启动后访问 Vite 默认的 5173 端口登录后台能看到消费流水和商户列表。建议的联调验证顺序是先用账号充值一块钱再通过 device-simulator 发一笔扫码消费最后在后台确认流水金额、余额、时间都对得上。如果哪一步断了优先查 Redis 里有没有卡片缓存再查后端日志有没有异常这个排查路径基本能覆盖 90% 的问题。5. 避坑集合一卡通系统上线最容易翻车的五个环节5.1 实体卡读卡器串口被占用现象设备模拟器启动时提示 serial port busy刷卡没反应。原因读卡器的 USB 转串口设备被占用通常是别的调试工具或上次未释放的程序占着端口。解决在设备端启动前先关闭占用程序代码里在 finally 块释放串口资源。如果用的是 Linux 部署不要忘了把当前用户加入 dialout 组否则没有访问权限。5.2 人脸识别在暗光环境识别率暴跌现象相同用户白天能通过晚上在闸机口连续失败。原因人脸特征提取依赖图像质量暗光环境噪点多特征比对分数整体偏低。解决降阈值不是唯一解先给设备加红外补光再把人脸特征存储在服务端做二次信息增强最后才考虑把默认阈值从 0.82 降到 0.72。同时开启活体动作检测防止低阈值引入伪造攻击。5.3 扫码支付重复扣款现象用户扫码后网络超时前端又发起重试后台出现两笔相同金额的流水。原因交易流水表没有对 channel serial_no 加联合唯一索引幂等校验存在并发窗口。解决在 transaction_log 表上补唯一索引代码里先查再插依然不够最终靠数据库唯一约束兜底。我在这类项目里习惯的做法是“Redis 锁挡并发MySQL 唯一索引挡重复”两层防护缺一不可。5.4 前后端联调阶段跨域报错现象前端页面能打开但请求后端接口时浏览器提示 CORS error。原因Vite 开发服务器在 5173 端口后端在 8080 端口属于跨域。解决在 vite.config.js 里配置 devServer.proxy把 /api 开头的请求转发到后端。生产环境直接用 Nginx 反代到后端地址前后端同源部署彻底消除跨域。不要在后端 GlobalCorsConfig 里放开所有域名安全隐患大。5.5 流水表时间字段差了 8 小时现象后台看交易时间总是晚 8 小时月初对账时总有几笔对不上。原因JDBC 连接串没指定 serverTimezoneMySQL 默认会话时区和系统时区不一致导致写入 DATETIME 字段时被转换偏移。解决JDBC 连接串加上 serverTimezoneAsia/ShanghaiMySQL 端的 time_zone 参数也统一改掉。这类偏差很难靠代码排查先看 SQL 日志里传进去的时间再看库里存的时间一秒就能定位是哪一层转坏了。6. 二次开发新增 NFC 支付通道的改造实践这套系统最有价值的扩展点是新增支付方式。比如园区要接 NFC 手环核心改动其实不在设备端而是把 NFC 返回的手环标识映射到卡号再走现有的 consume 方法。第一步在消费渠道枚举里加一个值public enum ConsumeChannel { QR_CODE, CARD, FACE, NFC_TAG }第二步在 controller 层加一个入口NFC 读到的 tag 值转成卡号后调用 service。NFC 的 tag 号和实体卡卡号在发卡时就已经绑定所以在 service 里加一行判断消费逻辑全部复用不需要改动交易表结构。PostMapping(/api/consume/nfc) public ApiResponseTransactionResult consumeNfc(RequestBody NfcConsumeRequest req) { // tag 映射到卡号 UserCard card userCardMapper.selectByTag(req.getTag()); if (card null || card.getStatus() ! UserCardStatus.ACTIVE) { throw new ServiceException(无效卡或未激活卡); } ConsumeRequest consumeReq ConsumeRequest.wrap(req, card.getCardNo()); return ApiResponse.success(consumeService.consume(consumeReq)); }另一个常见二次开发需求是给商户报表加“实时退款”按钮。前端在流水列表上加一列操作后端新增 refund 接口核心逻辑是校验原流水状态是 SUCCESS然后写入一条负数的 REFUND 流水并把原流水标记为 REFUNDED。验证方式很简单用同一个原流水号并发请求两次退款接口正确的结果是一次成功、一次返回“该流水已退款”。我踩过最大的坑是刚接手时改消费接口为了图快删掉了卡状态校验结果一批挂失卡全部能正常消费最后靠对账脚本才追回来。从那以后我每次改支付相关代码都强制走一遍“先压测、再对账、最后灰度”的流程这个习惯帮我躲过不少线上事故。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Vibe Coding氛围编程系列|2026全网最热Claude Skills工具箱:GitHub最受欢迎的7大开源AI技能库

Vibe Coding氛围编程系列|2026全网最热Claude Skills工具箱:GitHub最受欢迎的7大开源AI技能库

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 8:27:15 阅读更多 →
RGB-IR双模态检测:改进MF模块的MFEI输入级融合实现

RGB-IR双模态检测:改进MF模块的MFEI输入级融合实现

1. 为什么首选输入级融合:RGB-IR检测的痛点与融合层次之争做RGB-IR双模态目标检测的人,十有八九会遇到同一个尴尬局面:单用可见光(RGB),一到夜间、逆光、雨雾场景,目标直接消失;单用…

2026/10/19 17:25:24 阅读更多 →
FCA-RL框架:用强化学习破解出行运力调度与动态分配难题

FCA-RL框架:用强化学习破解出行运力调度与动态分配难题

有人看到“ECML-PKDD”这种会议名,第一反应可能是“又是一篇学术论文”,离实际业务很远。但如果你正好在做出行服务、网约车调度、同城货运派单这类偏实时决策的业务,这篇关于FCA-RL框架的工作,其实是一个很值得落地的参考样本。我…

2026/10/19 17:59:47 阅读更多 →

最新新闻

校招面试算法与JVM核心考点全解析:从刷题到调优的实战指南

校招面试算法与JVM核心考点全解析:从刷题到调优的实战指南

1. 校招面试官到底想在算法题里看到什么很多人准备校招的时候,第一反应是去刷题网站按标签刷个三五百道,觉得数量上去了自然就稳了。我带过几届校招的技术面,也跟不少做校招的朋友聊过,实际情况是:面试官出算法题&…

2026/10/19 22:50:12 阅读更多 →
【信息科学与工程学】【数据中心】 第十一篇 人工智能智算中心整体设计101 智算中心解决方案 第一部分1.1 智算中心ICT基础设施的定义与边界

【信息科学与工程学】【数据中心】 第十一篇 人工智能智算中心整体设计101 智算中心解决方案 第一部分1.1 智算中心ICT基础设施的定义与边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 22:46:04 阅读更多 →
open-code-review:CLI+LLM+Git驱动的开源代码审查新范式

open-code-review:CLI+LLM+Git驱动的开源代码审查新范式

1. 什么是 open-code-review:一个被严重低估的开发者协作新范式open-code-review 不是某个具体软件的名字,也不是某家公司的私有产品,而是一类正在快速成型的技术实践范式——它指代的是基于开源理念、命令行界面(CLI)…

2026/10/19 22:40:54 阅读更多 →
二轮平衡车全套源码:卡尔曼滤波与PID三环控制实战

二轮平衡车全套源码:卡尔曼滤波与PID三环控制实战

简介:这是一套面向电子爱好者与嵌入式初学者的二轮平衡车DIY完整资料包,涵盖从硬件搭建到软件调试的全链路内容。资源定位为STM32平台平衡车的开源学习套件,包含互补滤波、卡尔曼滤波、DMP三种姿态解算方案的源码,三套工程均编译通…

2026/10/19 22:39:52 阅读更多 →
加扰与解扰:原理、区别及在高速链路中的应用

加扰与解扰:原理、区别及在高速链路中的应用

简介:本资源面向数字通信、FPGA开发与信号处理方向的学习者,围绕加扰与解扰这一核心环节,提供基于VHDL的完整工程实现与验证流程。加扰通过可逆数学变换打散连续相同符号,降低信道自相关与互相关影响,广泛应用于数字电…

2026/10/19 22:35:37 阅读更多 →
数据范围决定算法选型:从O(n log n)到O(n)

数据范围决定算法选型:从O(n log n)到O(n)

我去年处理过一个挺有意思的活儿:某内部工具需要在上亿条日志里按时间窗口做聚合统计,数据的时间戳全部落在最近三个月内。一开始我按教科书思路上了红黑树、跳表这类“高级”结构,结果在真实数据上跑得还不如后来换上的一个普通数组快。原因…

2026/10/19 22:32:32 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →