多租户系统从哪里开始设计?租户身份、数据隔离、权限与套餐的整体方案
多租户系统最容易出现的误判是给业务表加一个tenant_id再给页面加一个“切换公司”入口就以为完成了多租户。实际运行时同一账号可能加入多家公司平台管理员和租户管理员管理的对象不同某个功能即使出现在菜单上也未必在当前套餐内导出和后台任务又不在最初的请求线程里。任何一处只信前端传来的租户编号都可能把两家公司的数据或管理权限混在一起。本文先建立整体设计地图回答系统要保存哪些对象、一次请求如何经过身份和授权边界、租户状态与套餐如何影响结果并用一组固定数据验证。后续文章再分别深入开通、数据隔离、两级管理员、多级菜单、套餐和配额。这里不重复“单库还是多库”的部署选型无论选哪种存储方式业务请求仍要确定自己属于哪个租户。示例环境Java 17、Spring Boot 风格服务层、MySQL 8.x。代码和表名是教学用的最小模型不对应任何产品的完整内部设计。案例中的公司、人员和单据均为虚构。目录先看一个会出错的业务场景多租户系统的五类对象一次请求必须经过哪些判断数据隔离不能只靠列表条件平台权限、租户权限和套餐能力如何区分生命周期与订阅变化如何影响已有数据最小实现、自动测试与SQL核对上线验收清单与适用边界一、先看一个会出错的业务场景假设一个 SaaS 系统服务两家公司。账号u_07同时加入甲公司和乙公司在甲公司是租户管理员在乙公司只是普通查看者。甲公司的套餐允许使用“批量报表”乙公司的套餐暂未开通。两家公司各有一张编号相似的申请单。对象甲公司t_a乙公司t_bu_07的成员身份管理员可查看和审批本公司单据查看者只能查看授权范围内单据申请单a_1001归属t_ab_1001归属t_b批量报表能力已开通未开通租户状态ACTIVEACTIVE现在u_07在乙公司页面把详情 URL 中的b_1001改成a_1001或者把请求头里的租户编号改成t_a试图在乙公司会话中调用甲公司的审批接口。这两种请求都不应因为“账号确实加入了甲公司”就直接成功当前会话选定的租户、该租户中的成员权限、目标记录的归属以及功能权益需要按顺序核对。**本文预期结果**乙公司会话查询甲公司单据返回不可见乙公司会话发起批量报表被能力校验拒绝切换到甲公司会话后可以查看甲公司授权范围内的数据但管理员身份也不能越过单据归属或数据范围。平台管理员的运营权限另走受控入口不能伪装成任意租户成员。图1账号可以相同但当前租户、成员权限、业务数据和套餐能力必须分别判定。二、多租户系统的五类对象设计时先把“账号”和“租户”分开。账号回答“谁登录了”租户回答“当前在为哪个组织操作”。两者之间的成员关系回答“这个人是否属于该组织、在该组织担任什么角色”。同一个账号可以有多条成员关系而一个租户可以有多名成员。再把管理权限、业务数据和套餐权益分开对象最小问题常见错误租户及生命周期组织是否已开通、可用、冻结或停用只检查登录成功不检查租户是否可用账号与租户成员登录者在当前租户中是什么身份把账号的全局角色当作各租户的角色权限与数据范围当前成员可执行什么动作、访问哪些记录只控制菜单不保护接口与详情套餐订阅与权益当前租户拥有哪些功能和资源额度把菜单可见等同于功能已开通业务资源单据、文件、任务归属哪个租户只给主表加租户字段附件和导出漏掉这五类对象并不意味着要堆成五个独立服务。小系统可以先在一个服务内清晰划分模块复杂系统可按职责拆分。关键是对象语义不能混用租户管理员是权限身份不是套餐套餐开通的是产品能力不自动授予用户审批权限租户状态决定是否允许新操作不等于把历史数据删除。图2成员关系连接账号与租户权限和订阅分别约束“谁能做”和“产品允许做什么”。三、一次请求必须经过哪些判断用u_07在甲公司审批a_1001为例一次请求可拆成六个判断顺序也有意义**认证账号。**确认请求来自已登录账号u_07不把前端填写的用户 ID 当作身份。**确定当前租户。**从受信会话或经过校验的租户选择结果得到t_a如果允许切换租户切换时重新验证成员关系并更新会话不能只改一个请求头。**检查租户与成员状态。**租户必须可用成员必须有效停用成员即使持有旧会话也不能继续新操作。**检查动作权限。**在当前租户内确认u_07有审批动作平台级权限不能自动折算为租户级审批权。**检查业务资源归属与数据范围。**按tenant_idt_a AND ida_1001找记录再核对该成员可访问这张单据查不到时不返回另一租户的存在信息。**检查产品能力与执行条件。**若审批属于需订阅功能检查当前生效权益若有配额还要在真正提交时作并发安全的额度校验。认证、授权、权益和数据归属不是同一把锁。即使u_07在甲、乙两家公司都是合法成员也不能省掉当前租户的确定即使有菜单权限也不能省掉业务资源的归属检查。列表、详情、修改、下载、导出及异步任务都要遵循同一套租户边界。图3任何一步失败都应停止执行页面入口只是提示不是服务器端授权证据。四、数据隔离不能只靠列表条件最常见的补丁是在列表查询增加WHERE tenant_id ?。这必要却远远不够。详情通常通过单据 ID 直查修改可能先按 ID 更新再校验附件可能用对象地址下载导出和后台任务可能在用户离开页面后继续执行。只要其中一条路径缺少租户归属约束列表再正确也挡不住串数据。一个实用原则是**资源的创建、读取、修改和交付都携带可信的租户边界。**写入时由服务端赋予tenant_id读取和更新时将tenant_id与资源 ID 一起作为定位条件附件保存所属租户和所属业务对象缓存键、搜索索引、消息和后台任务也显式携带租户标识。不能用客户端给出的tenant_id覆盖服务端已确认的身份。若采用共享数据库业务表中的tenant_id、组合索引和更新条件是基本防线。若采用独立数据库也需要在连接路由前正确确认租户身份且跨租户平台操作要有单独的审批与审计。两种部署方式解决的是不同层面的隔离不能替代接口授权。-- 教学用最小结构实际字段和索引应依据查询模式调整。CREATETABLEsample_tenant(tenant_idVARCHAR(32)PRIMARYKEY,statusVARCHAR(20)NOTNULL);CREATETABLEsample_request(idBIGINTPRIMARYKEY,tenant_idVARCHAR(32)NOTNULL,request_noVARCHAR(40)NOTNULL,statusVARCHAR(20)NOTNULL,UNIQUEKEYuk_tenant_request_no(tenant_id,request_no),UNIQUEKEYuk_tenant_request_id(tenant_id,id),CONSTRAINTfk_request_tenantFOREIGNKEY(tenant_id)REFERENCESsample_tenant(tenant_id));CREATETABLEsample_file(idBIGINTPRIMARYKEY,tenant_idVARCHAR(32)NOTNULL,request_idBIGINTNOTNULL,UNIQUEKEYuk_file_tenant_id(tenant_id,id),CONSTRAINTfk_file_requestFOREIGNKEY(tenant_id,request_id)REFERENCESsample_request(tenant_id,id));-- 详情必须同时使用可信租户 ID 与业务记录 ID。SELECTid,request_no,statusFROMsample_requestWHEREtenant_id:currentTenantIdANDid:requestId;-- 修改以影响行数判断是否仍存在且状态允许推进。UPDATEsample_requestSETstatusAPPROVEDWHEREtenant_id:currentTenantIdANDid:requestIdANDstatusPENDING;上述 SQL 只能保证不会按另一个租户的记录 ID 直接改到那条记录审批人范围、状态机和套餐能力仍应由服务层或更严格的数据访问层校验。尤其不要把“影响 0 行”直接解释为“记录不存在”也可能是记录属于别的租户、状态已变化或版本已冲突。对外错误应避免泄露跨租户信息对内日志保留可审计的原因码。五、平台权限、租户权限和套餐能力如何区分企业 SaaS 至少存在两个管理面。平台管理员维护租户开通、套餐模板、运行状态等平台对象租户管理员在自己的租户内管理成员、角色和业务配置。平台管理员不应因为“级别更高”就无条件拥有每家租户的业务单据读写权。需要排查租户问题时应通过受限、留痕的支持授权进入而不是默默绕过租户边界。多级菜单也容易混淆三个判断判断回答什么甲公司批量报表示例套餐能力甲公司是否购买/开通该能力有效订阅包含BATCH_REPORT租户内权限u_07在甲公司能否使用该能力角色包含“报表生成”动作数据范围生成的报表能包含哪些记录只能包含甲公司且在授权范围内的数据三项都成立操作才可以执行。菜单展示可依据前两项做友好提示但不能代替接口端的判断报表下载还要再次校验文件归属和访问权。套餐到期时可以停止创建新报表但如何处理已生成文件、进行中的任务需要提前写成明确规则不能由某个页面临时决定。图4两级管理员管理不同对象能看见入口、能执行动作和能读取数据是三件事。六、生命周期与订阅变化如何影响已有数据租户不是只有“存在/不存在”两种状态。以开通流程为例注册成功只代表取得租户编号管理员建立、基础配置就绪、订阅生效、必要资源可用之后才适合进入ACTIVE。任何一步失败都应保留可恢复的状态与失败原因避免页面宣告成功而后台尚不可用。运行中还会遇到升级、降级、到期、冻结和停用。一个可解释的设计应分别回答新操作能否发起历史数据能否读取正在执行的任务如何处理已有文件保留多久管理员能否导出必要记录这些问题不宜全部由一个enabled布尔值决定。变化新操作历史数据需要额外核对套餐升级按生效时间开放新增能力不应改写旧记录归属新权益何时生效、缓存何时刷新套餐降级或到期禁止超出当前权益的新操作原则上保留读取政策需明确运行任务、已生成产物及超额资源租户冻结暂停受限业务操作按合同与安全策略保留后台任务、消息发送、文件访问租户恢复通过核验后恢复允许的操作检查冻结期间状态一致性订阅是否仍有效、队列是否重放表中是设计讨论的起点不是适用于所有 SaaS 的合同条款。尤其涉及数据保留与删除应由产品政策、客户约定和适用法规共同确定工程实现只负责让规则可执行、可追踪。七、最小实现、自动测试与SQL核对下面的示例只展示业务服务层的检查顺序。TenantSession必须由认证层根据登录者、成员关系和租户切换结果构造不能从请求体直接实例化。Rights是预先解析的权限与套餐能力快照实际系统还需定义版本或失效策略避免权益变化后旧会话长期有效。recordTenantSession(StringuserId,StringtenantId,booleanactiveTenant,booleanactiveMember){}recordRights(booleancanApprove,booleanapprovalFeatureEnabled){}recordRequestRow(longid,StringtenantId,Stringstatus){}finalclassApprovalService{privatefinalRequestRepositoryrequests;ApprovalService(RequestRepositoryrequests){this.requestsrequests;}voidapprove(TenantSessionsession,Rightsrights,longrequestId){if(!session.activeTenant())thrownewAccessDeniedException(TENANT_INACTIVE);if(!session.activeMember())thrownewAccessDeniedException(MEMBER_INACTIVE);if(!rights.approvalFeatureEnabled())thrownewAccessDeniedException(FEATURE_DISABLED);if(!rights.canApprove())thrownewAccessDeniedException(ACTION_FORBIDDEN);RequestRowrowrequests.findByTenantAndId(session.tenantId(),requestId).orElseThrow(()-newAccessDeniedException(RESOURCE_UNAVAILABLE));if(!PENDING.equals(row.status()))thrownewIllegalStateException(STATE_CHANGED);if(requests.approveIfPending(session.tenantId(),row.id())!1)thrownewIllegalStateException(STATE_CHANGED);}}interfaceRequestRepository{OptionalRequestRowfindByTenantAndId(StringtenantId,longid);intapproveIfPending(StringtenantId,longid);}示例刻意不把平台管理员作为特权分支塞进approve跨租户支持访问应走单独的受控流程。代码中findByTenantAndId体现读边界approveIfPending体现写边界和并发状态约束。activeTenant应由可信租户状态服务生成生产环境还要按业务要求对数据范围作进一步判断。AccessDeniedException可用 Spring Security 对应异常若脱离该框架可替换为项目自己的拒绝异常。最少应有以下自动测试测试数据固定为a_1001属于t_a、b_1001属于t_bTestvoidbSessionCannotApproveARequestEvenWhenSameUserBelongsToBothTenants(){varbSessionnewTenantSession(u_07,t_b,true,true);varbRightsnewRights(true,true);// 即使误授予动作权限资源归属仍要挡住越界when(repository.findByTenantAndId(t_b,1001L)).thenReturn(Optional.empty());assertThatThrownBy(()-service.approve(bSession,bRights,1001L)).isInstanceOf(AccessDeniedException.class);verify(repository).findByTenantAndId(t_b,1001L);verify(repository,never()).approveIfPending(anyString(),anyLong());}TestvoidaSessionApprovesOnlyItsPendingRequest(){varaSessionnewTenantSession(u_07,t_a,true,true);varaRightsnewRights(true,true);when(repository.findByTenantAndId(t_a,1001L)).thenReturn(Optional.of(newRequestRow(1001L,t_a,PENDING)));when(repository.approveIfPending(t_a,1001L)).thenReturn(1);service.approve(aSession,aRights,1001L);verify(repository).approveIfPending(t_a,1001L);}TestvoidmenuPermissionCannotOverrideExpiredFeature(){varaSessionnewTenantSession(u_07,t_a,true,true);assertThatThrownBy(()-service.approve(aSession,newRights(true,false),1001L)).isInstanceOf(AccessDeniedException.class);verifyNoInteractions(repository);}TestvoidfrozenTenantRejectsOldSessionBeforeReadingBusinessData(){varfrozennewTenantSession(u_07,t_a,false,true);assertThatThrownBy(()-service.approve(frozen,newRights(true,true),1001L)).isInstanceOf(AccessDeniedException.class);verifyNoInteractions(repository);}这四个测试分别证明跨租户拒绝、合法路径可推进、功能未开通及租户被冻结时不访问业务数据。它们没有覆盖租户切换、附件下载或异步任务那些入口必须另加接口或集成测试不能因为服务层单测通过就宣布隔离完成。上线前还可对共享库做两类巡检。第一类找缺少租户归属或与主单据归属不一致的附件第二类检查业务记录是否关联有效租户。预期结果均为0 行否则应先修复数据与写入路径再开放新功能。SELECTf.id,f.tenant_idASfile_tenant,r.tenant_idASrequest_tenantFROMsample_file fJOINsample_request rONr.idf.request_idWHEREf.tenant_idr.tenant_id;SELECTr.id,r.tenant_idFROMsample_request rLEFTJOINsample_tenant tONt.tenant_idr.tenant_idWHEREt.tenant_idISNULL;这里的三张表只展示租户、业务单据和附件的关键约束不含真实产品的全部字段。复合外键可拦截“附件写在甲租户、却关联乙租户单据”的数据错误真实系统还应检查导出结果、缓存键和任务记录但不要用一条大 SQL 假装覆盖所有存储介质。图5验收应同时包含允许路径与拒绝路径并核对数据、权限和权益三个结果。八、上线验收清单与适用边界真正上线时至少用两家虚构租户、一个跨组织账号、不同成员角色、不同套餐和同名业务编号走完下面的矩阵场景预期结果验证证据在甲公司会话查看甲公司授权单据成功响应归属、查询日志与当前租户一致在乙公司会话猜测甲公司单据 ID拒绝且不泄露存在性接口测试与拒绝原因码乙公司未开通批量报表却直调接口拒绝不创建任务权益校验记录、任务表无新增租户管理员授予平台级权限拒绝授权范围测试、审计记录冻结乙公司后旧会话继续请求按冻结政策拒绝新操作会话与租户状态联动测试导出/后台任务在用户离开页面后执行仍使用创建时已确认的租户身份任务记录、文件归属及下载授权如果只有单租户部署或客户明确要求独立环境可以简化部分跨租户运行机制但仍要清楚区分账号身份、角色权限、资源归属与产品权益。反过来若已经存在跨组织协作、平台代运营或复杂合同规则上述最小模型只是起点必须补上受控授权、审计、保留政策及更严格的隔离措施。多租户的核心不是一个字段或一种数据库形态而是让每次操作都能回答四个问题**谁在操作、代表哪个租户、能碰哪些资源、当前权益是否允许。**把这四个答案贯穿页面、接口、数据、文件和后台任务后续的开通、两级管理员与套餐控制才有稳定落点。参考资料Microsoft Learn多租户方案的租赁模型与隔离权衡Microsoft Learn多租户方案中的身份与授权OWASP多租户安全检查清单Spring Security方法级授权

相关新闻

(161页PPT)制造业变革转型八大领域营销服务研发供应链制造质量财务及人力资源的痛点与改进策略(附下载方式)

(161页PPT)制造业变革转型八大领域营销服务研发供应链制造质量财务及人力资源的痛点与改进策略(附下载方式)

篇幅所限,本文只提供部分资料内容,完整资料请看下面链接 https://download.csdn.net/download/AI_data_cloud/88338620 资料解读:制造业变革转型八大领域营销服务研发供应链制造质量财务及人力资源的痛点与改进策略 详细资料请看本解读文章…

2026/10/12 3:52:20 阅读更多 →
Chrome 自动化「点了没反应」:5 个真实坑与一套排查法

Chrome 自动化「点了没反应」:5 个真实坑与一套排查法

Chrome 自动化「点了没反应」:5 个真实坑与一套排查法脚本跑完了,控制台没有报错,页面也没有任何反应。做浏览器自动化,最耗时的从来不是崩溃,而是静默失败。本文用 CDP(Chrome DevTools Protocol&#xff…

2026/10/12 3:52:20 阅读更多 →
【C++】从基类到派生类:关于C++继承的务实梳理

【C++】从基类到派生类:关于C++继承的务实梳理

🏆浮世尘弦:个人主页 ⭐个人专栏:《C语言》、《数据结构与算法》、《C》 💎:非淡薄无以明志,非宁静无以致远 前言 在 C 中,继承是面向对象编程的重要机制之一。它允许我们基于已有的类定义新的类…

2026/10/12 3:52:20 阅读更多 →

最新新闻

【I2C的七宗罪】深度总结:从协议原理到硬件故障排查

【I2C的七宗罪】深度总结:从协议原理到硬件故障排查

前言:最近在网上淘到了一篇讲解IIC的文章,讲的是一个老工程师的多年IIC调试工作的总结,看了之后感觉覆盖了几乎所有的IIC调试过程可能遇到的问题,由于在CSDN平台上面没有发现类似的完整的文章,现就此重新加工编辑一下&…

2026/10/13 7:01:55 阅读更多 →
新品发布后多久才能被AI推荐?——以iPhone Duo为例

新品发布后多久才能被AI推荐?——以iPhone Duo为例

新品发布后多久才能被AI推荐?——以iPhone Duo为例【摘要】Top1推荐率为0、总提及率仅7.1%:基于4款主流大模型、140个有效回答、9个品牌实测数据,拆解新品从「被报道」到「被AI推荐」的上架时差,将AI上架拆分为3道门槛&#xff0c…

2026/10/13 7:01:55 阅读更多 →
OS_labs内核沙盒:从页表映射到进程切换的可调试x86-64实操指南

OS_labs内核沙盒:从页表映射到进程切换的可调试x86-64实操指南

简介:本资源是一套面向高校操作系统课程学习者的C语言实验项目合集,聚焦进程管理、内存调度、文件系统与系统调用等核心原理的动手实践,适用于具备基础C编程能力的本科生及系统编程初学者。压缩包共12个文件,含5个C源码文件&#…

2026/10/13 7:01:55 阅读更多 →
医疗器械包装运输验证:GB/T4857标准应用与方案设计

医疗器械包装运输验证:GB/T4857标准应用与方案设计

医疗器械包装运输验证这件事,我这两年做得比较多,说句实话,大部分产品出事都出在“纸箱看起来没事,开箱后产品坏了”或者“运输途中振动导致精密部件脱落”这种不起眼的环节上。国内做医疗器械注册,包装验证经常是发补…

2026/10/13 7:01:55 阅读更多 →
Python函数参数:从提示词到temperature掌控大模型输出

Python函数参数:从提示词到temperature掌控大模型输出

我们开始上课。第14课,前面几节课,我们已经把Python基础语法过了一遍,也成功让自己的电脑接上了大语言模型的API,能写出类似“你今年几岁”这种问题,让AI回答。但是有个问题一直困扰着零基础的小朋友:同一个…

2026/10/13 7:01:55 阅读更多 →
Java实现GB28181 RTP PS流解包与RTMP推流实战

Java实现GB28181 RTP PS流解包与RTMP推流实战

1. 项目背景与整体设计思路1.1 为什么要在Java里做RTP PS流解包推RTMP做过安防流媒体对接的朋友应该都有体会,GB28181协议在国内安防行业几乎是绕不开的存在。摄像头、NVR、平台之间信令走SIP,媒体流走RTP承载PS封装,这套组合拳已经跑了很多年…

2026/10/13 7:00:55 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →