OAuth2.0+JWT:服务端与客户端授权闭环实现详解

简介:这套源码面向需要在Java环境里落地OAuth 2.0授权服务的开发者,以Maven和Apache OLTU库为基础,完整实现授权服务器与资源服务器,并把JWT作为访问令牌,覆盖授权码模式、令牌刷新、资源访问等核心流程。压缩包共221个文件,以176个Java源码为主,另有22个XML配置及依赖清单、少量脚本,整体仅355KB,目录结构紧凑,便于导入工程直接阅读。已有2184人学习浏览。源码包含授权服务器配置、资源服务器配置、认证提供者、访问令牌生成器等关键类,从授权端点、令牌端点到刷新令牌端点均有明确实现,并附客户端请求工具及测试代码,可对照理解角色交互和令牌生命周期。项目对密码字段采用MD5处理,适合中高级开发者参考其安全取舍;结合JWT的头部、负载与签名结构,也能为无状态鉴权设计提供直接示例。

1. OAuth2.0 + JWT:一套代码跑通的授权服务端与客户端闭环

很多项目只教授权服务器怎么写,客户端却在文档里一句“请参考第三方SDK”带过。但这套源码把服务端与客户端都实现完整:从登录跳转、授权码回调、令牌换取,到携带 JWT 访问资源接口,整条链路在一个工程里就能跑起来,调试时能同时看到两端的日志,这是它区别于一般 Demo 的最大价值。

项目适合两类人:一类是被 OAuth2.0 流程绕晕的入门者,可以直接把请求在两端之间的传递过程看一遍;另一类是想在自研系统里落地单点登录授权的后端开发,需要一个能改的基座而不是一堆碎片配置。接下来我会从工程拆分一路讲到二次开发,按真实动手顺序走一遍。

2. 工程拆分与依赖选型:server 与 client 各管什么

2.1 两个模块的职责边界

拿到源码后先不用急着看代码,打开工程根目录就能摸清整体布局。工程按职责分成三个 Maven 模块,目录结构大概是:

oauth2-jwt-demo ├── oauth-server │ ├── src/main/java │ │ └── com/example/oauthserver │ │ ├── config/AuthorizationServerConfig.java │ │ ├── config/ResourceServerConfig.java │ │ ├── controller/UserController.java │ │ └── service/UserService.java │ └── src/main/resources/application.yml ├── oauth-client │ ├── src/main/java │ │ └── com/example/oauthclient │ │ ├── config/OAuth2ClientConfig.java │ │ ├── controller/CallbackController.java │ │ └── interceptor/BearerTokenInterceptor.java │ └── src/main/resources/application.yml └── pom.xml

模块划分的意义在于调试时日志不会混在一起。我一般会先把两个应用分别启动,什么都不配,先看默认端口有没有冲突。服务端建议用 8080,客户端用 8081,这是最常见的本地联调组合。如果你的压缩包没有写明端口,直接看 application.yml 里的server.port即可。

2.2 依赖清单里值得留心的点

服务端和客户端的功能边界从依赖上也能看出来。服务端模块在 Spring Boot 2.x 下通常会这样声明:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.5.2.RELEASE</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> </dependencies>

第一处注意点是spring-security-oauth2的版本。老项目常用的 2.3.x 版本在 Spring Security 5.4 之后会出现兼容问题,一般我会直接锁 2.5.2.RELEASE,它在当前 Boot 2.7 下表现稳定。

第二处是 JJWT 分成三个包,api 只在编译期出现,impl 和 jackson 走 runtime,这是为了避免底层实现和 JSON 序列化包被重复打包。客户端模块的依赖轻很多,它不需要完整的服务端能力,只需引入spring-boot-starter-web和spring-boot-starter-security,再配合spring-security-oauth2中的客户端类即可,后面会讲到具体写法。

2.3 数据表设计:一张 client_details 就够起步

授权服务端涉及的数据量并不大,项目里通常只需要两张表:一张oauth_client_details存客户端注册信息,一张sys_user存登录用户。下面以客户端表为例,表结构如下:

