news 2026/10/17 5:48:27

Spring Boot+Shiro+CAS单点登录生产实践:多应用与分布式会话

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot+Shiro+CAS单点登录生产实践:多应用与分布式会话

把 Spring Boot、Shiro、CAS 这套单点登录方案写到第四篇的时候,我一度觉得登录、授权、SSO 这三个词已经被拆得差不多了。demo 能跑、流程图能画、配置能抄,看起来没有什么继续写下去的必要。但实际把项目往生产环境推的过程中,我很快发现之前的“跑通”只是起点:两个应用同时接同一套 CAS,A 系统登录完,B 系统依然不认;权限数据改了,不重启就永远停在旧版本;后端服务上了负载均衡之后,明明几分钟前还登录成功,下一次请求又被打回登录页。这一篇就是给这个系列收尾兼补坑的,重点解决三个生产级问题:授权模型怎么做得更灵活、SSO 会话在分布式环境里怎么保持、以及我实际改造过程中真实踩过的那些坑。适合已经能把基础 demo 跑起来、正在把这套体系往真实环境推进的读者。

1. 开局先对齐:前四篇做完,这套体系到底跑到了哪一步

1.1 快速回顾你手里现在这套链路

前面四篇做的事情,可以归纳成一条很清晰的链路:用户访问业务系统,业务系统发现未登录,重定向到 CAS Server 的登录页;用户输入账号密码完成身份认证,CAS Server 生成票据 Ticket 并通过回调地址返回给业务系统;业务系统拿着 Ticket 去 CAS Server 做服务端校验,校验成功后在自己的会话里建立登录态;Shiro 依靠 Realm 里查询出来的角色和权限数据,对受保护资源和接口做拦截判断。

这个链路里,Spring Boot 负责把整个流程组件化,Shiro 负责业务应用内部的身份和授权控制,CAS 负责统一认证和跨应用票据传递。前几篇的代码走到了“能登录、能拦截、能退出”这一步,对应的角色表、权限表、URL 过滤规则和 CAS 校验逻辑也都通掉了。单应用视角下,这确实已经是一套能演示的完整方案。

1.2 第五篇的重点不是“能登录”,而是“可上生产”

很多人把单点登录理解成“一次登录,到处访问”,但在真实项目里这句话只说对了一半。一个大型系统往往由多个业务应用构成,这些应用可能部署在不同服务器、不同域名路径,甚至属于不同团队维护。你的应用 A 可以跳转到 CAS 完成登录,应用 B 同样可以跳转登录,但两者之间的“登录状态”互相不感知。更麻烦的是,你自己可能还有集群环境、权限动态变更、接口鉴权统一返回 JSON 这些需求。

所以第五篇我换一个视角,不去重复讲基本流程怎么配,而是从“生产环境需要什么”这个角度把方案补完整。核心是三件事:多应用接入时的认证链路怎么调整,授权信息怎么做动态刷新,分布式会话和全局退出怎么保证一致。这三件事处理完,这套 springboot + shiro + cas 的组合才算真正有落地的底气。

2. 多应用接入下的认证链路改造

2.1 两个应用接同一个 CAS,为什么用户体验对不上

我第一次带新人接入第二个应用的时候,出现了一个很直观的问题:应用 A 在 8080 端口登录成功之后,切到应用 B 的页面,B 依然把用户弹回 CAS 登录页。这其实是符合 CAS 协议行为的,因为 CAS 是认证中心,它验证的是“用户身份是否合法”,而不是“用户登录过哪一个应用”。用户访问 A 时,A 拿到的是 A 的 service 票据;访问 B 时,B 没有这个票据,只能重新发起一次 CAS 校验流程。

更隐蔽的是回调地址不一致的问题。CAS Server 在登录成功后要把用户带回发起登录的应用,带回去的地址由 service 参数决定。如果你在 B 系统配置的 service 回调地址写错了一个路径,CAS 回调的时候直接打到一个不存在或无人处理的接口,用户看到的要么是 404,要么是校验失败重新跳回登录页。所以多应用接入的第一条经验是:所有应用的回调地址必须完整、独立、可重复访问,并且在 CAS Server 侧维护一份白名单。

2.2 三层结构改法:认证中心、业务应用、应用间互信

