SpringSecurity+JWT前后端分离架构登录认证

目录

1. 数据库设计

2. 代码设计

登录认证过滤器

认证成功处理器AuthenticationSuccessHandler

认证失败处理器AuthenticationFailureHandler

AuthenticationEntryPoint配置

AccessDeniedHandler配置

UserDetailsService配置

Token校验过滤器

登录认证过滤器接口配置

Spring Security全局配置

util包

测试结果


在SpringSecurity实现前后端分离登录token认证详解_springsecurity前后端分离登录认证-CSDN博客基础上进行重构,实现前后端分离架构登录认证,基本思想相同,借鉴开源Gitee代码进行改造,具有更好的代码规范。

1. 数据库设计

DROP TABLE IF EXISTS `t_auth`;
CREATE TABLE `t_auth`  (`id` BIGINT(11) NOT NULL AUTO_INCREMENT,`name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',`url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路径',`status` INT(1) NULL DEFAULT NULL,`create_time` DATETIME(0) NULL DEFAULT NULL,`update_time` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of t_auth
-- ----------------------------
INSERT INTO `t_auth` VALUES (1, '删除用户', '/usr/del', 1, '2021-11-26 17:08:11', '2021-11-26 17:07:52');
INSERT INTO `t_auth` VALUES (2, '新增用户', '/usr/add', 1, '2021-11-26 17:08:13', '2021-11-26 17:08:09');
INSERT INTO `t_auth` VALUES (3, '添加产品', '/product/add', 1, '2021-11-26 17:08:42', '2021-11-26 17:08:29');
INSERT INTO `t_auth` VALUES (4, '下架产品', '/product/del', NULL, NULL, '2021-11-26 17:12:17');
INSERT INTO `t_auth` VALUES (5, '注册', '/user/register', NULL, NULL, '2021-11-26 17:13:32');
INSERT INTO `t_auth` VALUES (6, '注销', '/user/logOff', NULL, NULL, '2021-11-26 17:13:50');-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (`id` BIGINT(11) NOT NULL,`name` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色名称',`status` INT(1) NULL DEFAULT NULL,`create_time` DATETIME(0) NULL DEFAULT NULL,`update_time` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'ROLE_admin', 1, '2021-11-26 17:08:52', '2021-11-26 17:08:51');
INSERT INTO `t_role` VALUES (2, 'ROLE_dba', 1, '2021-11-26 17:09:10', '2021-11-26 17:09:05');
INSERT INTO `t_role` VALUES (3, 'ROLE_vip', 1, '2021-11-26 17:09:32', '2021-11-26 17:09:25');
INSERT INTO `t_role` VALUES (4, 'ROLE_user', 1, '2021-11-26 17:09:45', '2021-11-26 17:09:42');-- ----------------------------
-- Table structure for t_role_auth
-- ----------------------------
DROP TABLE IF EXISTS `t_role_auth`;
CREATE TABLE `t_role_auth`  (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`role_id` BIGINT(20) NULL DEFAULT NULL,`auth_id` BIGINT(20) NULL DEFAULT NULL,`status` INT(1) NULL DEFAULT NULL,`create_time` DATETIME(0) NULL DEFAULT NULL,`update_time` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of t_role_auth
-- ----------------------------
INSERT INTO `t_role_auth` VALUES (1, 1, 3, 1, '2021-11-26 17:11:31', '2021-11-26 17:11:29');
INSERT INTO `t_role_auth` VALUES (2, 1, 4, 1, '2021-11-26 17:11:31', '2021-11-26 17:11:29');
INSERT INTO `t_role_auth` VALUES (3, 4, 5, 1, '2021-11-26 17:14:45', '2021-11-26 17:14:35');
INSERT INTO `t_role_auth` VALUES (4, 4, 6, 1, '2021-11-26 17:14:47', '2021-11-26 17:14:41');-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (`id` BIGINT(11) NOT NULL,`user_id` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '唯一的userId',`username` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',`password` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',`name` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',`status` INT(1) NULL DEFAULT NULL,`create_time` DATETIME(0) NULL DEFAULT NULL,`update_time` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, '120', 'zhangsan', '123456', '张三', 1, '2021-11-26 17:07:03', '2021-11-26 17:06:53');
INSERT INTO `t_user` VALUES (2, '110', 'lisi', '123456', '李四', 1, '2021-11-26 17:07:36', '2021-11-26 17:07:12');-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户唯一userId',`role_id` BIGINT(20) NULL DEFAULT NULL,`status` INT(1) NULL DEFAULT NULL,`create_time` DATETIME(0) NULL DEFAULT NULL,`update_time` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, '120', 1, 1, '2021-11-26 17:10:10', '2021-11-26 17:10:11');
INSERT INTO `t_user_role` VALUES (2, '110', 2, 1, '2021-11-26 17:11:16', '2021-11-26 17:11:13');

2. 代码设计

登录认证过滤器

Spring Security默认的表单登录认证的过滤器是UsernamePasswordAuthenticationFilter,这个过滤器并不适用于前后端分离的架构,因此我们需要自定义一个过滤器。参照UsernamePasswordAuthenticationFilter这个过滤器改造一下。

/*** 登录认证的filter,参照UsernamePasswordAuthenticationFilter,添加到这之前的过滤器*/public class JwtAuthenticationLoginFilter extends AbstractAuthenticationProcessingFilter {/*** 构造方法,调用父类的,设置登录地址/login,请求方式POST*/public JwtAuthenticationLoginFilter() {super(new AntPathRequestMatcher("/login", "POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {//获取表单提交数据String username = request.getParameter("username");String password = request.getParameter("password");//封装到token中提交UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,password);return getAuthenticationManager().authenticate(authRequest);}
}

认证成功处理器AuthenticationSuccessHandler

上述的过滤器接口一旦认证成功,则会调用AuthenticationSuccessHandler进行处理,因此我们可以自定义一个认证成功处理器进行自己的业务处理,代码如下:

@Component
public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Autowiredprivate JwtUtil jwtUtil;@AutowiredRedisTemplate redisTemplate;@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) throws IOException {UserDetails userDetails = (UserDetails) authentication.getPrincipal();SecurityContextHolder.getContext().setAuthentication(authentication);Map<String,String> map=new HashMap<>();map.put("username",userDetails.getUsername());//jwt生成tokenString token = jwtUtil.getToken(map);RedisUser redisUser = RedisUser.builder().username(userDetails.getUsername()).password(userDetails.getPassword()).authorities(userDetails.getAuthorities().stream().map(i->i.getAuthority()).collect(Collectors.toList())).build();//将用户信息保存到redis缓存中redisTemplate.opsForValue().set(userDetails.getUsername(),redisUser,12, TimeUnit.HOURS);ResponseUtils.result(httpServletResponse,new ResultMsg(200,"登录成功!",token));}}

认证失败处理器AuthenticationFailureHandler

同样的,一旦登录失败,比如用户名或者密码错误等等,则会调用AuthenticationFailureHandler进行处理,因此我们需要自定义一个认证失败的处理器,其中根据异常信息返回特定的JSON数据给客户端,代码如下:

@Component
public class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler {/*** 一旦登录失败则会被调用*/@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse response,AuthenticationException exception) throws IOException {//TODO 根据项目需要返回指定异常提示,这里演示了一个用户名密码错误的异常//BadCredentialsException 这个异常一般是用户名或者密码错误if (exception instanceof BadCredentialsException){ResponseUtils.result(response,new ResultMsg(200,"用户名或密码不正确!",null));}ResponseUtils.result(response,new ResultMsg(200,"登录失败",null));}
}

AuthenticationEntryPoint配置

AuthenticationEntryPoint这个接口当用户未通过认证访问受保护的资源时,将会调用其中的commence()方法进行处理。

@Component
@Slf4j
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {ResponseUtils.result(response,new ResultMsg(403,"认证失败,请重新登录!",null));}
}

AccessDeniedHandler配置

AccessDeniedHandler这处理器当认证成功的用户访问受保护的资源,但是权限不够,则会进入这个处理器进行处理。

@Component
public class RequestAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException {ResponseUtils.result(response,new ResultMsg(403,"权限不足!",null));}
}

UserDetailsService配置

UserDetailsService这个类是用来加载用户信息,包括用户名、密码、权限、角色集合,我们需要实现这个接口,从数据库加载用户信息,代码如下:

@Service
public class JwtTokenUserDetailsService implements UserDetailsService {/*** 查询用户详情的service*/@Autowiredprivate LoginService loginService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//从数据库中查询SecurityUser securityUser = loginService.loadByUsername(username);System.out.println(securityUser);//用户不存在直接抛出UsernameNotFoundException,security会捕获抛出BadCredentialsExceptionif (Objects.isNull(securityUser))throw new UsernameNotFoundException("用户不存在!");return securityUser;}
}

其中的LoginService是根据用户名从数据库中查询出密码、角色、权限,代码如下:

@Service
public class LoginServiceImpl implements LoginService {@Autowiredprivate PasswordEncoder passwordEncoder;@AutowiredTUserService tUserService;@AutowiredTRoleService tRoleService;@Nullable@Overridepublic SecurityUser loadByUsername(String username) {//获取用户信息TUser user = tUserService.getByUsername(username);if (Objects.nonNull(user)){SecurityUser securityUser = new SecurityUser();securityUser.setUsername(username);//todo 此处为了方便,直接在数据库存储的明文,实际生产中应该存储密文,则这里不用再次加密securityUser.setPassword(passwordEncoder.encode(user.getPassword()));//查询该用户的角色List<String> userRoles = tRoleService.selectAllByUsername(username);String[] a={};List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(userRoles.toArray(a));securityUser.setAuthorities(authorityList);return securityUser;}return null;}}

UserDetails这个也是个接口,其中定义了几种方法,都是围绕着用户名、密码、权限+角色集合这三个属性,因此我们可以实现这个类拓展这些字段,SecurityUser代码如下:

@Data
public class SecurityUser implements UserDetails {//用户名private String username;//密码private String password;//权限private Collection<? extends GrantedAuthority> authorities;public SecurityUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {this.username = username;this.password = password;this.authorities = authorities;}public SecurityUser(){}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}// 账户是否未过期@Overridepublic boolean isAccountNonExpired() {return true;}// 账户是否未被锁@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

Token校验过滤器

客户端请求头携带了token,服务端肯定是需要针对每次请求解析、校验token,因此必须定义一个Token过滤器,这个过滤器的主要逻辑如下:

  • 从请求头中获取accessToken

  • 对accessToken解析、验签、校验过期时间

  • 校验成功,将authentication存入ThreadLocal中,这样方便后续直接获取用户详细信息。

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {/*** JWT的工具类*/@Autowiredprivate JwtUtil jwtUtil;/*** UserDetailsService的实现类,从数据库中加载用户详细信息*/@Qualifier("jwtTokenUserDetailsService")@Autowiredprivate UserDetailsService userDetailsService;@AutowiredRedisTemplate redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {String token = request.getHeader("token");/*** token存在则校验token* 1. token是否存在* 2. token存在:*  2.1 校验token中的用户名是否失效*/if (!StringUtils.isEmpty(token)){DecodedJWT decodedJWT = jwtUtil.getTokenInfo(token);String username;try {username = decodedJWT.getClaim("username").asString();}catch (Exception e){throw new RuntimeException("token无效");}//从redis缓存中获得对应用户数据RedisUser redisUser = (RedisUser) redisTemplate.opsForValue().get(username);String[] a={};List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(redisUser.getAuthorities().toArray(a));UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(redisUser, null,authorityList);authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 将 authentication 存入 ThreadLocal,方便后续获取用户信息SecurityContextHolder.getContext().setAuthentication(authentication);}//继续执行下一个过滤器chain.doFilter(request,response);}
}

登录认证过滤器接口配置

上述定义了一个认证过滤器JwtAuthenticationLoginFilter,这个是用来登录的过滤器,但是并没有注入加入Spring Security的过滤器链中,需要定义配置,代码如下:

@Configuration
public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {/*** userDetailService*/@Qualifier("jwtTokenUserDetailsService")@Autowiredprivate UserDetailsService userDetailsService;/*** 登录成功处理器*/@Autowiredprivate LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;/*** 登录失败处理器*/@Autowiredprivate LoginAuthenticationFailureHandler loginAuthenticationFailureHandler;/*** 加密*/@Autowiredprivate PasswordEncoder passwordEncoder;/*** 将登录接口的过滤器配置到过滤器链中* 1. 配置登录成功、失败处理器* 2. 配置自定义的userDetailService(从数据库中获取用户数据)* 3. 将自定义的过滤器配置到spring security的过滤器链中,配置在UsernamePasswordAuthenticationFilter之前* @param*/@Overridepublic void configure(HttpSecurity http) {JwtAuthenticationLoginFilter filter = new JwtAuthenticationLoginFilter();filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));//认证成功处理器filter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);//认证失败处理器filter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);//直接使用DaoAuthenticationProviderDaoAuthenticationProvider provider = new DaoAuthenticationProvider();//设置userDetailServiceprovider.setUserDetailsService(userDetailsService);//设置加密算法provider.setPasswordEncoder(passwordEncoder);http.authenticationProvider(provider);//将这个过滤器添加到UsernamePasswordAuthenticationFilter之前执行http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);}
}

Spring Security全局配置

上述仅仅配置了登录过滤器,还需要在全局配置类做一些配置,如下:

  • 应用登录过滤器的配置

  • 将登录接口、令牌刷新接口放行,不需要拦截

  • 配置AuthenticationEntryPoint、AccessDeniedHandler

  • 禁用session,前后端分离+JWT方式不需要session

  • 将token校验过滤器TokenAuthenticationFilter添加到过滤器链中,放在UsernamePasswordAuthenticationFilter之前。

@Configuration
//@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;@Autowiredprivate EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;@Autowiredprivate RequestAccessDeniedHandler requestAccessDeniedHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//禁用表单登录,前后端分离用不上.disable()//应用登录过滤器的配置,配置分离.apply(jwtAuthenticationSecurityConfig).and()// 设置URL的授权.authorizeRequests().antMatchers("/login").permitAll()// anyRequest() 所有请求   authenticated() 必须被认证.anyRequest().authenticated()//处理异常情况:认证失败和权限不足.and().exceptionHandling()//认证未通过,不允许访问异常处理器.authenticationEntryPoint(entryPointUnauthorizedHandler)//认证通过,但是没权限处理器.accessDeniedHandler(requestAccessDeniedHandler).and()//禁用session,JWT校验不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//将TOKEN校验过滤器配置到过滤器链中,否则不生效,放到UsernamePasswordAuthenticationFilter之前.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)// 关闭csrf.csrf().disable();}// 自定义的Jwt Token校验过滤器@Beanpublic TokenAuthenticationFilter authenticationTokenFilterBean() {return new TokenAuthenticationFilter();}/*** 加密算法** @return*/@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}
}

util包

JWT工具类

@Component
@ConfigurationProperties(prefix = "jwt")
//@Data
public class JwtUtil {private  String signature="cbac";private  Integer expiration=12;/**** 生成token header.payload.signature*/public  String getToken(Map<String,String> payload){Calendar calendar = Calendar.getInstance();calendar.add(Calendar.HOUR, 24);  // 24小时JWTCreator.Builder builder = JWT.create();// 构建payloadpayload.forEach(builder::withClaim);// 指定签发时间、过期时间 和 签名算法,并返回tokenString token = builder.withIssuedAt(new Date()).withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(signature));return token;}/**** 获取token信息*/public  DecodedJWT getTokenInfo(String token){DecodedJWT verify=JWT.require(Algorithm.HMAC256(signature)).build().verify(token);return verify;}}

结果封装类

public class ResponseUtils {public static void result(HttpServletResponse response, ResultMsg msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream out = response.getOutputStream();ObjectMapper objectMapper = new ObjectMapper();out.write(objectMapper.writeValueAsString(msg).getBytes("UTF-8"));out.flush();out.close();}
}

测试结果

项目目录结构

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/638968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应用层—HTTPS详解(对称加密、非对称加密、密钥……)

文章目录 HTTPS什么是 HTTPSHTTPS 如何加密HTTPS 的工作过程对称加密非对称加密 HTTPS 什么是 HTTPS HTTPS 也是一个应用层的协议。是在 HTTP 协议的基础上引入的一个加密层。 由来&#xff1a;HTTP 协议内容都是按照文本的方式明纹传输&#xff0c;这就导致在传输过程中出现…

Springboot+vue的校园疫情防控系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的校园疫情防控系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的校园疫情防控系统&#xff0c;采用M&#xff…

2023年12月青少年机器人技术等级考试(四级)理论综合试卷

2023年12月青少年机器人技术等级考试&#xff08;四级&#xff09;理论综合试卷 单选题 第 1 题 单选题 Arduino UNO/Nano主控板&#xff0c;当数字引脚输出信号为高电平时&#xff0c;对应的电压是 &#xff1f;&#xff08; &#xff09; A.0V B.5V C.-0.5 ~ 1.5V D.3…

[SS]语义分割_转置卷积

转置卷积&#xff08;Transposed Convolution&#xff09; 抽丝剥茧&#xff0c;带你理解转置卷积&#xff08;反卷积&#xff09; 目录 一、概念 1、定义 2、运算步骤 二、常见参数 一、概念 1、定义 转置卷积&#xff08;Transposed Convolution&#xff09;&#xf…

Paimon教程

教程来自尚硅谷 目录 教程来自尚硅谷1. 概述1.1 简介1.2 核心特性1.3 文件布局1.3.1 LSM Trees 2. 集成Flink2.1 安装&配置2.2 Catalog 3. 进阶使用3.1 写入性能3.1.1 并行度3.1.2 Compaction3.1.3 触发Compaction的Sorted Run数3.1.4 写入初始化3.1.5 内存 3.2 读取性能3.…

mac 安装配置oh-my-zsh

1. 安装brew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 按照步骤安装即可 安装完成查看版本 brew -v 2. 安装zsh brew install zsh 查看版本 zsh --version 3. 安装oh-my-zsh github官网链…

【Linux系统编程】进程优先级

文章目录 1. 优先级的基本概念2. 为什么存在优先级3. 查看系统进程4. PRI and NI5. top命令修改已存在进程的nice值6. 其他概念 1. 优先级的基本概念 本篇文章讲解进程优先级&#xff0c;首先我们来了解一下进程优先级的概念&#xff1a; cpu资源分配的先后顺序&#xff0c;就…

[娱乐]索尼电视安装Kodi

索尼电视不能直接apk安装kodi应用 android studio安装后附带 abd&#xff0c; 路径 C:\Users\[yourname]\AppuoData\Local\Android\Sdk\platform-tools\adb.exe安卓电视点击内部版本号&#xff0c;启用开发者模式 adb 连接索尼安卓电视&#xff0c;记得电视上运行调试 abi选…

C#,入门教程(30)——扎好程序的笼子,错误处理 try catch

上一篇&#xff1a; C#&#xff0c;入门教程(29)——修饰词静态&#xff08;static&#xff09;的用法详解https://blog.csdn.net/beijinghorn/article/details/124683349 程序员语录&#xff1a;凡程序必有错&#xff0c;凡有错未必改&#xff01; 程序出错的原因千千万&…

Jmeter 配置元件

Jmeter 配置元件 CSV 数据集配置HTTP Cookie 管理器HTTP Header 信息头管理器增加多个用户案列 使用Jmeter发送请求的时候&#xff0c;需要配置元件&#xff0c;配置请求Header、Cookie、数据集合等。可以模拟多个在线用户登录&#xff0c;修改请求头数据。 CSV 数据集配置 C…

[学习笔记]刘知远团队大模型技术与交叉应用L4-Prompt-learning Delta-learning

Prompt-Learning and Delta-Tunning 背景和概览 但是从T5开始&#xff0c;大模型越来越大了。 微调很难了。 模型的趋势 Model Scaling&#xff1a;模型越来越大 Difficult Tuning&#xff1a;微调越来越难 Prompt-Learning 基本组成与流程介绍 预训练和fine-tuning有一…

140:leaflet加载here地图(v2软件多种形式)

第140个 点击查看专栏目录 本示例介绍如何在vue+leaflet中添加HERE地图(v2版本的软件),并且含多种的表现形式。包括地图类型,文字标记的设置、语言的选择、PPI的设定。 v3版本和v2版本有很大的区别,关键是引用方法上,请参考文章尾部的API链接。 直接复制下面的 vue+leaf…

2023年12月青少年机器人技术等级考试(二级)理论综合试卷

2023年12月青少年机器人技术等级考试&#xff08;二级&#xff09;理论综合试卷 选择题 第 1 题 单选题 下图中&#xff0c;能够将圆周运动转化为往复摆动的是&#xff1f;&#xff08; &#xff09; A. B. C. D. 第 2 题 单选题 如图&#xff0c;该机械结构可实现的运动…

新闻每天都在更新,那网页上的新闻页面是怎么使用Dreamweaver制作的?

新闻每天都在更新&#xff0c;那网页上的新闻页面是怎么使用Dreamweaver制作的&#xff1f; 新闻有很多种&#xff0c;但大多数结构都差不多&#xff0c;我们就先做一个简单的新闻页面&#xff0c;如图1中画圈圈的新闻内容。 图1 案例实现 新闻页面一般由四个部分构成&#…

MarkDown学习笔记 直观全面详细

前言 为什么我们要学习Markdown呢&#xff1f;因为Markdown简单易学易上手&#xff0c;可以以纯文本格式编写文档&#xff0c;然后转换成有效的HTML文档&#xff0c;并且以导出 HTML 、Word、图像、PDF、Epub 等多种格式的文档&#xff0c;许多网站平台的文章、博客、论文均可…

Acwing 138 周赛 解题报告 | 珂学家 | 偏序 + DP构造

前言 整体评价 很久没做acwing周赛了, 之前vp过一些周赛&#xff0c;感觉风格变了。 这次感觉还可以&#xff0c;都是些眼熟的套路题。 A. 5458. 进水排水问题 思路: 签到题 按题意描述编写 import java.io.*; import java.util.*;public class Main {public static void …

【QT+QGIS跨平台编译】之四:【libSSH2+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、libSSH2介绍二、文件下载三、文件分析四、pro文件五、编译实践 一、libSSH2介绍 libSSH2是一个开源的C函数库&#xff0c;用来实现SSH2协议。 SSH(Secure SHell)到目前为止有两个不兼容的版本——SSH1和SSH2。 SSH2避免了RSA的专利问题&#xff0c;并修补了CRC…

Mybatis 动态SQL条件查询(注释和XML方式都有)

需求 : 根据用户的输入情况进行条件查询 新建了一个 userInfo2Mapper 接口,然后写下如下代码,声明 selectByCondition 这个方法 package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*; import j…

DCDC电路基础认知

前言 从开关电源&#xff08;BMS充电器&#xff09;入门硬件之——DCDC电路基础认知 有纰漏请指出&#xff0c;转载请说明。 学习交流请发邮件 1280253714qq.com 最近做的项目中有个需求&#xff0c;输入的电压是变化的&#xff08;12-24V&#xff09;&#xff0c;但要保证…

unity单例模式

文章目录 在Unity中&#xff0c;单例模式是一种常用的编程设计模式&#xff0c;用于确保在整个应用程序生命周期中&#xff0c;只有一个类的实例存在。这样可以保证数据的全局唯一性和共享性&#xff0c;例如游戏场景中的资源管理器、游戏控制器、事件管理器等。 以下是一个简单…