CREATE TABLE oauth_client_details ( client_id VARCHAR(64) PRIMARY KEY, client_secret VARCHAR(128) NOT NULL, resource_ids VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), access_token_validity INTEGER DEFAULT 7200, refresh_token_validity INTEGER DEFAULT 2592000 );

字段里最关键的是authorized_grant_types。项目演示的授权码模式要求该字段至少包含authorization_code和refresh_token;如果只写了implicit,后面请求令牌时会出现unsupported_grant_type异常。另外web_server_redirect_uri必须和客户端模块里的回调地址完全一致,端口差一个都会跳转失败。

服务端如果配置的是本地 MySQL,启动时可以通过初始化 SQL 建表。想省事也可以临时换成 H2 内存库,把连接串改成jdbc:h2:mem:testdb,但注意重启后客户端注册数据会丢失,每次都要重新执行初始化脚本。

3. 服务端核心链路:授权码到 JWT 的生产全过程

3.1 五段式配置:AuthorizationServerConfigurerAdapter

服务端模块的心脏是AuthorizationServerConfig,它继承AuthorizationServerConfigurerAdapter。这个适配器提供五个扩展点,项目里按功能拆开写:

@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("demo-sign-key-change-me"); return converter; } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource) .passwordEncoder(new BCryptPasswordEncoder()); } @Override public void configure(AuthorizationServerSecurityConfigurer security) { security.tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(accessTokenConverter()) .tokenEnhancer(customTokenEnhancer()); } }

先看ClientDetailsServiceConfigurer:clients.jdbc(dataSource)告诉框架从oauth_client_details表加载客户端,密码用 BCrypt 加密。数据库里的client_secret存的是 BCrypt 密文,不是明文,这是很多第一次跑通 OAuth 的人也会踩的坑——拿明文 secret 去请求令牌,一直报invalid_client。

再看AuthorizationServerEndpointsConfigurer:authenticationManager必须传,授权码模式下用户登录后的认证结果需要它做二次确认;userDetailsService则是刷新令牌时重新加载用户信息的入口。tokenStore用的是 JwtTokenStore,它本身不存数据,只把 JWT 字符串解析回OAuth2AccessToken对象,真正的状态都在令牌里,所以服务端不需要统一保存会话。

tokenEnhancer挂的是自定义增强器,后面会具体展开。这里要提醒的是:如果你同时配置了accessTokenConverter和自定义TokenEnhancer,两者的调用顺序由 Spring 组装链决定,增强器先运行,再交给转换器签名;反过来写会拿不到增强器塞进去的附加参数。

3.2 授权码的一生:从 authorize 到 callback

授权码模式的完整调用链是:客户端把用户引导到/oauth/authorize?response_type=code&client_id=demo-client&redirect_uri=http://localhost:8081/callback&scope=read,服务端先弹登录页,登录成功后生成授权码并回跳到回调地址。

授权码生成过程在 Spring Security OAuth2 里已被封装,代码路径在AuthorizationEndpoint。为了看清它,我一般会给TokenEndpoint加一个日志过滤器,打印每次请求的参数:

@Component public class OAuthRequestLogFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request.getRequestURI().contains("/oauth/")) { String params = request.getParameterMap().entrySet().stream() .map(e -> e.getKey() + "=" + String.join(",", e.getValue())) .collect(Collectors.joining("&")); logger.info("OAuth request -> " + request.getRequestURI() + "?" + params); } filterChain.doFilter(request, response); } }

这个过滤器没有业务逻辑,但调试时极有用。它能让你看到授权码模式下客户端到底传了几个参数、scope 是什么、有没有把state带上。state参数是防 CSRF 的关键,官方客户端会自动生成,自己实现的客户端必须手动生成并校验。

授权码生成之后默认有效期是 1 分钟,且只能消费一次。服务端通过AuthorizationCodeServices存储授权码,默认基于内存;二次开发如果想做多节点部署,我会把它替换成 Redis 实现,把AuthorizationCodeServices子类改成读取RedisOperations即可。