多应用接入后,我习惯把整个体系改成三层结构。最顶层是 CAS Server,只负责两件事:对用户身份做认证、发放和校验 Ticket。中间层是业务应用,每个应用都是一个独立的 Shiro 客户端,负责自己的登录态维护和 URL 权限判断。最底层是统一用户中心或权限数据库,给 Realm 查询提供角色和权限数据。

这三层里容易犯错的是“信任传递”问题。CAS Server 验证通过后,业务应用应当在本地创建会话,后续请求只信任自己的本地会话,不每次回跳到 CAS Server。但不同业务应用之间如果想做接口级互调,就不能只依赖用户浏览器里的会话,而应该用接口签名、Token 或内部鉴权的方式解决问题。这里有一个常见的误解:单点登录只是让用户在多个应用之间免重复登录,它不负责解决服务与服务间的调用鉴权,后者是另一个独立话题。

2.3 跨域场景下的登录态传递:Ticket 管认证,Session 管会话

多应用一旦涉及不同二级域名,问题会更明显。比如应用在 a.example.com,另一个在 b.example.com,Cookie 默认不能跨域名共享,所以你在 A 应用种下的会话,B 应用根本无法读取。CAS 在这种场景下的价值才会真正体现出来:用户访问 A,被引导到统一登录域名,登录完成后生成 Service Ticket 回跳 A;再访问 B,B 依然没有本地会话,于是再引导到统一登录域名,但因为用户在 CAS Server 端已经有全局 TGT,所以这次不会让用户重新输密码,而是直接发一张新票据并回跳 B。

整个交互过程中,Cookie 不需要跨域,只需要统一登录域名自己的 Cookie 工作正常即可。也就是 CAS Server 管“你之前有没有登录过”,业务应用管“你当前这个应用的会话有没有建立”。这两段状态各管一段,配合起来才是完整的 SSO 体验。

2.4 Shiro 过滤器链的正确定位

多应用接入后,改动其实集中在 Shiro 的过滤链配置上。核心原则是要分清楚哪些地址必须匿名放行、哪些地址由 CAS 过滤器处理、哪些地址需要用户登录才能访问。我在配置里一般这样划分:

