java每日精进 5.06【框架之功能权限】
0.概述
0.1 整体架构概述
这个RBAC权限系统基于Spring Security和Token认证机制,主要包含以下核心组件:
-
用户-角色-菜单的多对多关系模型
-
基于Token的认证流程
-
细粒度的权限控制(菜单权限、按钮权限)
-
灵活的权限配置方式
1.用户-角色-菜单的多对多关系模型功能权限
1.1功能权限5张表设计
system_user 表
意义:用于存储系统中用户的基本信息,是系统识别和管理用户的基础。它涵盖了用户身份验证、个人信息、组织归属、操作记录等多方面信息,是用户在系统内的数字化档案。在用户登录、权限分配、数据访问控制等业务流程中,都依赖此表中的数据。
#system_user 表
id:用户唯一标识,用于区分不同用户记录,方便在系统中进行数据关联、检索、更新等操作 。
username:用户登录名,是用户登录系统的凭证之一 。
password:用户登录密码,经过加密存储,用于验证用户身份 。
nickname:用户昵称,可用于展示在系统前端,方便用户识别和称呼 。
remark:备注信息,用于记录关于该用户的额外说明,如特殊权限原因、岗位描述等 。
dept_id:用户所属部门 ID,关联部门表,用于确定用户所在组织架构位置,方便进行部门级权限控制和数据统计 。
post_ids:用户所担任岗位 ID 集合(可能以逗号分隔等形式存储多个岗位 ID ),用于岗位相关权限和职责分配 。
email:用户电子邮箱,可用于找回密码、接收系统通知等 。
mobile:用户手机号码,用于身份验证(如短信验证码登录、找回密码 )、接收通知等。
sex:用户性别标识,用于统计、个性化展示等 。
avatar:用户头像地址,用于前端展示用户形象 。
status:用户状态(如启用、禁用 ),控制用户能否登录和使用系统功能。
login_ip:用户最后一次登录的 IP 地址,用于安全审计,追踪登录来源 。
login_date:用户最后一次登录时间,用于分析用户登录频率、活跃情况等 。
creator:创建该用户记录的操作人 ,用于记录操作日志和追溯创建来源。
create_time:用户记录创建时间,用于数据审计和分析 。
updater:最后更新该用户记录的操作人 ,便于记录操作历史。
update_time:用户记录最后更新时间,用于版本管理和数据变更追溯 。
deleted:逻辑删除标识(0 表示未删除,1 表示已删除 ),方便数据归档和恢复,不真正从数据库删除记录。
tenant_id:多租户标识,在多租户系统中,用于区分不同租户下的用户数据 。
system_role 表
意义:主要用于定义系统中的角色。角色是权限的集合载体,通过设置不同的角色,如管理员、普通用户、审核员等,可将一组相关权限赋予该角色。便于对用户进行批量的权限管理,提高权限分配和管理的效率,是权限控制体系中的关键组成部分。
#system_role 表
id:角色唯一标识,用于在系统中区分不同角色,进行权限关联等操作 。
name:角色名称,方便用户识别角色用途,如 “管理员”“普通用户” 等 。
code:角色编码,可用于程序内部逻辑判断和权限控制代码中,作为角色的唯一代码标识 。
sort:角色排序字段,用于前端展示角色列表时的排序 。
data_scope:数据权限范围(如全部数据、本部门数据、自定义数据范围 ),控制角色能访问的数据范围 。
data_scope_dept_ids:当 data_scope 为自定义数据范围时,存储可访问部门 ID 集合 。
status:角色状态(启用、禁用 ),决定角色是否生效,影响关联用户的权限 。
type:角色类型(如系统角色、自定义角色 ),用于区分角色性质 。
remark:关于角色的备注信息,如角色职责说明等 。
creator:创建该角色记录的操作人 ,用于操作追溯。
create_time:角色记录创建时间,用于审计和分析 。
updater:最后更新该角色记录的操作人 ,记录操作历史。
update_time:角色记录最后更新时间,用于数据版本管理 。
deleted:逻辑删除标识(0 表示未删除,1 表示已删除 ),方便数据管理和恢复 。
tenant_id:多租户标识,区分不同租户下的角色数据 。
system_user_role 表
意义:作为用户和角色的关联表,用于建立用户与角色之间的多对多关系。一个用户可以拥有多个角色(如既具有普通用户角色,又具有审核角色 ),一个角色也可以被多个用户拥有。该表让系统能够明确每个用户所具备的角色,进而确定其相应权限。
#system_user_role 表
id:表记录唯一标识 。
user_id:关联用户表的用户 ID,用于建立用户和角色的关联关系 。
role_id:关联角色表的角色 ID,用于建立用户和角色的关联关系 。
creator:创建该关联记录的操作人 ,记录操作来源。
create_time:关联记录创建时间,用于审计 。
updater:最后更新该关联记录的操作人 ,记录操作历史。
update_time:关联记录最后更新时间,用于数据变更追溯 。
deleted:逻辑删除标识(0 表示未删除,1 表示已删除 ),方便管理关联关系 。
tenant_id:多租户标识,区分不同租户下的用户 - 角色关联数据 。
system_menu 表
意义:用于管理系统中的菜单信息。它定义了系统的功能结构和导航体系,前端通过读取该表数据展示菜单,用户通过菜单访问系统功能。同时,结合权限标识等字段,可实现对菜单访问权限的控制,确保不同权限的用户看到和能操作的菜单功能不同。
# system_menu 表
id:菜单唯一标识,用于区分不同菜单记录,进行菜单管理和权限关联 。
name:菜单名称,用于前端展示和用户识别 。
permission:菜单对应的权限标识,用于在代码中判断用户是否拥有访问该菜单功能的权限 。
menu_type:标识菜单权限操作权限(如目录、菜单、按钮 ),用于更细粒度的权限控制 。
sort:菜单排序字段,用于前端菜单展示的顺序排列 。
parent_id:父菜单 ID,用于构建菜单树结构,确定菜单层级关系 。
path:菜单访问路径,用于前端路由跳转和后端接口访问 。
icon:菜单图标,用于前端展示菜单样式 。
component:菜单对应的前端组件路径,用于前端渲染菜单对应的页面或功能模块 。
status:菜单状态(启用、禁用 ),控制菜单是否在前端展示和可访问 。
creator:创建该菜单记录的操作人 ,记录操作来源。
create_time:菜单记录创建时间,用于审计 。
updater:最后更新该菜单记录的操作人 ,记录操作历史。
update_time:菜单记录最后更新时间,用于数据变更追溯 。
deleted:逻辑删除标识(0 表示未删除,1 表示已删除 ),方便菜单数据管理 。
tenant_id:多租户标识,区分不同租户下的菜单数据 。
system_role_menu 表
意义:是角色和菜单的关联表,用于建立角色与菜单之间的多对多关系。它决定了不同角色能够访问哪些菜单功能,即通过此表将角色与具体的菜单权限进行绑定,从而实现基于角色的菜单访问控制,是实现系统权限管理中菜单权限分配的关键表 。
# system_role_menu 表
id:表记录唯一标识 。
role_id:关联角色表的角色 ID,用于建立角色和菜单的关联关系 。
menu_id:关联菜单表的菜单 ID,用于建立角色和菜单的关联关系 。
creator:创建该关联记录的操作人 ,记录操作来源。
create_time:关联记录创建时间,用于审计 。
updater:最后更新该关联记录的操作人 ,记录操作历史。
update_time:关联记录最后更新时间,用于数据变更追溯 。
deleted:逻辑删除标识(0 表示未删除,1 表示已删除 ),方便管理角色 - 菜单关联关系 。
tenant_id:多租户标识,区分不同租户下的角色 - 菜单关联数据 。
2.基于Token的认证流程
2.1使用账号密码登录,返回对应的token
2.1.1校验验证码
@Overridepublic AuthLoginRespVO login(AuthLoginReqVO reqVO) {// 校验验证码validateCaptcha(reqVO);// 使用账号密码,进行登录AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());// 如果 socialType 非空,说明需要绑定社交用户(什么方法登录的时候传socialtype)if (reqVO.getSocialType() != null) {socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));}// 创建 Token 令牌,记录登录日志return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);}
@VisibleForTestingvoid validateCaptcha(AuthLoginReqVO reqVO) {ResponseModel response = doValidateCaptcha(reqVO);// 校验验证码if (!response.isSuccess()) {// 创建登录失败日志(验证码不正确)createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());}}private ResponseModel doValidateCaptcha(CaptchaVerificationReqVO reqVO) {// 如果验证码关闭,则不进行校验if (!captchaEnable) {return ResponseModel.success();}ValidationUtils.validate(validator, reqVO, CaptchaVerificationReqVO.CodeEnableGroup.class);CaptchaVO captchaVO = new CaptchaVO();captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());return captchaService.verification(captchaVO);}
ValidationUtils.validate() 方法参数
ValidationUtils.validate(validator, reqVO, CaptchaVerificationReqVO.CodeEnableGroup.class);
validator
参数
作用:执行实际校验工作的
Validator
实例来源:通常通过
Validation.buildDefaultValidatorFactory().getValidator()
获取职责:
解析对象上的校验注解
执行对应的校验逻辑
收集校验结果
reqVO
参数
作用:需要被校验的对象实例
类型:应该是
CaptchaVerificationReqVO
或其它包含校验注解的DTO对象特点:
该对象类上应该定义了各种校验注解(如
@NotBlank
,@Size
等)可能包含分组标记的校验注解
CaptchaVerificationReqVO.CodeEnableGroup.class
参数
作用:校验分组标记
意义:
只校验属于
CodeEnableGroup
分组的约束忽略不属于该分组的其他约束
2.1.2使用账号密码,进行登录
@Overridepublic AdminUserDO authenticate(String username, String password) {final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;// 校验账号是否存在AdminUserDO user = userService.getUserByUsername(username);if (user == null) {createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);throw exception(AUTH_LOGIN_BAD_CREDENTIALS);}if (!userService.isPasswordMatch(password, user.getPassword())) {createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);throw exception(AUTH_LOGIN_BAD_CREDENTIALS);}// 校验是否禁用if (CommonStatusEnum.isDisable(user.getStatus())) {createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);throw exception(AUTH_LOGIN_USER_DISABLED);}return user;}
根据是否使用社交帐号登录判断是否将系统用户和社交帐户进行绑定解绑等操作;
@Override@Transactional(rollbackFor = Exception.class)public String bindSocialUser(SocialUserBindReqDTO reqDTO) {// 获得社交用户SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(),reqDTO.getCode(), reqDTO.getState());Assert.notNull(socialUser, "社交用户不能为空");// 社交用户可能之前绑定过别的用户,需要进行解绑(例:微信绑定过其他账号)socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId());// 用户可能之前已经绑定过该社交类型,需要进行解绑(例:本账号之前绑定过其他微信账号)socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(),socialUser.getType());// 绑定当前登录的社交用户SocialUserBindDO socialUserBind = SocialUserBindDO.builder().userId(reqDTO.getUserId()).userType(reqDTO.getUserType()).socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();socialUserBindMapper.insert(socialUserBind);return socialUser.getOpenid();}
案例 场景描述 关键操作 结果
1 首次绑定 直接创建新绑定 成功绑定
2 更换同类型社交账号 先删除旧绑定,再创建新绑定 绑定关系更新
3 社交账号已被其他用户绑定 先解除他人的绑定,再创建新绑定 原用户需重新绑定
4 绑定不同类型社交账号 各自独立绑定 多社交账号共存
5 绑定过程出错 事务回滚 无变更
2.1.3创建 Token 令牌,记录登录日志
private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {// 插入登陆日志createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);// 创建访问令牌OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);// 构建返回结果return AuthConvert.INSTANCE.convert(accessTokenDO);}
插入日志
private void createLoginLog(Long userId, String username,LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {// 插入登录日志LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();reqDTO.setLogType(logTypeEnum.getType());reqDTO.setTraceId(TracerUtils.getTraceId());reqDTO.setUserId(userId);reqDTO.setUserType(getUserType().getValue());reqDTO.setUsername(username);reqDTO.setUserAgent(ServletUtils.getUserAgent());reqDTO.setUserIp(ServletUtils.getClientIP());reqDTO.setResult(loginResult.getResult());loginLogService.createLoginLog(reqDTO);// 更新最后登录时间if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {userService.updateUserLogin(userId, ServletUtils.getClientIP());}}
创建令牌
@Override@Transactional(rollbackFor = Exception.class)public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) {OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);// 创建刷新令牌OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes);// 创建访问令牌return createOAuth2AccessToken(refreshTokenDO, clientDO);}
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes) {OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()).setUserId(userId).setUserType(userType).setClientId(clientDO.getClientId()).setScopes(scopes).setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds()));oauth2RefreshTokenMapper.insert(refreshToken);return refreshToken;}
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()).setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())).setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()).setRefreshToken(refreshTokenDO.getRefreshToken()).setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号oauth2AccessTokenMapper.insert(accessTokenDO);// 记录到 Redis 中oauth2AccessTokenRedisDAO.set(accessTokenDO);return accessTokenDO;}
2.2使用得到的token进行前端请求及鉴权
2.2.1得到token并构建对应的用户
每次请求 → 提取 token → 验证 token 合法性 → 获取用户信息 → 注入 Spring Security 上下文 → 请求继续执行
@SuppressWarnings("NullableProblems")protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {String token = SecurityFrameworkUtils.obtainAuthorization(request,securityProperties.getTokenHeader(), securityProperties.getTokenParameter());if (StrUtil.isNotEmpty(token)) {Integer userType = WebFrameworkUtils.getLoginUserType(request);try {// 1.1 基于 token 构建登录用户LoginUser loginUser = buildLoginUserByToken(token, userType);// 2. 设置当前用户if (loginUser != null) {SecurityFrameworkUtils.setLoginUser(loginUser, request);}} catch (Throwable ex) {CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);ServletUtils.writeJSON(response, result);return;}}// 继续过滤链chain.doFilter(request, response);}
根据token创建登录用户
private LoginUser buildLoginUserByToken(String token, Integer userType) {try {OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);if (accessToken == null) {return null;}// 用户类型不匹配,无权限// 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型// 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的if (userType != null&& ObjectUtil.notEqual(accessToken.getUserType(), userType)) {throw new AccessDeniedException("错误的用户类型");}// 构建登录用户return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()).setInfo(accessToken.getUserInfo()) // 额外的用户信息.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()).setExpiresTime(accessToken.getExpiresTime());} catch (ServiceException serviceException) {// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可return null;}}
2.2.2接口请求鉴权过程
@PostMapping("/create")@Operation(summary = "新增用户")@PreAuthorize("@ss.hasPermission('system:user:create')")public CommonResult<Long> createUser(@Valid @RequestBody UserSaveReqVO reqVO) {Long id = userService.createUser(reqVO);return success(id);}
@Overridepublic boolean hasAnyPermissions(Long userId, String... permissions) {// 如果为空,说明已经有权限(全权限)if (ArrayUtil.isEmpty(permissions)) {return true;}// 获得当前登录的角色。如果为空,说明没有权限List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);if (CollUtil.isEmpty(roles)) {return false;}// 情况一:遍历判断每个权限,如果有一满足,说明有权限for (String permission : permissions) {if (hasAnyPermission(roles, permission)) {return true;}}// 情况二:如果是超管,也说明有权限return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));}
/*** 获得用户拥有的角色,并且这些角色是开启状态的** @param userId 用户编号* @return 用户拥有的角色*/@VisibleForTestingList<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {// 获得用户拥有的角色编号Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);// 获得角色数组,并移除被禁用的List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));return roles;}
当 @PreAuthorize
注解里的 Spring EL 表达式返回 false
时,表示没有权限。
而 @PreAuthorize("@ss.hasPermission('system:user:list')")
表示调用 Bean 名字为 ss
的 #hasPermission(...)
方法,方法参数为 "system:user:list"
字符串。
3.自定义权限配置
默认配置下,所有接口都需要登录后才能访问,不限于管理后台的 /admin-api/**
所有 API 接口、用户 App 的 /app-api/**
所有 API 接口。
如下想要自定义权限配置,设置定义 API 接口可以匿名(不登录)进行访问,可以通过下面三种方式:
3.1自定义 AuthorizeRequestsCustomizer 实现
WebSecurityConfigurerAdapter 是一个 Spring Security 的配置类,用于定义权限认证规则和安全配置。它通过自定义 SecurityFilterChain 来设置 URL 访问权限、Token 认证、跨域支持等功能。
1️⃣WebSecurityConfigurerAdapter
🔧 功能定位:Spring Security 的主配置类
它定义了:
-
全局安全策略(如禁用 Session、启用 Token 认证等)
-
配置认证过滤器链
-
定义通用的权限放行规则(如静态资源、@PermitAll 注解)
-
统一收集所有模块的自定义权限规则(这是与第二个类关联的关键点)
🌐 属于“框架层配置”。
/*** 自定义的 Spring Security 配置适配器实现*/
@AutoConfiguration
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
@EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfigurerAdapter {@Resourceprivate WebProperties webProperties;@Resourceprivate SecurityProperties securityProperties;/*** 认证失败处理类 Bean*/@Resourceprivate AuthenticationEntryPoint authenticationEntryPoint;/*** 权限不够处理器 Bean*/@Resourceprivate AccessDeniedHandler accessDeniedHandler;/*** Token 认证过滤器 Bean*/@Resourceprivate TokenAuthenticationFilter authenticationTokenFilter;/*** 自定义的权限映射 Bean 们** @see #filterChain(HttpSecurity)*/@Resourceprivate List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;@Resourceprivate ApplicationContext applicationContext;/*** 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入* 通过覆写父类的该方法,添加 @Bean 注解,解决该问题*/@Beanpublic AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}/*** 配置 URL 的安全配置** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole | 如果有参数,参数表示角色,则其角色可以访问* permitAll | 用户可以任意访问* rememberMe | 允许通过remember-me登录的用户访问* authenticated | 用户登录后可访问*/@Beanprotected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {// 登出httpSecurity// 开启跨域.cors(Customizer.withDefaults())// CSRF 禁用,因为不使用 Session.csrf(AbstractHttpConfigurer::disable)// 基于 token 机制,所以不需要 Session.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))// 一堆自定义的 Spring Security 处理器.exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler));// 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高// 获得 @PermitAll 带来的 URL 列表,免登录Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();// 设置每个请求的权限httpSecurity// ①:全局共享规则.authorizeHttpRequests(c -> c// 1.1 静态资源,可匿名访问.requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll()// 1.2 设置 @PermitAll 无需认证.requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll().requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll().requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll().requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll().requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll().requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()// 1.3 基于 moyun.security.permit-all-urls 无需认证.requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll())// ②:每个项目的自定义规则.authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))// ③:兜底规则,必须认证.authorizeHttpRequests(c -> c.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景.anyRequest().authenticated());// 添加 Token FilterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();}private String buildAppApi(String url) {return webProperties.getAppApi().getPrefix() + url;}private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {Multimap<HttpMethod, String> result = HashMultimap.create();// 获得接口对应的 HandlerMethod 集合RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)applicationContext.getBean("requestMappingHandlerMapping");Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();// 获得有 @PermitAll 注解的接口for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {HandlerMethod handlerMethod = entry.getValue();if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {continue;}Set<String> urls = new HashSet<>();if (entry.getKey().getPatternsCondition() != null) {urls.addAll(entry.getKey().getPatternsCondition().getPatterns());}if (entry.getKey().getPathPatternsCondition() != null) {urls.addAll(convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));}if (urls.isEmpty()) {continue;}// 特殊:使用 @RequestMapping 注解,并且未写 method 属性,此时认为都需要免登录Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();if (CollUtil.isEmpty(methods)) {result.putAll(HttpMethod.GET, urls);result.putAll(HttpMethod.POST, urls);result.putAll(HttpMethod.PUT, urls);result.putAll(HttpMethod.DELETE, urls);result.putAll(HttpMethod.HEAD, urls);result.putAll(HttpMethod.PATCH, urls);continue;}// 根据请求方法,添加到 result 结果entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {switch (requestMethod) {case GET:result.putAll(HttpMethod.GET, urls);break;case POST:result.putAll(HttpMethod.POST, urls);break;case PUT:result.putAll(HttpMethod.PUT, urls);break;case DELETE:result.putAll(HttpMethod.DELETE, urls);break;case HEAD:result.putAll(HttpMethod.HEAD, urls);break;case PATCH:result.putAll(HttpMethod.PATCH, urls);break;}});}return result;}
}
filterChain
- 功能:
配置 Spring Security 的核心过滤链,定义了 URL 访问规则、跨域支持、CSRF 配置、Token 认证等。- 参数:
- HttpSecurity httpSecurity:Spring Security 提供的配置对象,用于设置安全策略。
- 返回值:
- SecurityFilterChain:Spring Security 的过滤链,定义了所有安全规则。
- 详细步骤:
- 跨域支持:.cors(Customizer.withDefaults()) 开启 CORS 支持。
- CSRF 禁用:.csrf(AbstractHttpConfigurer::disable) 因为使用 Token 认证,不需要 CSRF 保护。
- 无状态会话:.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 使用 Token 认证,不需要 Session。
- Frame 选项:.headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) 禁用 Frame 保护,允许 iframe 嵌入。
- 异常处理:设置认证失败和权限不足的处理器。
- URL 权限规则:
- 静态资源(/*.html, /*.css, /*.js)允许匿名访问。
- 通过 getPermitAllUrlsFromAnnotations() 获取 @PermitAll 注解的 URL,允许匿名访问。
- 使用 securityProperties.getPermitAllUrls() 获取配置文件的 permit-all-urls,允许匿名访问。
- 使用 authorizeRequestsCustomizers 应用自定义规则,遍历 authorizeRequestsCustomizers 列表,依次调用每个 AuthorizeRequestsCustomizer 实例的 customize 方法。
- 异步请求(DispatcherType.ASYNC)允许匿名访问(用于 SSE 场景)。
- 其他所有请求(anyRequest())需要认证。
- 添加 Token 过滤器:在 UsernamePasswordAuthenticationFilter 之前添加 authenticationTokenFilter。
注:
每个 Maven Module 可以定义自己的 AuthorizeRequestsCustomizer 实现类,作为 Spring Bean 注入到容器中。例如,yudao-module-infra 模块可以定义一个 InfraAuthorizeRequestsCustomizer,yudao-module-system 模块可以定义 SystemAuthorizeRequestsCustomizer。
在 WebSecurityConfigurerAdapter 的 filterChain 方法中,通过 @Resource private List authorizeRequestsCustomizers收集所有 AuthorizeRequestsCustomizer Bean。
核心代码(在 filterChain 方法中):
.authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
功能:遍历 authorizeRequestsCustomizers 列表,依次调用每个 AuthorizeRequestsCustomizer 实例的 customize 方法。
参数:
c 是 AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry 类型,提供了 requestMatchers 等方法,用于定义 URL 匹配和权限规则。
实现:
每个 AuthorizeRequestsCustomizer 实现类通过 customize 方法添加模块特定的规则。例如,允许某些 URL 匿名访问或需要特定权限。
动态匹配:
customize 方法是抽象的,子类必须实现它。子类可以通过 requestMatchers 方法指定 URL 模式(如 /admin-api/**)和权限策略(如 permitAll()、authenticated())。
2️⃣ SecurityConfiguration
🔧 功能定位:某个模块(infra)的权限自定义扩展配置
它只做一件事:
-
提供一个名为
infraAuthorizeRequestsCustomizer
的AuthorizeRequestsCustomizer
Bean,用于告诉主配置类:有哪些路径可以免认证访问(比如 Swagger、Actuator、Druid、文件接口等)。
🧩 属于“模块层扩展”。
/*** Infra 模块的 Security 配置*/
@Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration")
public class SecurityConfiguration {@Value("${spring.boot.admin.context-path:''}")private String adminSeverContextPath;@Bean("infraAuthorizeRequestsCustomizer")public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {return new AuthorizeRequestsCustomizer() {@Overridepublic void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {// Swagger 接口文档registry.requestMatchers("/v3/api-docs/**").permitAll().requestMatchers("/webjars/**").permitAll().requestMatchers("/swagger-ui.html").permitAll().requestMatchers("/swagger-ui/**").permitAll();// Spring Boot Actuator 的安全配置registry.requestMatchers("/actuator").permitAll().requestMatchers("/actuator/**").permitAll();// Druid 监控registry.requestMatchers("/druid/**").permitAll();// Spring Boot Admin Server 的安全配置registry.requestMatchers(adminSeverContextPath).permitAll().requestMatchers(adminSeverContextPath + "/**").permitAll();// 文件读取registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll();}};}}
全过程
- 应用过程:
- Spring 容器启动时,扫描到 InfraAuthorizeRequestsCustomizer 和 SystemAuthorizeRequestsCustomizer,注入到 authorizeRequestsCustomizers 列表。
- 在 filterChain 方法中,依次调用:
- InfraAuthorizeRequestsCustomizer.customize()(优先级 1),设置 /admin-api/infra/file/*/get/** 为匿名访问。
- SystemAuthorizeRequestsCustomizer.customize()(优先级 2),设置 /admin-api/system/user/** 需 ADMIN 角色。
- 最终生成的 SecurityFilterChain 包含所有模块的规则,按优先级顺序应用。
- URL 匹配:
使用 requestMatchers 方法支持 Ant 风格路径(如 /admin-api/**)或正则表达式,Spring Security 会根据请求的 URL 和 HTTP 方法与规则进行匹配。 - 权限检查:
匹配成功后,Spring Security 会根据 permitAll()、authenticated() 或 hasRole() 等策略决定是否允许访问。 - 冲突处理:
如果多个规则匹配同一 URL,后定义的规则会覆盖前面的规则(基于优先级和顺序)。
总结:
+-----------------------------------------------------------+
| WebSecurityConfigurerAdapter |
| |
| - 构建 HttpSecurity 策略 |
| - 允许静态资源、@PermitAll、全局配置路径免认证 |
| - 遍历调用: |
| authorizeRequestsCustomizers.forEach(customizer) |
| |
+-----------------------------------------------------------+↑|Spring 自动装配的 AuthorizeRequestsCustomizer Bean|
+-----------------------------------------------------------+
| SecurityConfiguration (infra模块) |
| |
| @Bean -> infraAuthorizeRequestsCustomizer |
| - 配置 swagger、actuator、druid 等路径 permitAll |
+-----------------------------------------------------------+
这种设计体现了 分层解耦 + 开放扩展性 的原则:
-
🧩 分模块配置权限:每个模块只需要注册自己的
AuthorizeRequestsCustomizer
即可,而不用动核心安全逻辑。 -
🔧 核心逻辑集中管理:
WebSecurityConfigurerAdapter
统一处理认证、异常处理、Token 过滤器等关键配置,避免散乱。 -
🧹 避免权限配置集中臃肿:不需要在主配置类里手动维护一堆
registry.requestMatchers(...)
。
对比点 WebSecurityConfigurerAdapter
SecurityConfiguration
作用层级 框架级(全局) 模块级(局部) 主要功能 配置 HttpSecurity、认证逻辑、处理器、Token 过滤器等 添加模块内自定义免认证 URL 使用方式 被 Spring 自动加载 注册 AuthorizeRequestsCustomizer
Bean与对方关系 调用并执行所有 AuthorizeRequestsCustomizer
被调用者,提供自定义配置 是否推荐改动 一般不要动,改动需了解安全机制 可自由扩展,适合业务模块使用
3.2@PermitAll
注解
在 API 接口上添加 @PermitAll 注解,示例如下:
// FileController.java
@GetMapping("/{configId}/get/{path}")
@PermitAll
public void getFileContent(HttpServletResponse response,@PathVariable("configId") Long configId,@PathVariable("path") String path) throws Exception {// ...
}
3.3yudao.security.permit-all-urls
配置项
在 application.yaml
配置文件,通过 yudao.security.permit-all-urls
配置项设置,示例如下:
yudao:security:permit-all-urls:- /admin-ui/** # /resources/admin-ui 目录下的静态资源- /admin-api/xxx/yyy
相关文章:
java每日精进 5.06【框架之功能权限】
0.概述 0.1 整体架构概述 这个RBAC权限系统基于Spring Security和Token认证机制,主要包含以下核心组件: 用户-角色-菜单的多对多关系模型 基于Token的认证流程 细粒度的权限控制(菜单权限、按钮权限) 灵活的权限配置方式 1…...
静态NAT
实验需求 PC1和PC2通过静态NAT去访问服务器 实验拓扑 图13-1 静态NAT 实验步骤 步骤1:IP地址的配置 PC1的配置 PC2的配置 R1的配置 <Huawei>system-view [Huawei]undo info-center enable [Huawei]sysname R1 [R1]interface g0/0/0 [R1-GigabitEt…...
RabbitMQ-api开发
前言 MQ就是接收并转发消息 核心概念 admin是用户 每个虚拟机上都有多个交换机 快速入门 引入依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.22.0</version></dependen…...
const MachineTree getMachineTree() ; MachineTree getMachineTree() const; 区别?
这两个函数声明在语法和语义上有明显的区别,它们的用途和行为也不同。让我们逐一分析它们的区别: 1. const MachineTree &getMachineTree(); 这个函数声明表示: 返回类型:const MachineTree &,即返回一个 M…...
使用DevTools工具调试前端页面,便捷脚本,鸿蒙调试webView
参考官方文章 便捷脚本 创建文本,复制修改后缀为bat 建立bat文件 echo off setlocal enabledelayedexpansion:: Initialize port number and PID list set PORT9222 set PID_LIST:: Get the list of all forwarded ports and PIDs for /f "tokens2,5 delims…...
浏览器存储 Cookie,Local Storage和Session Storage
什么是Cookie? 存储容量:一般限制在 4KB 以内。数据有效期:可以设置过期时间,若未设置,则在浏览器关闭时失效。数据共享:在同一域名下,不同页面可以共享cookie数据。并且在每次 HTTP 请求时&am…...
校内周赛题(思维题)
这次周赛的题目没有什么很难的代码实现,基本上都是对思路的把握。 与君共勉🌹 选取x个数,看能不能使得这x个数相加的结果是奇数。 如果x是偶数,他的分布肯定是一个奇数一个偶数若干个两两配对的奇数若干个两两配对的偶数。 如果…...
在 GitLab 中部署Python定时任务
在 GitLab 中部署定时任务(如每天早8点运行Python脚本并存储结果)可以通过 GitLab CI/CD 结合 计划任务(Scheduled Pipelines) 实现。以下是详细步骤和准备工作: 1. 准备工作 (1) 项目结构准备 确保项目包含: Python脚本(如 main.py):执行核心算法逻辑。 结果存储模…...
学习黑客Windows权限体系
已思考 24 秒 借着 Week 2 Day 4 的号角,我们把权限系统这条「双持长枪」摆上擂台:一边是 Windows DACL/icacls,另一边是 Linux sudoers。你将看到二者在授权语法、常见配置漏洞与提权打法上的一一对照——尤其关注 可写服务(Wr…...
DXFViewer进行中2 -> 直线 解析+渲染 ✅已完成
DXFViewer进行中 : ->封装OpenGL -> 解析DXF直线-CSDN博客https://blog.csdn.net/qq_25547755/article/details/147723906 上篇博文 解析dxf直线635条 1. DXFViewer.h #pragma once #include "Application.h" #include <stdio.h> #inc…...
当智能科技遇上医疗行业会帮助疫苗如何方便管理呢?
随着科技的发展,智能科技在医疗行业的应用日益广泛,在疫苗管理方面,温湿度监控设备的安装可以简化管理流程,提高监测效率,降低疫苗损坏的风险。 疫苗管理面临着诸多挑战和需求。疫苗的存储、运输、接种等环节都…...
Excel 数据 可视化 + 自动化!Excel 对比软件
各位Excel小能手们!你们有没有过要对比两个Excel表格数据差异,却看得眼睛都花了的经历?其实啊,现在有专门的Excel文件比较软件能帮咱解决这大难题。这软件就是用来快速找出两个或多个Excel表格数据不同之处,还能把修改…...
Selenium模拟人类行为,操作网页的方法(全)
看到有朋友评论问,用selenium怎么模仿人类行为,去操作网页的页面呢? 我想了想,这确实是一个很大的点,不应该是一段代码能解决的, 就像是,如果让程序模拟人类的行为。例如模拟人类买菜,做饭&am…...
LVS负载均衡群集解析:理解LVS-NAT的工作原理
目录 一、LVS群集应用基础 1.群集技术概述 2.LVS虚拟服务器 3.NFS共享存储服务 二、案例:地址转换模式(LVS-NAT) 1.资源清单 2.修改主机名 3.配置负载调度器(LVS上) 4.配置节点服务器(web1、web2) 5.测试LVS…...
Leetcode Hot 100最长连续序列
题目描述 思路 思路1 我们对数组进行排序,通过遍历数组,如果前一个数组的值1等于当前数组的值,计数count,如果中断了,计算当前最大连续长度的值ans,并且统计值count重新置为1,最后返回count与…...
【东枫科技】代理英伟达产品:交换机系统
文章目录 总体详细:NVIDIA Spectrum SN5000详细:NVIDIA Spectrum SN2100详细:NVIDIA Spectrum SN4700详细:NVIDIA Spectrum SN2010详细:NVIDIA Spectrum SN4600详细:NVIDIA Spectrum SN3700详细:…...
[前端]Javascript获取元素宽度
元素宽度属性对比示意图 ---------------------------------- | 外边距(margin) | -------------------------------- | | 边框(border) | | | -------------------------- | | | …...
MySQL + Qwen3-0.5B + Flask + Dify 工作流部署指南
1. 安装MySQL和PyMySQL 安装MySQL # 在Ubuntu/Debian上安装 sudo apt update sudo apt install mysql-server sudo mysql_secure_installation# 启动MySQL服务 sudo systemctl start mysql sudo systemctl enable mysql 安装PyMySQL pip install pymysql 使用 apt 安装 My…...
项目三 - 任务3:学生多态方式喂养宠物
在本次实战中,我们通过创建动物类及其子类(猫、狗、鸟),并设计学生类的喂养方法,深入学习了Java中的多态和方法重载。学生类通过重载方式为每种动物提供专门的喂养方法,而通过多态方式则仅用一个方法即可喂…...
TypeScript速成
1、类型推断 这里的str已经推断为string类型,不能像JavaScript一样直接给str赋值number类型 2、类型注解 let str:stringabc 或者 let str:string strabc 3、类型断言 因为typescript会判定这个item可能为undefined类型,但是我们人为可以确定item一…...
CompletableFuture的底层ForkJoinPool
什么是 ForkJoinPool?它和普通线程池(ThreadPoolExecutor)有什么区别? 答案要点: ForkJoinPool 是 Java 7 引入的线程池,专为 分治任务 设计,支持递归任务拆分(Fork)和…...
高等数学第五章---定积分(§5.1定积分的概念、性质和应用)
5.1 定积分的概念及性质 一、引例 在学习定积分之前,我们先通过两个具体的例子来感受其思想和方法。 例1 曲边梯形的面积 定义:曲边梯形 由连续曲线 y f ( x ) y f(x) yf(x)(假设 f ( x ) ≥ 0 f(x) \ge 0 f(x)≥0 在所讨论的区间上&…...
Java基础学完,继续深耕(0506)SQL--多表关系
多表关系 一对多(多对一) 一对一 多对多 一对多 场景:部门与员工的关系 (一个部门下有多个员工)。 实现:在数据库表中多的一方,添加字段,来关联一的一方的主键。 现在只是在语法上关联了,…...
python打卡day17
聚类的基础知识 知识点 聚类的指标聚类常见算法:kmeans聚类、dbscan聚类、层次聚类三种算法对应的流程 实际在论文中聚类的策略不一定是针对所有特征,可以针对其中几个可以解释的特征进行聚类,得到聚类后的类别,这样后续进行解释也…...
洛谷---P1629 邮递员送信
题目描述 有一个邮递员要送东西,邮局在节点 1。他总共要送 n−1 样东西,其目的地分别是节点 2 到节点 n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m 条道路。这个邮递员每次只能带一样东西,并且运…...
第11次:用户注册(简要版)
1、定义模板 在templates文件夹下边新建register.html,代码如下: <html lang"en"> <head><meta charset"UTF-8"><title>注册</title> </head> <body><!--{%是模板标签, …...
【IP101】图像特征提取技术:从传统方法到深度学习的完整指南
🌟 特征提取魔法指南 🎨 在图像处理的世界里,特征提取就像是寻找图像的"指纹",让我们能够识别和理解图像的独特性。让我们一起来探索这些神奇的特征提取术吧! 📚 目录 基础概念 - 特征的"体…...
对windows的简单介绍
目录 一、Windows 操作系统概述 1. 定义与定位 2. 核心目标 二、历史与版本演变 1. 早期阶段(1985–1995) 2. NT 内核时代(1996–2009) 3. 现代操作系统(2012–至今) 三、系统架构与技术特性 1. 内…...
Waymo公司正在加快其位于亚利桑那州新工厂的无人驾驶出租车(robotaxi)生产进度
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
JavaSE核心知识点01基础语法01-03(流程控制:顺序、分支、循环)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 JavaSE核心知识点01基础语法01-03࿰…...
信息论01:从通信到理论的飞跃
信息论01:从通信到理论的飞跃 1. 信息论的诞生与发展 1.1 前信息论时代(1920s之前) 信息与消息的混淆:传统认知中将信息等同于消息本身先驱者奠基: 哈里奈奎斯特 (1924):提出《影响电报速度的某些因素》…...
Pandas 的透视与逆透视
目录 1. 透视 1.1 pivot 1.2 pivot_table 2.逆透视 1. 透视 透视是长表变宽表。 pivot() 和 pivot_table()两个函数都可以做到,后者可以聚合前者不行。 特性df.pivot()df.pivot_table()重复值处理要求索引和列的组合唯一,否则报错 ValueError允许…...
AI大模型驱动的智能座舱研发体系重构
随着AI大模型(如LLM、多模态模型)的快速发展,传统智能座舱研发流程面临巨大挑战。传统座舱研发以需求驱动、功能固定、架构封闭为特点,而AI大模型的引入使得座舱系统向自主决策、动态适应、持续进化的方向发展。 因此思考并提出一…...
【东枫科技】代理英伟达产品:DPU
NVIDIA BlueField-3 DPU 400Gb/s 基础设施计算平台 NVIDIA BlueField -3 数据处理单元 (DPU) 是第三代基础设施计算平台,使企业能够构建从云端到核心数据中心再到边缘的软件定义、硬件加速的 IT 基础设施。借助 400Gb/s 以太网或 NDR 400Gb/s InfiniBand 网络连接…...
【KWDB 创作者计划】一文掌握KWDB的时序表管理
前言 本文是在对时序数据库有一定了解,并且KWDB的数据库操作了解后进行学习的文章安排,如果对时序数据库与KWDB的数据库操作还不怎么了解的可以查阅官网的文档进行提前学习,当有了这些基础后,本文就是对时序数据表的一个管理操作…...
《算法导论(第4版)》阅读笔记:p9-p9
《算法导论(第4版)》学习第 6 天,p9-p9 总结,总计 1 页。 一、技术总结 1. data structure A data structure is a way to store and organize data in order to facilitate access and modifications(数据结构是一种存储和组织数据的方式,…...
Facebook隐私保护措施的优缺点解析
在这个数字化的时代,隐私保护已成为公众关注的热点话题。Facebook,作为全球最大的社交媒体平台之一,其隐私保护措施自然也受到了广泛的关注和讨论。本文将对Facebook的隐私保护措施进行解析,探讨其优点与缺点,并探讨如…...
深入了解linux系统—— 进程地址空间
前言 程序地址空间 在之前,我们学习C/C时,多多少少都看过这样的一张图 我们现在通过下面这一段代码看一下: #include <stdio.h> #include <unistd.h> #include <stdlib.h> int g_unval; int g_val 100; int main(int…...
电动加长杆金属硬密封法兰式蝶阀泄漏等级解析:水、蒸汽、油品介质的可靠选择-耀圣
电动加长杆金属硬密封法兰式蝶阀泄漏等级解析:水、蒸汽、油品介质的可靠选择 在工业流体控制领域,电动金属硬密封蝶阀凭借其卓越的密封性能和耐高温高压特性,成为水、蒸汽、油品等介质的核心控制设备。其泄漏等级作为衡量阀门性能的关键指标…...
win11共享打印机主机设置
1.首先打开设置,选择打印机和扫描仪点击打印机属性,将共享窗口的共享这台打印机和在客户端计算机上呈现打印作业这两项勾选上。 2.通过cmd命令 gpedit.msc 打开本地组策略编辑器。 3.网络访问选择仅来宾 4.将账户来宾状态选择启用 5.将拒绝从网络访问这台…...
Flowable7.x学习笔记(二十)查看流程办理进度图
前言 本文是基于继承Flowable的【DefaultProcessDiagramCanvas】和【DefaultProcessDiagramGenerator】实现的自定义流程图生成器,通过自定义流程图生成器可以灵活的指定已经执行过的节点和当前正在活跃的节点样式,比如说已经执行完成的节点我们标绿&…...
【计算机网络 第8版】谢希仁编著 第四章网络层 地址类题型总结
小结 个人觉得地址类在网络层算好做的题,这部分知识本身并不多,理解到位了就是2进制和10进制的换算题了。而且这种题给你一小时例题和标答,肯定自己都能悟出来。但是计网网络层的整体我感觉很散,老师讲的也乱七八糟的,…...
一种基于条件生成对抗网络(cGAN)的CT重建算法
简介 简介:该文提出了一种基于条件生成对抗网络(cGAN)的CT重建算法,通过引入CBAM注意力机制增强网络对关键特征的提取能力,有效解决了CT成像中因噪声干扰导致的重建精度下降问题。实验采用固体火箭发动机模拟件数据集,将正弦图分为五组并添加不同程度的噪声进行训练。结…...
欧拉系统(openEuler)上部署OpenStack的完整指南 ——基于Yoga版本的全流程实践
(资源区里有上传的配置好的openstack镜像) 一、环境规划与前置准备 1. 硬件与节点规划升级 存储节点(可选):若需Cinder后端,建议配置SSDHDD混合存储网络拓扑强化: 管理网络:启用V…...
oceanbase不兼容SqlSugarCore的问题
问题发现 C#程序使用SqlSugarCore5.1.4.166进行数据库操作,而且项目需要在多台服务器上面部署,结果发现A服务器部署运行没有问题, B服务器部署却报错:SqlSugar.SqlSugarException:Connect timeout expired. 但是我们的C#代码是一…...
深入理解分布式锁——以Redis为例
一、分布式锁简介 1、什么是分布式锁 分布式锁是一种在分布式系统环境下,通过多个节点对共享资源进行访问控制的一种同步机制。它的主要目的是防止多个节点同时操作同一份数据,从而避免数据的不一致性。 线程锁: 也被称为互斥锁(…...
OrangePi Zero 3学习笔记(Android篇)1 - 搭建环境
目录 1. 下载安装Ubuntu22.04 1.1 安装增强功能 1.2 设置共享文件夹 1.3 创建AOSP.vdi 1.4 更新相关软件包 2. 解压AOSP源代码 3. 编译代码 3.1 编译uboot/Linux 3.2 编译AOSP源代码 3.3 内存问题调试记录 3.3.1 查看具体什么问题 3.3.2 关闭dex2oat(无…...
RabbitMq(尚硅谷)
RabbitMq 1.RabbitMq异步调用 2.work模型 3.Fanout交换机(广播模式) 4.Diret交换机(直连) 5.Topic交换机(主题交换机,通过路由匹配) 6.Headers交换机(头交换机) 6…...
OpenAI的“四面楚歌”:从营利到非营利,一场关于AGI控制权的革命
引言 当“奥特曼妥协”与“四面楚歌”并置时,OpenAI的这次重大调整,仿佛在科技史上投下一颗震撼弹。这家曾因“拒绝盈利”而备受争议的人工智能公司,如今却在资本与理想之间艰难抉择——放弃营利性转型,回归非营利初心。这不仅是对…...
[250505] Arch Linux 正式登陆 Linux 的 Windows 子系统
目录 Arch Linux 正式登陆 Windows Subsystem for Linux (WSL) Arch Linux 正式登陆 Windows Subsystem for Linux (WSL) Arch Linux 社区与 Microsoft 合作,正式宣布 Arch Linux 现已提供官方的 Windows Subsystem for Linux (WSL) 镜像。这意味着 Windows 用户现…...