3.3 JWT 令牌生成:增强器附加声明并交给转换器签名

服务端发令牌的入口是/oauth/token,在授权码模式下拿code换access_token。Spring Security OAuth2 默认生成的 JWT 只包含sub、iat、exp、client_id等标准声明,业务上往往不够用,所以源码里带了一个CustomTokenEnhancer:

public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { final Map<String, Object> additionalInfo = new HashMap<>(); String userName = authentication.getName(); additionalInfo.put("username", userName); additionalInfo.put("source", "oauth-server"); additionalInfo.put("tenant_id", "default"); // 从认证信息中提取用户实体,填充到声明里 if (authentication.getPrincipal() instanceof UserDetails) { UserDetails user = (UserDetails) authentication.getPrincipal(); additionalInfo.put("authorities", user.getAuthorities()); } ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }

这里的关键是把DefaultOAuth2AccessToken的additionalInformation设置到 JWT payload 中,最终由JwtAccessTokenConverter加密签名后输出。你会在令牌串中间那段 payload 里看到username和authorities。

JwtAccessTokenConverter默认使用 HMAC-SHA256 对称签名,setSigningKey设置的字符串就是共享密钥。这个密钥在资源服务器验证签名时还得再用一次,两端必须完全一致。项目里放在application.yml的jwt.signing-key配置下,我一般会建议把它挪到环境变量里,避免写死在代码仓库。

注意:JWT 的 payload 只是 Base64 编码,并非加密,任何拿到密钥的客户端都能直接解码查看声明内容,敏感信息不要往这里塞。

4. 客户端接入实现:回调换令牌与携带令牌访问资源

4.1 客户端的跳转逻辑

客户端模块本质上是另一个 Spring Boot 应用,它把用户重定向到服务端。第一步是配置客户端的基本信息:

@Configuration public class OAuth2ClientConfig { @Bean public AuthorizationCodeResourceDetails resourceDetails() { AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); details.setId("oauth2-client"); details.setClientId("demo-client"); details.setClientSecret("demo-secret"); details.setAccessTokenUri("http://localhost:8080/oauth/token"); details.setUserAuthorizationUri("http://localhost:8080/oauth/authorize"); details.setScope(Arrays.asList("read", "write")); details.setUseCurrentUri(false); details.setRedirectUri("http://localhost:8081/login/oauth2/code/oauth-server"); return details; } }

setUseCurrentUri(false)是容易忽略的点。默认情况下客户端会把当前请求的 URI 作为回调,这在经过网关转发时会出问题;显式设置redirectUri后,无论从哪个入口发起跳转,回调地址都固定且与服务端注册的一致。

4.2 回调接口:授权码换令牌

用户授权完成后,服务端把code拼在重定向 URL 上,回到客户端的回调接口。这里要做两件事:校验state、用code换令牌。

