【SpringSecurity】基本开发流程
文章目录
- 概要
- 整体架构流程
- 实现流程
- 1、编写各种Handler
- `2 、AccessToken处理器`
- `3、定义AuthenticationFilter 继承 OncePerRequestFilter (OncePerRequestFilter是Spring提供的一个过滤器基类,它确保了在一次完整的HTTP请求中,无论请求经过多少次内部转发,过滤器的逻辑都只会被执行一次。这对于需要在请求处理之前或之后进行一次性设置或清理资源的场景特别有用。)`
- `4、编写SecurityAutoConfiguration 把Handler和AccessTokenManager 注册成bean`
- `5、编写 WebSecurityAutoConfiguration 注册SecurityFilterChain的bean`
- `6 辅助类`
- `6.1 公共配置注册(如何不在采用的地方用@EnableConfigurationProperties注解,可以写在公共配置文件里面)`
- `6.2忽略url写在配置文件`
- `6.3 自定义UserDetailsServiceImpl实现UserDetailsService`
- `6.4 自定义redis操作`
- `6.5 自定义加密解密(依据之前对接过微信支付相关token逻辑进行简单的RSA相关加解密)`
- 小结
概要
security-spring-boot-starter
SpringSecurity参考笔记
整体架构流程
模块代码截图
实现流程
1、编写各种Handler
1.1 AccessDeniedHandler 处理当访问被拒绝时的逻辑
import com.core.web.Web;
import com.core.web.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class myAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {Result<Object> result = Result.fail();if (accessDeniedException != null) {result.setMessage(accessDeniedException.getLocalizedMessage());}Web.Response.json(response, HttpStatus.FORBIDDEN, result);}
}
1.2 AuthenticationEntryPoint 处理未认证逻辑
import com.core.web.Web;
import com.core.web.result.Result;
import com.core.web.result.ResultCode;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class myAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {Result<Object> result = Result.build(ResultCode.UNAUTHORIZED);if (authException != null && !(authException instanceof InsufficientAuthenticationException)) {result.setMessage(authException.getLocalizedMessage());}Web.Response.json(response, HttpStatus.UNAUTHORIZED, result);}
}
1.3 AuthenticationFailureHandler 处理认证失败逻辑
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class myAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException {Web.Response.json(response, Result.fail(exception.getLocalizedMessage()));}
}
1.4 AuthenticationSuccessHandler 处理认证成功逻辑
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class myAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {LoginUser user = (LoginUser) authentication.getPrincipal();AccessTokenManager.create(user);Web.Response.json(response, Result.ok(user));}
}
1.5 LogoutSuccessHandler 处理退出登录逻辑
为了安全起见采用RES对token进行加密,后端拿到数据后进行解密再做进一步的判断
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
@EnableConfigurationProperties(TokenProperties.class) // 如何不写这个注解可以采用写在第五步的 CommonConfig
public class JsonLogoutSuccessHandler implements LogoutSuccessHandler {@Resourceprivate TokenProperties tokenProperties ;@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {// 获取请求参数数据InputStream inputStream = request.getInputStream();//把inputStream 转mapObjectMapper mapper = new ObjectMapper();// 前端采用post 请求/logout,参数为key,value为token加密的密文的json数据Map<String,String> s = mapper.readValue(inputStream, Map.class);// 解密得到tokenString token = AesUtil.decryptToString(s.get("key"));// 获取登录用户信息LoginUser user = AccessTokenManager.getLoginUser(token);if($.isNotNull(user) && !StringUtils.equals(tokenProperties.getScreen(), token)){AccessTokenManager.remove(token);}Web.Response.json(response, Result.ok());}
}
2 、AccessToken处理器
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.InitializingBean;import static com.zyc.core.Constants.Separator.COLON;@RequiredArgsConstructor
public class AccessTokenManager implements InitializingBean {private static AccessTokenManager MANAGER;public static AccessTokenManager getInstance() {return MANAGER;}/*** 过期时间(单位:秒)*/private static final long EXPIRE_IN_SECONDS = 24 * 60 * 60;/*** 访问凭证缓存键*/public static final String TOKEN_CACHE_KEY = "token_to_user" + COLON;/*** 根据访问凭证获取登录用户信息** @param accessToken 访问凭证* @param <T> 用户信息类型* @return 登录用户信息*/public static <T extends LoginUser> T getLoginUser(String accessToken) {T user = RedisCache.get(getInstance().getCacheKey(accessToken));if (user == null) {return null;}refresh(accessToken, user);return user;}public static void remove(String accessToken) {RedisCache.del(getInstance().getCacheKey(accessToken));}public static String create(LoginUser user) {String token = $.id();user.setToken(token);refresh(token, user);return token;}public static void refresh(String accessToken, LoginUser user) {if ($.isBlank(accessToken)) {return;}boolean isSuperAdmin = SecureContextHolder.hasSuperAdmin(user.getPermissions());if (isSuperAdmin) {RedisCache.set(getInstance().getCacheKey(accessToken), user);} else {RedisCache.set(getInstance().getCacheKey(accessToken), user, EXPIRE_IN_SECONDS);}}String getCacheKey(String accessToken) {return TOKEN_CACHE_KEY.concat(accessToken);}@Overridepublic void afterPropertiesSet() throws Exception {MANAGER = this;}
}
3、定义AuthenticationFilter 继承 OncePerRequestFilter (OncePerRequestFilter是Spring提供的一个过滤器基类,它确保了在一次完整的HTTP请求中,无论请求经过多少次内部转发,过滤器的逻辑都只会被执行一次。这对于需要在请求处理之前或之后进行一次性设置或清理资源的场景特别有用。)
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/*** 认证过滤* <p>* 用于从请求信息 {@link HttpServletRequest} 获取访问凭证,获取登录信息,设置到安全认证上下文中;* 以实现登录* </p>* <p>* set before at {@link org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter}* </p>**/
@Slf4j
public class AuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {log.debug("[auth.filter] server_name: {} uri: {}, query: {}",request.getServerName(), request.getRequestURI(), request.getQueryString());Authentication authentication = getAuthentication(request);SecurityContextHolder.getContext().setAuthentication(authentication);Optional.ofNullable(authentication).ifPresent(auth -> {List<String> authorities = auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());ContextHolder.set(SUPER_ADMIN, SecureContextHolder.hasSuperAdmin(authorities));});//TODO 对参数进行一些过滤校验/*ParameterRequestWrapper paramRequest = new ParameterRequestWrapper(request);chain.doFilter(paramRequest, response);*/chain.doFilter(request, response);}/*** 获取认证信息** @param request 请求信息* @return 认证信息*/private Authentication getAuthentication(HttpServletRequest request) {String accessToken = obtainAccessToken(request);log.debug("[auth.filter] obtain access_token: {}", accessToken);if ($.isBlank(accessToken)) {return null;}LoginUser login = AccessTokenManager.getLoginUser(accessToken);return Optional.ofNullable(login).map(user -> new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities())).orElse(null);}/*** 获取访问凭证** @param request 请求信息* @return 访问凭证*/private String obtainAccessToken(HttpServletRequest request) {String token = request.getHeader(TOKEN_HEAD);return Optional.ofNullable(token).orElseGet(() -> request.getParameter(TOKEN_PARAM));}
}
4、编写SecurityAutoConfiguration 把Handler和AccessTokenManager 注册成bean
import com.security.servlet.AccessTokenManager;
import com.security.servlet.handler.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;@EnableConfigurationProperties(SecurityProperties.class) // 如何不写这个注解可以采用写在第五步的 CommonConfig
@Configuration(proxyBeanMethods = false)
@Import(WebSecurityAutoConfiguration.class)
public class SecurityAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic AccessTokenManager accessTokenManager() {return new AccessTokenManager();}@Bean@ConditionalOnMissingBeanpublic AuthenticationEntryPoint authenticationEntryPoint() {return new JsonAuthenticationEntryPoint();}@Bean@ConditionalOnMissingBeanpublic AccessDeniedHandler accessDeniedHandler() {return new JsonAccessDeniedHandler();}@Bean@ConditionalOnMissingBeanpublic AuthenticationFailureHandler authenticationFailureHandler() {return new JsonAuthenticationFailureHandler();}@Bean@ConditionalOnMissingBeanpublic AuthenticationSuccessHandler authenticationSuccessHandler() {return new JsonAuthenticationSuccessHandler();}@Bean@ConditionalOnMissingBeanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@Bean@ConditionalOnMissingBeanpublic LogoutSuccessHandler logoutSuccessHandler() {return new JsonLogoutSuccessHandler();}
}
5、编写 WebSecurityAutoConfiguration 注册SecurityFilterChain的bean
import com.security.configurers.AuthorizeRequestsCustomizer;
import com.security.servlet.filter.AuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET
)
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class WebSecurityAutoConfiguration {/*** 默认静态资源常量数组*/private static final String[] DEFAULT_STATIC_RESOURCES = new String[]{"/favicon.ico","/**/*.css","/**/*.js","/doc.html","/swagger-ui/**","/swagger-ui.html","/swagger-ui/index.html","/swagger-resources/**","/v3/api-docs","/v3/api-docs/**"};private final SecurityProperties securityProperties;private final AuthenticationSuccessHandler authenticationSuccessHandler;private final AuthenticationFailureHandler authenticationFailureHandler;private final AuthenticationEntryPoint authenticationEntryPoint;private final AccessDeniedHandler accessDeniedHandler;private final LogoutSuccessHandler loggingSuccessHandler;private final List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.formLogin(form -> form.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler))// CSRF 禁用,因为不使用 Session.csrf(AbstractHttpConfigurer::disable).sessionManagement(sessionManagement ->sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 异常处理.exceptionHandling(exceptionHandling ->exceptionHandling.accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint))// 忽略认证.authorizeRequests(auth -> auth.antMatchers(DEFAULT_STATIC_RESOURCES).permitAll().antMatchers($.toArray(securityProperties.getIgnoreUrls())).permitAll())// 兜底,必须通过认证才能访问.authorizeRequests().anyRequest().authenticated().and()// 前置通过凭证获取登录信息.addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)//退出逻辑.logout().logoutSuccessHandler(loggingSuccessHandler);return http.build();}AntPathRequestMatcher[] convertRequestMatcher(Stream<String> stream) {return stream.map(AntPathRequestMatcher::new).collect(Collectors.toList()).toArray(new AntPathRequestMatcher[]{});}
}
6 辅助类
6.1 公共配置注册(如何不在采用的地方用@EnableConfigurationProperties注解,可以写在公共配置文件里面)
import com..autoconfigure.ConfigProperties;
import com.boot.autoconfigure.security.TokenProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;@EnableConfigurationProperties({SecurityProperties.class,TokenProperties.class
})
@Configuration
public class CommonConfig {
}
6.2忽略url写在配置文件
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.ArrayList;
import java.util.List;@Data
@ConfigurationProperties("cfg.security")
public class SecurityProperties {/*** 忽略 url*/private List<String> ignoreUrls = new ArrayList<>();
}
特定token配置在配置文件
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "ems.token")
public class TokenProperties {private String screen;
}
自定义LoginUser 继承UserDetails
import cn.hutool.core.map.MapUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;import java.time.LocalDate;
import java.util.Collection;
import java.util.Map;
import java.util.Set;@Data
@Accessors(chain = true)
public class LoginUser implements UserDetails, CredentialsContainer {/*** 主键标识*/private String id;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 昵称*/private String nickname;/*** 权限集合*/private Set<String> permissions;/*** 访问凭证*/private String token;/*** 扩展信息*/private Map<String, Object> extra;/*** 上下文信息*/private Map<String, Object> context;/*** 机构标识*/private String orgId;/*** 机构类型*/private Integer orgType;/*** 机构名称*/private String orgName;/*** 有效期*/private LocalDate validityPeriod;@JsonIgnore@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return AuthorityUtils.createAuthorityList($.toArray(permissions, String.class));}@JsonIgnore@Overridepublic String getPassword() {return password;}@JsonIgnore@Overridepublic boolean isAccountNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isAccountNonLocked() {return true;}@JsonIgnore@Overridepublic boolean isCredentialsNonExpired() {return true;}@JsonIgnore@Overridepublic boolean isEnabled() {return true;}@Overridepublic void eraseCredentials() {this.password = null;}
}
6.3 自定义UserDetailsServiceImpl实现UserDetailsService
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {private final OperatorService operatorService;private final RoleService roleService;private final MenuService menuService;private final ProjectService projectService;private final OrgService orgService;@Overridepublic UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {Operator operator = operatorService.lambdaQuery().eq(Operator::getUsername, username).oneOpt().orElseThrow(() -> new UsernameNotFoundException("用户名或者密码错误"));List<Role> roles = roleService.listByOperator(operator.getId(), operator.getOrgId());boolean isSuperAdmin = roles.stream().anyMatch(role -> $.equals(role.getId(), Constants.Default.SUPER_ADMIN));Set<String> roleIds = roles.stream().map(Role::getId).collect(Collectors.toSet());// 菜单权限Set<String> permissions = menuService.listByRole(isSuperAdmin, $.toArray(roleIds, String.class)).stream().map(Menu::getPermission).filter(Kit::isNotBlank).collect(Collectors.toSet());permissions.addAll(roleIds.stream().map(Constants.Default.ROLE_PREFIX::concat).collect(Collectors.toList()));// 项目权限Set<String> projectIds = projectService.listByRole(operator.getOrgId(), isSuperAdmin, roles);permissions.addAll(projectIds.stream().filter(Kit::isNotBlank).map(Values.PROJECT_PREFIX::concat).collect(Collectors.toList()));String password = Constants.Security.DEFAULT_ENCRYPTION_SIGNATURE.concat(operator.getPassword());Org org = orgService.getById(operator.getOrgId());//如果机构设置了有效期,则有效期过后不能登录LocalDate now = LocalDate.now();if ($.isNotNull(org.getValidityPeriod())) {if (now.isAfter(org.getValidityPeriod())) {throw new AccountExpiredException("账户过期,请联系管理员");}}LoginUser user = new LoginUser().setUsername(username).setPassword(password).setPermissions(permissions);user.setId(operator.getId());user.setNickname(operator.getNickname());user.setOrgId(operator.getOrgId());user.setOrgType(org.getOrgType());user.setOrgName(org.getOrgName());user.setValidityPeriod(org.getValidityPeriod());return user;}}
6.4 自定义redis操作
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;@SuppressWarnings("unchecked")
@Slf4j
public class RedisCache {private static RedisCache redisCache;private final RedisTemplate<String, Object> redisTemplate;private final ValueOperations<String, Object> valueOperations;private final HashOperations<String, String, Object> hashOperations;private final ListOperations<String, Object> listOperations;private final SetOperations<String, Object> setOperations;private final ZSetOperations<String, Object> zSetOperations;private final String keyPrefix;public RedisCache(RedisTemplate<String, Object> redisTemplate, String keyPrefix) {this.redisTemplate = redisTemplate;this.keyPrefix = keyPrefix;this.valueOperations = redisTemplate.opsForValue();this.hashOperations = redisTemplate.opsForHash();this.listOperations = redisTemplate.opsForList();this.setOperations = redisTemplate.opsForSet();this.zSetOperations = redisTemplate.opsForZSet();redisCache = this;}/*** 获取缓存** @param key 键* @param <T> 类型* @return 指定类型值*/public static <T> T get(String key) {return key == null ? null : (T) opsValue().get(obtainKey(key));}/*** 写入缓存** @param key 键* @param value 值* @return true:成功*/public static boolean set(String key, Object value) {try {opsValue().set(obtainKey(key), value);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** 写入缓存并设置过期时间** @param key 键* @param value 值* @param second 过期时间(单位:秒)* @return true:成功*/public static boolean set(String key, Object value, long second) {try {if (second > 0) {opsValue().set(obtainKey(key), value, second, TimeUnit.SECONDS);} else {set(obtainKey(key), value);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** 写入缓存并设置过期时间** @param key 键* @param value 值* @param time 过期时间* @param unit 时间单位* @return true:成功*/public static boolean set(String key, Object value, long time, TimeUnit unit) {try {if (time > 0) {opsValue().set(obtainKey(key), value, time, unit);} else {set(obtainKey(key), value);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** 设置指定缓存的失效时间** @param key 键* @param second 过期时间(单位:秒)* @return true:成功*/public static boolean expire(String key, long second) {try {if (second > 0) {template().expire(obtainKey(key), second, TimeUnit.SECONDS);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** 获取指定缓存的过期时间** @param key 键* @return 过期时间;0:代表永久有效*/public static long getExpire(String key) {return Optional.ofNullable(template().getExpire(obtainKey(key), TimeUnit.SECONDS)).orElse(0L);}/*** 判断缓存是否存在** @param key 键* @return true:存在*/public static boolean has(String key) {try {return Optional.ofNullable(template().hasKey(obtainKey(key))).orElse(false);} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** 删除缓存** @param key 键(支持多个)*/public static void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {template().delete(obtainKey(key[0]));} else {template().delete(Arrays.stream(key).map(RedisCache::obtainKey).collect(Collectors.toList()));}}}/*** 递增指定数值** @param key 键* @param delta 递增数值(大于0)* @return 递增后的数值*/public static long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增数值必须大于 0");}return Optional.ofNullable(opsValue().increment(obtainKey(key), delta)).orElse(0L);}/*** 递减指定数值** @param key 键* @param delta 递减数值(大于0)* @return 递减后的数值*/public static long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减数值必须大于 0");}return Optional.ofNullable(opsValue().decrement(obtainKey(key), delta)).orElse(0L);}/*** Hash* 获取指定字段的值** @param key 键* @param field 字段* @param <T> 类型* @return 指定类型值*/public static <T> T hget(String key, String field) {return (T) opsHash().get(obtainKey(key), field);}/*** Hash* 设置指定键字段值** @param key 键* @param field 字段* @param value 值* @return true:成功*/public static boolean hset(String key, String field, Object value) {try {opsHash().put(obtainKey(key), field, value);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** Hash* 设置指定键字段值** @param key 键* @param field 字段* @param value 值* @param second 过期时间(单位:秒)* @return true:成功*/public static boolean hset(String key, String field, Object value, long second) {try {opsHash().put(obtainKey(key), field, value);if (second > 0) {expire(obtainKey(key), second);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** Hash* 获取所有的键值** @param key 键* @return 所有的键值*/public static <T> Map<String, T> hgetall(String key) {return (Map<String, T>) opsHash().entries(obtainKey(key));}/*** Hash* 设置多个字段值** @param key 键* @param map 多个字段值* @return true:成功*/public static <T> boolean hmset(String key, Map<String, T> map) {try {opsHash().putAll(obtainKey(key), map);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** Hash* 设置多个字段值并设置过期时间** @param key 键* @param map 多个字段值* @param second 过期时间(单位:秒)* @return true:成功*/public static <T> boolean hmset(String key, Map<String, T> map, long second) {try {opsHash().putAll(obtainKey(key), map);if (second > 0) {expire(obtainKey(key), second);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** Hash* 删除指定键指定字段缓存** @param key 键* @param fields 字段(支持多个)*/public static void hdel(String key, Object... fields) {opsHash().delete(obtainKey(key), fields);}/*** Hash* 判断指定字段是否存在** @param key 键* @param field 字段* @return true:成功*/public static boolean hhas(String key, String field) {return opsHash().hasKey(obtainKey(key), field);}/*** Hash* 递增指定字段数值** @param key 键* @param field 字段* @param delta 递增数值* @return 递增后的数值*/public static long hincr(String key, String field, long delta) {return opsHash().increment(obtainKey(key), field, delta);}/*** Hash* 递减指定字段数值** @param key 键* @param field 字段* @param delta 递减数值* @return 递减后的数值*/public static long hdecr(String key, String field, long delta) {return opsHash().increment(obtainKey(key), field, -delta);}/*** Set* 添加元素** @param key 键* @param values 值(支持多个)* @return 成功个数*/public static long sadd(String key, Object... values) {try {return Optional.ofNullable(opsSet().add(obtainKey(key), values)).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0L;}}/*** Set* 添加元素** @param key 键* @param second 过期时间(单位:秒)* @param values 值(支持多个)* @return 成功个数*/public static long sadd(String key, long second, Object... values) {try {Long count = opsSet().add(obtainKey(key), values);if (second > 0) {expire(obtainKey(key), second);}return Optional.ofNullable(count).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0;}}/*** Set* 获取缓存大小** @param key 键* @return 大小*/public static long scard(String key) {try {return Optional.ofNullable(opsSet().size(obtainKey(key))).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0;}}/*** Set* 移除元素** @param key 键* @param values 值(支持多个)* @return 移除个数*/public static long srem(String key, Object... values) {try {return Optional.ofNullable(opsSet().remove(obtainKey(key), values)).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0;}}/*** List* 列表头部插入缓存** @param key 键* @param value 值* @return true:成功*/public static boolean lpush(String key, Object value) {try {opsList().leftPush(obtainKey(key), value);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** List* 列表头部插入多个缓存** @param key 键* @param second 过期时间(单位秒)* @param values 值(支持多个)* @return true:成功*/public static boolean lpush(String key, long second, Object... values) {try {opsList().leftPushAll(obtainKey(key), values);if (second > 0) {expire(obtainKey(key), second);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** List* 列表尾部插入缓存** @param key 键* @param value 值* @return true:成功*/public static boolean lrpush(String key, Object value) {try {opsList().rightPush(obtainKey(key), value);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** List* 列表尾部插入多个缓存** @param key 键* @param second 过期时间(单位秒)* @param values 值(支持多个)* @return true:成功*/public static boolean lrpush(String key, long second, Object... values) {try {opsList().rightPushAll(obtainKey(key), values);if (second > 0) {expire(obtainKey(key), second);}return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** List* 设置指定索引的值** @param key 键* @param index 索引* @param value 值* @return true:成功*/public static boolean lset(String key, long index, Object value) {try {opsList().set(obtainKey(key), index, value);return true;} catch (Exception e) {log.error(e.getMessage(), e);return false;}}/*** List* 获取指定范围内的元素** @param key 键* @param start 开始索引* @param end 结束索引* @param <T> 类型* @return 元素列表*/public static <T> List<T> lrange(String key, long start, long end) {try {return (List<T>) opsList().range(obtainKey(key), start, end);} catch (Exception e) {log.error(e.getMessage(), e);return null;}}/*** List* 获取指定列表缓存的长度** @param key 键* @return 长度*/public static long llen(String key) {try {return Optional.ofNullable(opsList().size(obtainKey(key))).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0;}}/*** List* 获取指定索引对应的值** @param key 键* @param index 索引* @param <T> 类型* @return 值*/public static <T> T lindex(String key, long index) {try {return (T) opsList().index(obtainKey(key), index);} catch (Exception e) {log.error(e.getMessage(), e);return null;}}/*** List* 移动指定个数的元素** @param key 键* @param count 移除个数* @param value 值* @return 成功移除个数*/public static long lrem(String key, long count, Object value) {try {return Optional.ofNullable(opsList().remove(obtainKey(key), count, value)).orElse(0L);} catch (Exception e) {log.error(e.getMessage(), e);return 0;}}private static String obtainKey(String key) {return Optional.ofNullable(redisCache.keyPrefix).map(prefix -> prefix + key).orElse(key);}public static RedisTemplate<String, Object> template() {return redisCache.redisTemplate;}public static ValueOperations<String, Object> opsValue() {return redisCache.valueOperations;}public static HashOperations<String, String, Object> opsHash() {return redisCache.hashOperations;}public static ListOperations<String, Object> opsList() {return redisCache.listOperations;}public static SetOperations<String, Object> opsSet() {return redisCache.setOperations;}@SuppressWarnings({"AlibabaLowerCamelCaseVariableNaming"})public static ZSetOperations<String, Object> opsZSet() {return redisCache.zSetOperations;}public static GeoOperations<String, Object> opsGeo() {return template().opsForGeo();}public static StreamOperations<String, Object, Object> opsStream() {return template().opsForStream();}public static ClusterOperations<String, Object> opsCluster() {return template().opsForCluster();}
}
6.5 自定义加密解密(依据之前对接过微信支付相关token逻辑进行简单的RSA相关加解密)
package com.zyc.security.servlet.handler;//import com.ahlinksoft.common.utils.SysPropUtils;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class AesUtil {static final int KEY_LENGTH_BYTE = 32;static final int TAG_LENGTH_BIT = 128;private final static String publicKey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC69/jlQYoDKIWLR9dGD0/LLInD+KZyptdrdeqPQY9lBRmgcR2DHvyEDOe6cbGvV+0HcfOef6FFEqW2dzadjglbBqPMQttb5+0kEn4bZT+j2mMvQex51tD7AgmbEvV5XFi9DBYtxaM8c3DhBI+gobF3zzybqj3T9qY6ZPzAaQmKywIDAQAB";private final static String privateKey ="MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALr3+OVBigMohYtH10YPT8ssicP4pnKm12t16o9Bj2UFGaBxHYMe/IQM57pxsa9X7Qdx855/oUUSpbZ3Np2OCVsGo8xC21vn7SQSfhtlP6PaYy9B7HnW0PsCCZsS9XlcWL0MFi3FozxzcOEEj6ChsXfPPJuqPdP2pjpk/MBpCYrLAgMBAAECgYBFQlhz7FxIoTkfCCTJsiBX3g7GhpR6Ai3og8y49os826/LjDtneBIIhJNOsJEmqAaI7IBXZWYDJU43kl+r65Kw/z0h81jCFNOxyBmSkweWRXGYl77BxmTtxvvDrEEcj3z2e/plzeOwJpncErqwPgXz60zpuXmkNvtm5YnK+jIp0QJBAPaGU4ZpQjWbdjE9fSEkXYI3tReNpsGLuGbaExw4P5v1iaSuSIwhz0oorRMDWvXijAEEkDKYG+yV8B8s9w/F8okCQQDCJ6VAe3MBln/wCxMI2ksnEDfViFLzHljzrw4QWuq4pFkM0LmjqBAOh4SuGtnSAATg+Dn3cBcrW61lXY7d0A2zAkAH2q++kIvzeUiCuRrLe4UfKPc0EOdHM1Rksn35YDFmnqQMAyF8lXctPa6F9I1pWBZt0d9mPUJmf0BvPdeBQSVBAkAUg/XtO14qo8l/BtyLCe35Abi0ooQJVoKVtUCqL8Evo4YZWZDUHzUbI+y8LPApRYE+eFw/riN0nGpVFK6893gLAkA0VQEWHjzeq0qCJL8F9UXW67zfT/J3hMlahK0YXxfdU9kusqMjvHxzLg6+iZX/jaK1T3kWj2EafwUSxhJf1b7X";private static Map<String, String> params = new HashMap<>();//私钥解密public static byte[] decrypt(byte[] content, PrivateKey privateKey) throws Exception{Cipher cipher=Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(content);}//私钥解密public static String decryptToString(String ciphertext) {try {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));byte[ ] content =Base64.getDecoder().decode(ciphertext);byte[ ] doFinalResult = null;try {doFinalResult = cipher.doFinal(content);} catch (IllegalBlockSizeException e) {throw new RuntimeException(e);} catch (BadPaddingException e) {throw new RuntimeException(e);}try {return new String(doFinalResult, "utf-8");} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new IllegalStateException(e);} catch (InvalidKeyException e) {throw new IllegalArgumentException(e);}}//将base64编码后的公钥字符串转成PublicKey实例public static PublicKey getPublicKey(String publicKey) throws Exception{byte[ ] keyBytes=Base64.getDecoder().decode(publicKey.getBytes());X509EncodedKeySpec keySpec=new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory=KeyFactory.getInstance("RSA");return keyFactory.generatePublic(keySpec);}/*** String转私钥PrivateKey* @param key* @return* @throws Exception*/public static PrivateKey getPrivateKey(String key) {byte[] keyBytes;try {keyBytes = (new BASE64Decoder()).decodeBuffer(key);} catch (IOException e) {throw new RuntimeException(e);}PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = null;try {keyFactory = KeyFactory.getInstance("RSA");} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}PrivateKey privateKey = null;try {privateKey = keyFactory.generatePrivate(keySpec);} catch (InvalidKeySpecException e) {throw new RuntimeException(e);}return privateKey;}//生成密钥对public static KeyPair genKeyPair(int keyLength) throws Exception{KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(1024);return keyPairGenerator.generateKeyPair();}/*** 获取私钥。** @param filename 私钥文件路径 (required)* @return 私钥对象*//*public static PrivateKey getPrivateKey(String filename) throws IOException {String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");try {String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持RSA", e);} catch (InvalidKeySpecException e) {throw new RuntimeException("无效的密钥格式");}}*/public static void main(String[] args) throws Exception {String data="028fbb4b16bf48bf93a02d1d0687e149";/*KeyPair keyPair=genKeyPair(1024);//获取公钥,并以base64格式打印出来PublicKey publicKey=keyPair.getPublic();System.out.println("公钥:"+new String(Base64.getEncoder().encode(publicKey.getEncoded())));//获取私钥,并以base64格式打印出来PrivateKey privateKey=keyPair.getPrivate();System.out.println("私钥:"+new String(Base64.getEncoder().encode(privateKey.getEncoded())));*///公钥加密byte[] encryptedBytes=encrypt(data.getBytes(), getPublicKey(publicKey));log.debug("加密后:{}",encryptToString(encryptedBytes));//私钥解密byte[] decryptedBytes=decrypt(encryptedBytes, getPrivateKey(privateKey));log.debug("解密后:{}",new String(decryptedBytes,"utf-8"));//私钥解密String decryptedString = decryptToString(encryptToString(encryptedBytes));log.debug("解密后:{}",decryptedString);}//公钥加密public static byte[] encrypt(byte[] content, PublicKey publicKey) throws Exception{Cipher cipher=Cipher.getInstance("RSA");//java默认"RSA"="RSA/ECB/PKCS1Padding"cipher.init(Cipher.ENCRYPT_MODE, publicKey);return cipher.doFinal(content);}public static String encryptToString(byte[] encryptedBytes) throws Exception{return Base64.getEncoder().encodeToString(encryptedBytes);}}
小结
提示:这里可以添加总结
相关文章:

【SpringSecurity】基本开发流程
文章目录 概要整体架构流程实现流程1、编写各种Handler2 、AccessToken处理器3、定义AuthenticationFilter 继承 OncePerRequestFilter (OncePerRequestFilter是Spring提供的一个过滤器基类,它确保了在一次完整的HTTP请求中,无论请求经过多少…...

Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)
redis实现查询缓存的业务逻辑 service层实现 Overridepublic Result queryById(Long id) {String key CACHE_SHOP_KEY id;// 现查询redis内有没有数据String shopJson (String) redisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(shopJson)){ // 如果redis的数…...

ChatGPT从数据分析到内容写作建议相关的46个提示词分享!
在当今快节奏的学术环境中,研究人员面临着海量的信息和复杂的研究任务。幸运的是,随着人工智能技术的发展,像ChatGPT这样的先进工具为科研人员提供了强大的支持。今天就让我们一起探索如何利用ChatGPT提升研究效率进一步优化研究流程。 ChatG…...

在 Windows 11 中设置 WSL2 Ubuntu 的 `networkingMode=mirrored` 详细教程
在 Windows 11 中设置 WSL2 Ubuntu 的 networkingModemirrored 详细教程 引言环境要求配置 .wslconfig 文件重启 WSL2验证镜像网络模式解决常见问题其他注意事项结论 引言 在 Windows 11 中使用 WSL2(Windows Subsystem for Linux 2)时,默认…...

万字长文总结前端开发知识---JavaScriptVue3Axios
JavaScript学习目录 一、JavaScript1. 引入方式1.1 内部脚本 (Inline Script)1.2 外部脚本 (External Script) 2. 基础语法2.1 声明变量2.2 声明常量2.3 输出信息 3. 数据类型3.1 基本数据类型3.2 模板字符串 4. 函数4.1 具名函数 (Named Function)4.2 匿名函数 (Anonymous Fun…...

怎么样把pdf转成图片模式(不能复制文字)
贵但好用的wps, 转换——转为图片型pdf —————————————————————————————————————————— 转换前: 转换后: 肉眼可见,模糊了,且不能复制。 其他免费办法,参考&…...

本地centos网络配置
1、路径 2、配置 另外还需要...

kotlin内联函数——runCatching
1.runCatching作用 代替try{}catch{}异常处理,用于捕获异常。 2.runCatching函数介绍 参数:上下文引用对象为参数返回值:lamda表达式结果 调用runCatching函数,如果调用成功则返回其封装的结果,并可回调onSuccess函…...

Python3 正则表达式:文本处理的魔法工具
Python3 正则表达式:文本处理的魔法工具 内容简介 本系列文章是为 Python3 学习者精心设计的一套全面、实用的学习指南,旨在帮助读者从基础入门到项目实战,全面提升编程能力。文章结构由 5 个版块组成,内容层层递进,逻…...
《DiffIR:用于图像修复的高效扩散模型》学习笔记
paper:2303.09472 GitHub:GitHub - Zj-BinXia/DiffIR: This project is the official implementation of Diffir: Efficient diffusion model for image restoration, ICCV2023 目录 摘要 1、介绍 2、相关工作 2.1 图像恢复(Image Rest…...

windows平台通过命令行安装前端开发环境
访问node.js官网 访问node.js官网https://nodejs.org/en/download/,可以看到类似画面: 可以获取以下命令 # Download and install fnm: winget install Schniz.fnm # Download and install Node.js: fnm install 22 # Verify the Node.js version: no…...

记交叉编译asio_dtls过程
虽然编译成功了,但是还是有一些不妥的地方,参考一下就行了。 比如库的版本选择就有待商榷,我这里不是按照项目作者的要求严格用对应的版本编译的,这里也可以注意一下。 编译依赖库asio 下载地址, 更正一下,我其实用…...

学习yosys(一款开源综合器)
安装 sudo apt-get install yosys #ubuntu22.04仓库里面是yosys-0.9 sudo install xdot 创建脚本show_rtl.ys read_verilog cpu.v hierarchy -top cpu proc; opt; fsm; opt; memory; opt; show -prefix cpu 调用脚本 yosys show_rtl.ys verilog代码 module cpu(input c…...

自定义数据集 使用tensorflow框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
一、使用tensorflow框架实现逻辑回归 1. 数据部分: 首先自定义了一个简单的数据集,特征 X 是 100 个随机样本,每个样本一个特征,目标值 y 基于线性关系并添加了噪声。tensorflow框架不需要numpy 数组转换为相应的张量࿰…...

对于Docker的初步了解
简介与概述 1.不需要安装环境,工具包包含了环境(jdk等) 2.打包好,“一次封装,到处运行” 3.跨平台,docker容器在任何操作系统上都是一致的,这就是实现跨平台跨服务器。只需要一次配置好环境&…...

C语言进阶——3字符函数和字符串函数(2)
8 strsrt char * strstr ( const char *str1, const char * str2);查找子字符串 返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回 null 指针。匹配过程不包括终止 null 字符,但会在此处停止。 8.1 库函数s…...

机器学习day3
自定义数据集使用框架的线性回归方法对其进行拟合 import matplotlib.pyplot as plt import torch import numpy as np # 1.散点输入 # 1、散点输入 # 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1]…...

追剧记单词之:国色芳华与单词速记
●wretched adj. 恶劣的;悲惨的;不幸的;难过的 (不幸的)胜意出生于一个(恶劣的)家庭环境,嫁给王擎后依然过着(悲惨的)生活,她死后,牡丹…...

AIGC浪潮下,图文内容社区数据指标体系构建探索
在AIGC(人工智能生成内容)浪潮席卷之下,图文内容社区迎来了新的发展机遇与挑战。为了有效监控和优化业务发展,构建一个科学、全面的数据指标体系显得尤为重要。本文将深入探讨如何在AIGC背景下,为图文内容社区构建一套…...

总线、UART、IIC、SPI
一图流 总线 概念 连接多个部件的信息传输线,是各部件共享的传输介质 类型 片内总线:连接处理器内核和外设的总线,在芯片内部 片外总线:连接芯片和其他芯片或者模块的总线 总线的通信 总线通信的方式 串行通信 数据按位顺序传…...

戴尔电脑设置u盘启动_戴尔电脑设置u盘启动多种方法
最近有很多网友问,戴尔台式机怎么设置u盘启动,特别是近两年的戴尔台式机比较复杂,有些网友不知道怎么设置,其实设置u盘启动有两种方法,下面小编教大家戴尔电脑设置u盘启动方法。 戴尔电脑设置u盘启动方法一、戴尔进入b…...

【python】四帧差法实现运动目标检测
四帧差法是一种运动目标检测技术,它通过比较连续四帧图像之间的差异来检测运动物体。这种方法可以在一定的程度上提高检测的准确性。 目录 1 方案 2 实践 ① 代码 ② 效果图 1 方案 具体的步骤如下: ① 读取视频流:使用cv2.VideoCapture…...

JVM学习指南(48)-JVM即时编译
文章目录 即时编译(Just-In-Time Compilation, JIT)概述为什么JVM需要即时编译?即时编译与传统的静态编译的区别JVM中的即时编译器HotSpot VM中的C1和C2编译器编译器的作用和位置即时编译的工作流程代码的加载和解释执行热点代码检测编译优化编译优化技术公共子表达式消除循…...

office 2019 关闭word窗口后卡死未响应
最近关闭word文件总是出现卡死未响应的状态,必须从任务管理器才能杀掉word 进程,然后重新打开word再保存,很是麻烦。(#其他特征,在word中打字会特别变慢,敲击键盘半秒才出现字符。) office官网…...

[操作系统] 深入进程地址空间
程序地址空间回顾 在C语言学习的时,对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间,不同类型的数据有着不同的存储地址。 下图为程序地址空间的存储分布和和特性: 使用以下代码来验证一下…...

CVE-2025-0411 7-zip 漏洞复现
文章目录 免责申明漏洞描述影响版本漏洞poc漏洞复现修复建议 免责申明 本文章仅供学习与交流,请勿用于非法用途,均由使用者本人负责,文章作者不为此承担任何责任 漏洞描述 此漏洞 (CVSS SCORE 7.0) 允许远程攻击者绕…...

leetcode151-反转字符串中的单词
leetcode 151 思路 时间复杂度:O(n) 空间复杂度:O(n) 首先将字符串转为数组,这样可以方便进行操作,然后定义一个新的数组来存放从后到前的单词,由于arr中转换以后可能会出现有些项是空格的情况,所以需要判…...

若依 v-hasPermi 自定义指令失效场景
今天使用若依跟往常一样使用v-hasPermi 自定义指令的时候发现这个指令失效了,原因是和v-if指令一块使用,具体代码如下: <el-buttonsize"mini"type"text"icon"el-icon-edit-outline"v-hasPermi"[evalu…...

vue3中自定一个组件并且能够用v-model对自定义组件进行数据的双向绑定
1. 基础用法 在 Vue3 中,v-model 在组件上的使用有了更灵活的方式。默认情况下,v-model 使用 modelValue 作为 prop,update:modelValue 作为事件。 1.1 基本示例 <!-- CustomInput.vue --> <template><input:value"mo…...

使用 Python 和 Tesseract 实现验证码识别
验证码识别是一个常见且实用的技术需求,尤其是在自动化测试和数据采集场景中。通过开源 OCR(Optical Character Recognition,光学字符识别)工具 Tesseract,结合 Python 的强大生态,我们可以高效实现验证码识…...