/ = anon /callback = anon /login = cas /logout = anon /css/** = anon /js/** = anon /static/** = anon /** = user

这里有两个容易踩坑的点。第一,原因/login和/callback不能全部 anon,login 地址要指向 CAS 的登录入口,所以交给 cas 过滤器处理比单纯 anon 更合适;第二,/** = user并不是多余配置,它保证了除上述白名单外的所有地址都要有本地会话,而user过滤器比authc更宽容,允许“记住我”的用户访问,实际用下来体验更贴近业务要求。还有一点必须注意:Spring Boot 下 Shiro 过滤器配置是LinkedHashMap,配置顺序就是执行顺序,匿名放行一定要放在拦截规则之前,一旦把/**写在前面,后面全部白搭。

3. 授权这层,别只满足于“能拦就行”

3.1 每次请求都查数据库,还是走缓存

基础 demo 里最常见的写法是每来一个请求,Shiro 的 Realm 就根据用户名去数据库查询角色和权限列表。单用户场景下没有任何问题,但并发上来之后,权限查询会成为很明显的性能瓶颈。所以我把权限数据放到了 Redis,登录成功后首次查询写入缓存,后续授权判断直接走缓存。

Shiro 的授权缓存理解起来很简单:权限数据不常变,变化频率远低于用户请求频率,把它缓存起来换取更短的响应时间。Realm 中通过getAuthorizationInfo方法返回用户权限集合,Shiro 的AuthorizingRealm内部会根据用户名调用这个方法来取数据,你只要把返回值做成“先查缓存,缓存没有再查库,查完再写回缓存”的逻辑就可以了。实现上可以直接依赖 Shiro 内置的 CacheManager,也可以自己接 Redis,我更推荐后者,因为集群环境下多节点共享同一个 Redis,权限缓存才能全局一致。

3.2 权限改完不重启,动态刷新的设计思路

缓存带来的副产物是数据陈旧。你给某个用户新增了一个角色,数据库已经改了,但 Redis 里还留着旧的授权信息,用户刷新页面发现权限没生效。这个问题在第四篇里其实就已经存在,只是当时没有单独讲。动态刷新的本质是:当你主动修改了某个用户的角色或权限之后,必须把该用户对应的授权缓存删掉,让下一次请求重新查库并写缓存。

我有一个比较省事的做法:所有权限变更操作走一个统一的服务接口,操作完之后根据用户名调用刷新方法,把该用户在 Shiro 授权缓存里的记录移除。代码骨架大致是这样:

public void refreshUserAuthorization(String username) { String cacheKey = "shiro:authorization:" + username; redisTemplate.delete(cacheKey); // 让 shiro 的授权缓存也失效 authorizationCache.remove(username); }

注意这里不只是删自己 Redis 的 key,还要处理 Shiro 内置的授权缓存。如果用的是 Shiro 自带的 CacheManager 且底层就是 Redis,那删一个 key 就够了;如果是内存缓存,就还需要显式调用缓存管理器的 remove 方法。实际项目里由于存在多节点,我推荐直接统一走 Redis 缓存,这样刷新动作只要执行一次,所有节点就都感知到了。

3.3 授权粒度:URL 拦截与注解两个维度怎么配合

Shiro 授权可以写在 filterChain 配置层,也可以写在 Controller 方法上。URL 维度适合做粗粒度的安全边界,比如/admin/**只有 admin 角色能访问;注解维度适合做细粒度控制,比如某个具体的删除接口要求指定权限码user:delete。初学的时候很容易把这两种手段混用,结果同一个接口被两层逻辑限制,排查问题的时候都不知道是谁拦的。

我的经验是分清楚层级:FilterChain 负责入口管控,解决“哪些人进得去”的问题;方法注解负责接口级授权,解决“进去之后能干什么”的问题。两者并不冲突,而且生产上非常有用。举个例子,一个普通用户可以进/user/**路径,但其中某个删除接口标了@RequiresPermission("user:delete"),没有这个权限码的用户就算走到接口,也会在方法调用前被拦截下来。这套组合既能保护大范围路径,又不丢失细粒度控制能力。

4. 会话与 SSO 状态保持的各种疑难杂症

4.1 会话放在哪:CAS Server、应用本地,还是 Redis

做 CAS 接入的时候,一个绕不开的问题是“用户登录状态到底存在哪里”。CAS Server 的 Session 里保存的是全局登录态 TGT,业务应用自己不直接感知;业务应用在拿到 Service Ticket 并通过验证后,需要在本地再建一个自己的 Session,保存当前用户信息和授权信息。很多人误以为 CAS 登录成功后业务应用就一直靠 CAS 会话撑着,这是不对的,业务应用后续接口判断登录,看的永远是自己的 Shiro Session。

那这个本地 Session 存哪里?小项目可以放内存,部署在同一个进程里没有问题;大一点、有多节点部署的,必须把 Session 放到 Redis。因为一旦你做了负载均衡,用户请求可能会被转发到不同节点,如果 Session 只在节点 A 的内存里,请求落到节点 B 自然就丢了。这个问题不是 Shiro 特有的,但 Shiro 给你提供了替换 Session 存储的接口,接 Redis 属于常规操作。

4.2 多节点部署:同一个登录态怎么跨节点保持

多节点会话保持的配置,核心改造点是给DefaultWebSessionManager挂一个RedisSessionDAO。每次创建新会话的时候,session 对象会被序列化到 Redis;后续请求如果落到其他节点,节点从 Redis 取出同一个 Session,用户无感知。代码上比较典型的一段配置长这样:

@Bean public DefaultWebSessionManager sessionManager(RedisSessionDAO sessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(sessionDAO); sessionManager.setGlobalSessionTimeout(1800000); sessionManager.setDeleteInvalidSessions(true); return sessionManager; }

这里有个容易忽略的细节:sessionManager必须被设置进SecurityManager,不然配置白做。另外一个体验相关的参数是globalSessionTimeout,不能设太短,否则用户操作稍微慢一点就被踢出;也不能太长,否则安全风险大。30 分钟是一个比较平衡的默认值,具体按业务敏感度调整。

4.3 全局退出怎么做得干净,又怎么避免“假退出”

单应用退出很简单,业务应用调一下subject.logout(),把本地会话销毁就完了。但 CAS 单点登录要求的退出是“全局退出”,也就是用户在一个应用点击退出,其他已经登录过的应用也要同步失效。协议上的做法是业务系统重定向到 CAS Server 的 logout 接口,CAS Server 再把其他受保护应用的会话一并清掉,然后通过 service 参数跳回指定页面。

这段逻辑看着简单,但坑也在这里。如果你只是销毁了本地 Shiro 会话,不跳 CAS 的 logout 接口,其他应用在用户下一次访问时还会带着 CAS 的 TGT 自动登录,给用户的感受就是“退出了个寂寞”。反过来,如果你每次都跳全局退出,但有一个应用的回调地址配置了错误域名,CAS 在跳转时会报错或者卡住。所以我这里处理登出的控制层通常会写成两段式:先销毁本地会话,再 302 到 CAS 的统一退出地址,同时带上一个完整可回跳的 service 参数。

5. 踩坑实录:重定向、Cookie、返回值三大拦路虎

5.1 重定向死循环:浏览器反复横跳怎么定位

整个接入过程中遇到最多的问题就是“页面一直跳转,最后浏览器提示重定向次数过多”。大部分原因集中在三个地方:第一个是 CAS Server 回调的业务地址写在了受保护范围内,回调地址本身又触发登录跳转,形成死循环;解决方法是把回调地址anon放行。第二个是业务应用拿到的 Service Ticket 已经被消费过一次,第二次用同一个 Ticket 再去校验,CAS Server 直接返回失败,系统又把用户打回登录页;这个经常出现在刷新回调页面的场景,所以我的习惯是校验完 Ticket 后立刻重定向到一个清理掉 ticket 参数的内部地址,别让票据留在浏览器地址栏。第三个是 Nginx 层的跳转协议和 CAS Server 配置的协议不一致,HTTP 和 HTTPS 之间来回跳,导致每次都拿不到预期的回调地址;这种问题最坑,排查时先抓包看 Location 地址,基本一眼就能看出来。

5.2 Cookie 作用域和同名 Cookie 冲突

多应用共存的另一个经典问题是 Cookie 同名导致会话互相干扰。常规 Java Web 容器默认生成的 Session Cookie 叫JSESSIONID。如果应用 A 和应用 B 都在同一个主域下、部署在不同路径,浏览器里可能会同时存在不同路径范围的同名 Cookie。某些请求会把 A 的 Cookie 带到 B,B 解析 Session ID 失败,又把用户当成未登录处理,表现就是登录状态时有时无。

这种问题我在生产里处理过几次,最直接的方案是给不同应用设置不同的会话 Cookie。Shiro 的会话 Cookie 名称是JSESSIONID,可以在DefaultWebSessionManager里换掉,比如改成APP_A_SESSION、APP_B_SESSION。从根源上规避同名冲突,比调整 Cookie 作用域要省心得多。

5.3 接口被重定向到登录页,Ajax 调用方一脸懵

如果业务系统后端有大量接口返回 JSON,而前端是 Ajax 调用,登录失效场景下有个很烦的行为:默认的 Shiro 过滤器发现未登录,会把请求重定向到登录页。前端拿到的是 302 和一段 HTML,而不是预期的{code:401},页面逻辑直接混乱,用户看到的可能是跳回登录页,也可能是整个页面的内容被塞进某个 div。

解决办法是自定义一个AuthenticationFilter子类,覆盖未登录场景的响应逻辑。判断请求头里的Accept或X-Requested-With,如果是 Ajax 请求直接返回 401 JSON;否则才走默认重定向。这样前后端接口和页面跳转就能各走各的通道,不会再互相干扰。

public class JsonAuthFilter extends FormAuthenticationFilter { @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { return true; } HttpServletRequest req = (HttpServletRequest) request; if (req.getHeader("X-Requested-With") != null || req.getHeader("Accept") != null && req.getHeader("Accept").contains("application/json")) { HttpServletResponse resp = (HttpServletResponse) response; resp.setStatus(401); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().write("{\"code\":401,\"message\":\"session timeout\"}"); return false; } return super.onAccessDenied(request, response); } }

5.4 常见问题速查表

现象可能原因处理方式
重定向次数过多回调地址受保护 / Ticket 被重复消费 / 协议跳转不一致放行回调地址、校验后清理 ticket、对齐协议配置
登录成功但接口仍 401Ajax 接口被默认重定向拦截用自定义 Filter 针对 JSON 请求返回 401
权限改了不生效授权缓存未清除权限变更后删 Shiro 授权缓存
多节点登录态丢失Session 存在单节点内存接 RedisSessionDAO
退出后其他应用还能访问没走 CAS 全局退出跳 CAS logout 接口
CAS 回调 404应用没配置回调接口或路径不匹配检查 service 参数和回调接口

这张表的每一条我都在项目里真实遇到过,而且大部分问题在 demo 环境根本暴露不出来,只有部署到多应用、多节点环境才会显现。

6. 性能与安全加固,最后再做一遍收口

6.1 认证请求放大问题与票据校验的短期缓存

单点登录的代价是每一次进入新应用都要向 CAS Server 发起一次票据校验请求。用户量大的时候,CAS Server 的票据校验接口会成为一个热点,尤其是所有应用共享同一个中心节点时,压力会被成倍放大。一个用户从访问 A 到访问 B、再到访问 C,每个应用的登录请求都要打一次校验接口,这还不算用户多开浏览器标签的情况。

我的优化思路有两个层面。第一层是业务应用本地做好 Session 缓存,用户完成一次登录后,后续请求不会每次都回 CAS;第二层是给 CAS Server 的票据校验接口加短期结果缓存,比如一分钟内同一个 Ticket 的校验结果可以直接复用。但这里必须注意一个边界:Ticket 是一次性的,CAS 服务端在正式协议里是允许消费一次,如果缓存时间过长,回调时同一个 Ticket 带过来两次,就会和本地逻辑冲突。所以缓存只适合做“拒绝结果”或“短时间重复查询”的快速返回,不能把“已消费”状态当成永久结果。我的建议是控制在一分钟以内,既缓解压力,又不影响协议行为。

6.2 授权缓存的安全性不能和会话完全绑定

授权缓存虽然好用,但有一个安全边界要想清楚:缓存的数据是“用户当前拥有的权限”,如果用户被禁用、被移除角色、被改权限,缓存必须同步失效。如果权限缓存和登录会话绑定在同一个过期时间,那用户被禁用后最坏情况下,只有会话过期才会触发重新查询,这中间的时间窗口是不可接受的。

我在系统里加了一个很小的机制:用户表维护一个权限版本号或者状态字段,每次登录时把版本号带进会话,授权缓存 Key 里包含版本号。权限变更时版本号递增,旧版本的授权缓存自然作废,新请求会重新查库。这比单纯删 key 更可靠,因为它天然覆盖了多节点、多缓存副本的问题,也方便排查缓存的时效性。

6.3 一套生产可用的核心配置参考

把前面若干点合起来,我贴一段在生产环境验证过的配置参考。这里不展开每个类,只给关键骨架,你按自己的包路径替换即可:

@Configuration public class ShiroConfig { @Bean public Realm realm() { // 自定义或使用 CasRealm,核心是实现认证和授权信息获取 CustomCasRealm realm = new CustomCasRealm(); realm.setCachingEnabled(true); realm.setAuthorizationCachingEnabled(true); realm.setAuthorizationCacheName("authorizationCache"); return realm; } @Bean public DefaultWebSessionManager sessionManager(RedisSessionDAO sessionDAO) { DefaultWebSessionManager manager = new DefaultWebSessionManager(); manager.setSessionDAO(sessionDAO); manager.setGlobalSessionTimeout(1800000L); manager.setSessionIdCookieEnabled(true); manager.setSessionIdCookie(new SimpleCookie("APP_SESSION_ID")); return manager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, CasFilter casFilter, JsonAuthFilter jsonAuthFilter) { ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); // loginUrl 指向 CAS 登录入口,service 为当前应用完整回调地址 factory.setLoginUrl("https://cas.example.com/login?service=https://app.example.com/callback"); Map<String, Filter> filters = new HashMap<>(); filters.put("cas", casFilter); filters.put("jsonAuth", jsonAuthFilter); factory.setFilters(filters); Map<String, String> chain = new LinkedHashMap<>(); chain.put("/", "anon"); chain.put("/callback", "anon"); chain.put("/static/**", "anon"); chain.put("/logout", "logout"); chain.put("/**", "jsonAuth"); factory.setFilterChainDefinitionMap(chain); return factory; } }

注意这里logout过滤器我直接用了 Shiro 内置的 logout,它会先销毁会话,然后按配置跳到指定的登出地址,这个地址要指向 CAS 的全局退出接口。

7. 把这个方案真正落到项目里的一些思考

7.1 最终的系统分工与部署节奏

整套方案稳定之后,部署结构应该是清晰的:一台(或一组)CAS Server 作为认证中心,若干业务应用作为 Shiro 客户端,一个 Redis 集群承载业务应用的会话和授权缓存,一个用户权限库为 Realm 提供查询数据。团队协作上,认证中心和业务应用可以分头维护,只要保证回调地址、票据校验规则、权限缓存清理策略这些接口契约一致就行。

我建议改造节奏也按这个分层走:先让单个业务应用接好 CAS,跑通认证;再引入 Redis 会话解决多节点问题;接着做授权缓存和动态清理;最后再接入第二个应用做跨应用验证。每一步验证完再往下走,排查问题的范围会小很多,不会出现“一堆配置同时改完,出问题了不知道怪谁”的情况。

7.2 方案取舍的底线:CAS 管认证,Shiro 管授权

做这套方案最忌讳的是职责混在一起。CAS 只负责“你是不是合法用户”,Shiro 只负责“这个用户能不能访问这个资源”。有人会把业务系统的权限判断逻辑塞进 CAS Server,想做成一个万能中心,最后发现 CAS Server 改动频繁,认证中心变得异常脆弱,所有接入它的应用全部受影响。正确的边界是 CAS Server 保持精简,授权逻辑留在业务应用侧;不同业务应用完全可以选择不同的授权粒度,认证中心不需要理解业务流程。这套组合适合老系统改造、需要保护既有 URL 权限规则、团队对 Spring 生态和过滤器机制比较熟悉的场景。如果是全新系统并且没有历史包袱,那么 OIDC 或者 JWT 方案也值得考虑,但那不是本系列的出发点。

7.3 后续扩展方向

如果这个体系还要继续往前走,最值得扩展的是三个方向。第一个是登录方式矩阵化:CAS Server 本身可以扩展支持扫码登录、短信验证码登录、企业微信或钉钉这类第三方身份源,业务应用不需要改动,认证中心替你把多种登录方式统一掉。第二个是授权模型升级:目前以 URL 和注解控制为主,规模大了之后可以往数据权限、对象权限模型演进,比如某个用户只能看自己部门的数据,这已经不属于 Shiro 拦截器要解决的问题,而是业务查询层需要统一的权限过滤器。第三个是网关层整合:如果系统前面已经有统一网关,可以考虑把 Shiro 的会话校验前置到网关,后端业务只信任经过网关校验的请求头,这样每个业务应用不用再各自维护一套过滤逻辑。

我个人的体会是,这个系列做到这里,实际上已经不是一个单纯的“接入教程”了,更像是一套完整的安全体系改造纪实。中间最折磨人的部分不是 Shiro 或 CAS 的 API 调用方式,而是本地会话、全局会话、缓存失效、统一退出这些状态同步问题。最后再分享一个非常实用的小技巧:联调阶段一定要在普通窗口、无痕窗口、不同浏览器之间反复切换测试,只有把登录态在多窗口下的表现看清楚了,你才能真正理解 CAS 的 TGT 和业务应用 Session 之间的交互边界,很多莫名奇妙的问题到最后都会聚合到同一个结论上——状态放哪了,谁负责清理,有没有做到全局一致。把这几个问题想透,这套 springboot + shiro + cas 的体系才算真正长在你脑子里了。

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

ARMv8-M TrustZone 双世界工程从零实现:SAU配置与安全调用指南

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

作者头像 李华
网站建设 2026/10/17 5:48:07

基于MP2642GR-Z与MK24FN256VDC12的储能电池主动均衡系统设计与实现

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

作者头像 李华
网站建设 2026/10/17 5:47:40

Python爬虫实战:网易云音乐数据采集与结构化存储

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

作者头像 李华
网站建设 2026/10/17 5:47:25

音频事件检测全链路实战:从特征工程到工程部署

你有没有过这样的时刻&#xff1a;在嘈杂的车间里突然分辨出某台设备的“不对劲”声音&#xff0c;在安静的家中听到一声清脆的玻璃破碎声&#xff0c;或者在走廊尽头听到一阵异常的急促脚步&#xff1f;声音里的信息远比我们想象中丰富&#xff0c;而音频事件检测就是让机器学…

作者头像 李华
网站建设 2026/10/17 5:46:38

AI辅助编程新手避坑:从应援APP开发总结的实战经验

看到这个标题&#xff0c;你可能有点意外&#xff1a;追星和AI开发&#xff0c;怎么凑到一起了&#xff1f;但恰恰是这种看起来“业余”的项目&#xff0c;最容易让新手把AI辅助开发这条路走明白。我最早接触AI写代码&#xff0c;不是在公司项目里&#xff0c;而是因为想给喜欢…

作者头像 李华
网站建设 2026/10/17 5:46:09

流程可视化追踪:BPMN高亮映射与引擎数据组装实战

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

作者头像 李华