@RestController public class CallbackController { @Autowired private OAuth2RestTemplate oauth2RestTemplate; @GetMapping("/login/oauth2/code/oauth-server") public String callback(@RequestParam("code") String code, @RequestParam("state") String state) { // 校验 state 与发起跳转时保存的值是否一致 if (!"expected-state".equals(state)) { return "state 校验失败"; } // 底层会自动调用 /oauth/token 完成 code -> token 的交换 OAuth2AccessToken token = oauth2RestTemplate.getAccessToken(); return "拿到令牌: " + token.getValue(); } }

OAuth2RestTemplate是 Spring Security OAuth2 提供的封装好的令牌管理类。它内部维护了一个AccessTokenProvider,调用getAccessToken()时如果发现当前没有有效令牌,会自动携带code与redirect_uri去服务端交换。这个类的设计有一个“后悔药”:服务端如果配置了refresh_token授权类型,OAuth2RestTemplate会在令牌过期后自动用刷新令牌续期,业务方无需关心续期逻辑。

4.3 资源服务器验证 JWT

拿到令牌后,客户端要请求服务端受保护接口,此时资源服务器的 JWT 解码就派上用场了。资源服务器配置长这样:

@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.tokenServices(tokenServices()); } @Bean public ResourceServerTokenServices tokenServices() { DefaultTokenServices services = new DefaultTokenServices(); services.setTokenStore(new JwtTokenStore(jwtAccessTokenConverter())); services.setSupportRefreshToken(true); return services; } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("demo-sign-key-change-me"); // 必须与服务端一致 return converter; } }

这里面的JwtAccessTokenConverter使用同一个签名密钥,所以资源服务器不需要远程调用服务端的 check_token 接口,直接本地解析 JWT 签名即完成验证。生产环境更紧实的做法是非对称加密:服务端用私钥签名,资源服务器配置公钥验签,签名密钥不通过网络传输。若要改成非对称方式,替换JwtAccessTokenConverter的setKeyPair配置,并在资源服务器设置对应的公钥文件即可。

请求资源接口时,客户端只需要在请求头带Authorization: Bearer <token>。我给客户端写了一个简单的 RestTemplate 拦截器:

public class BearerTokenInterceptor implements ClientHttpRequestInterceptor { private final Supplier<String> tokenSupplier; public BearerTokenInterceptor(Supplier<String> tokenSupplier) { this.tokenSupplier = tokenSupplier; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { request.getHeaders().setBearerAuth(tokenSupplier.get()); return execution.execute(request, body); } }

注册拦截器后,客户端每次请求后端服务都会自动带上令牌。这里要注意tokenSupplier返回的必须是还在有效期内的值,否则每次都要先通过OAuth2RestTemplate.getAccessToken()主动刷新一次,再把新令牌注入Supplier。

5. 避坑:授权流程中常见的六个翻车现场

5.1 提示 invalid_client 但账号明明是对的

现象:POST /oauth/token返回error="invalid_client",client_id 和 client_secret 核对过没有拼写错误。

原因:数据库里的client_secret存的是 BCrypt 加密后的密文,而 Spring 的ClientDetailsService默认用明文比较。项目的AuthorizationServerConfig里如果漏了passwordEncoder配置,就会一直报这个错。

解决:检查AuthorizationServerConfigurerAdapter.configure(ClientDetailsServiceConfigurer)里有没有指定passwordEncoder。没有就补上:

clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder());

5.2 回调地址对不上导致无限重定向

现象:用户登录成功之后,浏览器一直在授权页和回调页之间来回跳,最终页面报redirect_uri_mismatch。

原因:oauth_client_details.web_server_redirect_uri里配置的是http://localhost:8081/callback,客户端实际回调写的是http://127.0.0.1:8081/callback,Spring 对 URI 做精确匹配,localhost 与 127.0.0.1 视作不同地址。

解决:把两端配置改成完全一致,最好都用域名而不是 IP;同时检查客户端模块setRedirectUri有没有显式设置,未设置时框架会用当前请求的完整地址作为回调,动态拼接的地址很容易触发 mismatch。

5.3 JWT 签名验证报 SignatureException

现象:资源服务器访问受保护接口时抛异常,日志里出现JWT signature does not match locally computed signature。

原因:JwtAccessTokenConverter在服务端用的签名密钥是demo-sign-key-change-me,资源服务器配置成了别的值;或者有一方经过配置中心覆盖了密钥。

解决:把签名密钥抽成环境变量或配置项,服务端与资源服务器引用同一个 key。我一般还会在测试环境固定一个校验逻辑:解码 JWT 头,看alg字段,若为HS256,确认两端共享密钥一致即可。

5.4 刷新令牌返回 null 或提示 unauthorized

现象:access_token 过期后自动刷新,OAuth2RestTemplate抛异常,提示 refresh token 无效。

原因:JWT 模式不保存服务端会话,刷新令牌的续期逻辑依赖 refresh_token 里的client_id和user_name信息。如果客户端注册时的authorized_grant_types缺少refresh_token,换取令牌时服务端根本不会返回刷新令牌。

解决:在oauth_client_details表中为authorized_grant_types补齐authorization_code,refresh_token;并确认refresh_token_validity设置得比 access_token 的有效期长。

5.5 scope 不一致导致 403 而不是 401

现象:令牌明明有效,请求资源接口时返回 403,日志提示Access is denied。

原因:客户端申请的 scope 是read,但资源接口的方法上标注的是#oauth2.hasAnyScope('write'),令牌里的 scope 列表与接口要求不一致,此时 Spring 返回 403。

解决:查看 JWT payload 里的scope字段,确认客户端申请时的 scope。不要只看代码里标注了什么,以令牌实际携带的信息为准。

5.6 Spring Security 版本兼容问题

现象:更换到 Spring Boot 2.7.18 后,@EnableAuthorizationServer报错找不到注解。

原因:Spring 官方从 Boot 2.7 开始将 spring-security-oauth2 标记为 deprecated 并迁移到 spring-authorization-server,旧的AuthorizationServerConfigurerAdapter在新版本中不再自动加载。

解决:有两条路线。留在 Boot 2.7.x 并把依赖锁定在2.5.2.RELEASE,完全不用改动代码;或者升级到 Boot 3.x + spring-authorization-server,那相当于重写授权服务端配置。学习项目用前者足够,生产若想升级则要做好大量适配工作。

6. 二次开发思路:多客户端注册与自定义 JWT 载荷

6.1 多客户端注册与动态密钥加载

项目默认只注册了一个demo-client,真实环境至少要区分内部后台和移动端。多客户端的关键是在oauth_client_details表插入新记录,给每个客户端配独立的 scope 和回调地址。配置已经走 jdbc 动态读取,所以不需要改代码。

若想让不同客户端使用不同签名密钥,需要自定义JwtAccessTokenConverter的SigningKeyResolver,让它根据 JWT 头里的kid字段查询对应密钥。这是把单密钥升级为多密钥的常用手法,也便于后续做密钥轮换。

6.2 自定义声明到资源侧的落地

令牌里的username、authorities、tenant_id在资源侧不应该是黑匣子。我一般做法是写一个ThreadLocal工具类,在接口拦截器里解析 payload 存入ThreadLocal,接口处理完成后再 remove,避免线程池复用造成数据串台。做完这一步,Controller 就可以直接读tenant_id做数据隔离,同一套资源服务同时服务多个租户不再是难事。

6.3 改造后的验证路线

扩展多客户端或自定义声明后,按四步验证:重启服务端,确认/oauth/authorize能正常打开;用新 client_id 走一遍授权码流程,看回调是否成功;随后请求/oauth/token,解码新的 JWT 检查新增声明;最后请求资源接口,确认资源侧能读到新增字段。四步走完,绝大多数改动在十分钟内就能回归。从那以后我做任何 OAuth 改造,都会强制把“解码 JWT 看声明”作为第一步,而不是盲改配置,能省下不少排查时间,希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

YOLOv3口罩检测工程落地:小目标识别与边缘部署鲁棒性实战

YOLOv3口罩检测工程落地:小目标识别与边缘部署鲁棒性实战

1. 这不是“又一个YOLO demo”&#xff0c;而是一次真实场景下的工程落地复盘去年冬天&#xff0c;某高校实验室接了一个社区防疫辅助系统的需求&#xff1a;在老旧监控摄像头画质模糊、光照不均、人员密集穿行的环境下&#xff0c;实时识别未规范佩戴口罩的行为。他们最初找来…

2026/10/20 20:39:12 阅读更多 →
分组交换的本质:不是路由器堆砌,而是机制与权衡

分组交换的本质:不是路由器堆砌,而是机制与权衡

1. 为什么“网络核心”不是路由器堆出来的——从一个被反复误解的章节讲起刚带完一届某高校《计算机网络》实验课&#xff0c;有位A同学在课后追着我问&#xff1a;“老师&#xff0c;第一章1.3节说‘网络核心由分组交换构成’&#xff0c;可我查了三本教材、看了五个视频&…

2026/10/20 20:39:12 阅读更多 →
Android城市选择器开发实战:拼音索引、搜索与定位回填

Android城市选择器开发实战:拼音索引、搜索与定位回填

简介&#xff1a;这是一款面向Android开发者的城市选择器组件&#xff0c;仿照美团城市选择界面设计&#xff0c;帮助开发者快速在电商、旅游、社交或生活服务类应用中集成城市选择功能&#xff0c;适合从初学者到资深工程师的各类开发者使用。资源包共285个文件&#xff0c;包…

2026/10/20 20:39:12 阅读更多 →

最新新闻

AI Agent成本结构全解析:从API调用费到隐性人力的完整账本

AI Agent成本结构全解析:从API调用费到隐性人力的完整账本

先说一个我最近复盘的真实项目。某电商团队上线了一个订单查询与退换货的客服 Agent&#xff0c;第一周看账单&#xff0c;推理费一天几百块&#xff0c;负责人很高兴&#xff0c;觉得这玩意儿比招人划算多了。三个月后做财务复盘&#xff0c;把所有配套成本拉出来一算&#xf…

2026/10/20 21:19:04 阅读更多 →
7825张车身缺陷检测数据集:VOC+YOLO双格式标注与YOLOv5训练实践

7825张车身缺陷检测数据集:VOC+YOLO双格式标注与YOLOv5训练实践

简介&#xff1a;面向车身部件损坏与车体缺陷检测的VOCYOLO双格式数据集&#xff0c;对应7825张车辆图像的缺陷标注&#xff0c;涵盖划痕、凹陷、裂缝、破损、褪色等15类常见问题&#xff0c;可直接用于YOLO系列、Faster R-CNN等目标检测模型的训练与评估&#xff0c;服务汽车制…

2026/10/20 21:19:04 阅读更多 →
OpenClaw基于概念的解释:TCAV原理、实践与工程坑

OpenClaw基于概念的解释:TCAV原理、实践与工程坑

模型解释性最近几年都快被聊烂了&#xff0c;但每次有人跑来问我“OpenClaw 能不能做基于概念的解释”&#xff0c;我还是会一下子精神起来。这个问题问得比“你模型准确率多少”有价值多了&#xff0c;因为它背后真正想说的是&#xff1a;模型做判断的理由&#xff0c;能不能用…

2026/10/20 21:19:04 阅读更多 →
TensorFlow 2 复现 Facenet:MTCNN 对齐与三元组损失实战

TensorFlow 2 复现 Facenet:MTCNN 对齐与三元组损失实战

1. 为什么我最终选了 TensorFlow 2 来复现 Facenet1.1 从一次“翻车”的考勤机项目说起前两年我接了一个小活儿&#xff0c;帮一个做企业内训的朋友搞一套“刷脸签到”的演示系统。需求听起来不复杂&#xff1a;员工站在摄像头前&#xff0c;系统认出是谁&#xff0c;自动打勾。…

2026/10/20 21:19:04 阅读更多 →
上下文模式实战:从设计到落地的架构决策与避坑指南

上下文模式实战:从设计到落地的架构决策与避坑指南

1. 从“上下文模式”说起&#xff1a;一个被低估的架构决策第一次看到“context-mode”这个词&#xff0c;很多人会下意识觉得它是个抽象得没边儿的概念——上下文嘛&#xff0c;不就是程序运行时的环境变量、线程本地存储、或者某个框架里的配置对象&#xff1f;但如果你真的在…

2026/10/20 21:19:04 阅读更多 →
STM32智能台灯毕业设计:从硬件选型到PID调光完整实战

STM32智能台灯毕业设计:从硬件选型到PID调光完整实战

简介&#xff1a;本资源为嵌入式方向本科毕业设计「智能台灯」的完整开发套件&#xff0c;面向电子工程、自动化及物联网相关专业学生&#xff0c;解决智能硬件从原理设计到系统落地的全流程实践需求。压缩包含140个文件&#xff0c;总大小162.69MB&#xff0c;涵盖33张硬件调试…

2026/10/20 21:18:02 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →