从0开始搭建一个生产级SpringBoot2.0.X项目(十二)SpringBoot接口SpringSecurity JWT鉴权

前言

最近有个想法想整理一个内容比较完整springboot项目初始化Demo。

SpringBoot接口权限控制 SpringSecurity 接口使用 Bearer token类型  JWT 鉴权

一、pom文件新增依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.12.6</version>
</dependency>

application.yaml 增加参数 用于模拟后续生成TOKEN和验证使用

#JTW 加密的密码
jwt:# secret 必须足够长 40bits之上# 否则 报错 The specified key byte array is 40 bits which is not secure enough            #for any JWT HMAC-SHA algorithmsecret: 12345678123456781234567812345678
#认证系统编号 clientid: clientid

二、创建 UserInfo 类 模拟用户信息

import lombok.Data;/*** 用户信息*/
@Data
public class UserInfo {private String clientid;//用户授权idprivate String username;//用户名private String password;//用户名
}

三、创建 JwtTokenUtil 类,用于生成和验证JWT令牌。

package com.murg.bootdemo.util;import com.murg.bootdemo.business.entity.UserInfo;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@Component
public class JwtTokenUtil implements Serializable {/*** 过期时间(单位:秒)*/public static final Integer REFRESH_TOKEN_EXPIRE = 72000;/*** 加密算法*/private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;/*** 私钥*/private static String SECRET;/*** 秘钥实例*/public static SecretKey KEY;/*** jwt签发者*/private final static String JWT_ISS = "Sunreal";/*** jwt主体*/private final static String SUBJECT = "dwd";@Value("${jwt.secret}")public void setSecret(String secret) {JwtTokenUtil.SECRET = secret;JwtTokenUtil.KEY=Keys.hmacShaKeyFor(SECRET.getBytes());}/*** 获取令牌用户数据* @param token* @return*/public static UserInfo getUserInfoByToken(String token) {final Claims claims = parsePayload(token);UserInfo userInfo = null;if (null != claims) {userInfo = new UserInfo();userInfo.setClientid(claims.get("clientid").toString());userInfo.setUsername(claims.get("username").toString());}return userInfo;}/*** 获取令牌用户数据* @param token* @return*/public static String getUsernameFromToken(String token) {final Claims claims = parsePayload(token);UserInfo userInfo = null;if (null != claims) {userInfo = new UserInfo();userInfo.setClientid(claims.get("clientid").toString());userInfo.setUsername(claims.get("username").toString());}return userInfo.getUsername();}/*** 令牌过期校验 true-过期 false-未过期* @param token* @return*/public static Boolean isExpired(String token) {Boolean result = true;final Claims claims = parsePayload(token);if (null != claims) {String exp = claims.get("exp").toString();long diff = Long.parseLong(exp) - System.currentTimeMillis() / 1000;if(diff > 0) {result = false;}}return result;}public Boolean validateToken(String token, UserDetails userDetails) {final String username = getUsernameFromToken(token);return (username.equals(userDetails.getUsername()) && !isExpired(token));}/*** 获取令牌的过期时间*/public static Long getTokenExpireIn(String token) {Long expireIn = System.currentTimeMillis() / 1000;final Claims claims = parsePayload(token);if (null != claims) {String exp = claims.get("exp").toString();return Long.parseLong(exp);}return expireIn;}public String generateToken(UserDetails userDetails,String clientid) {Map<String, Object> claims = new HashMap<>();claims.put("username",userDetails.getUsername());// claims.put("password",userDetails.getPassword());claims.put("clientid",clientid);return generateToken(claims);}/*** 生成令牌*/public static String generateToken(Map<String, Object> payload) {Date expireDate = Date.from(Instant.now().plusSeconds(REFRESH_TOKEN_EXPIRE));return Jwts.builder().header().add("typ", "JWT").add("alg", "HS256").and().claims(payload).id(UUID.randomUUID().toString()).expiration(expireDate).issuedAt(new Date()).subject(SUBJECT).issuer(JWT_ISS).signWith(KEY, ALGORITHM).compact();}/*** 解析令牌claims*/public static Jws<Claims> parseClaim(String token) {Jws<Claims> claimsJws = null;claimsJws = Jwts.parser() .verifyWith(KEY) .build().parseSignedClaims(token);return claimsJws;}/*** 解析令牌header*/private static JwsHeader parseHeader(String token) {Jws<Claims> claimsJws = parseClaim(token);if(null == claimsJws) {return null;}return claimsJws.getHeader();}/*** 解析令牌payload*/private static Claims parsePayload(String token) {Jws<Claims> claimsJws = parseClaim(token);if(null == claimsJws) {return null;}return claimsJws.getPayload();}
}

四、创建 JwtAuthenticationEntryPoint 类,用于处理未经授权的请求

package com.murg.bootdemo.filter;import com.murg.bootdemo.common.WebResult;
import com.murg.bootdemo.util.JsonUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;/**此类继承Spring Security的AuthenticationEntryPoint类,并重写其commence。它拒绝每个未经身份验证的请求并发送错误代码401。*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {private static final long serialVersionUID = -7858869558953243875L;@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {//返回统一封装的格式WebResult<Object> webResult = WebResult.ok();webResult.isWrong("您可能未登录或登录超时,请重新登陆!");response.setContentType("application/json");response.setStatus(401);try {JsonUtil.write2Stream(response.getOutputStream(), webResult);} catch (Exception var6) {throw new ServletException();}}
}

五、创建JWTUserDetailsService以便使用用户名从数据库中获取用户详细信息。

package com.murg.bootdemo.interceptor;import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.ArrayList;/*** JWTUserDetailsService* JWTUserDetailsService实现了Spring Security UserDetailsService接口。* 它会覆盖loadUserByUsername,以便使用用户名从数据库中获取用户详细信息。* 当对用户提供的用户详细信息进行身份验证时,Spring Security Authentication Manager调用此方法从数据库中获取用户详细信息* 在这里,我们从硬编码的用户列表中获取用户详细信息。* 以后实际会从数据库中获取用户详细信息的DAO实现。* 用户密码也使用BCrypt以加密格式存储。在这里,您可以使用在线Bcrypt生成器为密码生成Bcrypt。*/
@Service
public class JwtUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if ("admin".equals(username)) {return new User("admin", "$2a$10$XRemBdMT1hrf4EdfsDWHCeSQFczKq0STVDqVRXT8YnbPoQcN21KAi",new ArrayList<>());} else {throw new UsernameNotFoundException("User not found with username: " + username);}}/*** 测试生成BCrypt加密格式* @param args*/public static void main(String[] args) {String pass = "123456";BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();final String passHash = encoder.encode(pass);System.out.println(passHash);final boolean matches = encoder.matches(pass, passHash);System.out.println(matches);}
}

六、创建 JwtRequestFilter 类,用于解析和验证JWT 

package com.murg.bootdemo.filter;import com.murg.bootdemo.interceptor.JwtUserDetailsService;
import com.murg.bootdemo.util.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;/**JwtRequestFilter继承了Spring Web的OncePerRequestFilter类。对于任何传入请求,都会执行此Filter类。它检查请求是否具有有效的JWT令牌。如果它具有有效的JWT令牌,则它将在上下文中设置Authentication,以指定当前用户已通过身份验证。*/
@Slf4j
@Component
public class JwtRequestFilter extends OncePerRequestFilter {private  final JwtUserDetailsService jwtUserDetailsService;private final JwtTokenUtil jwtTokenUtil;public JwtRequestFilter(JwtUserDetailsService jwtUserDetailsService, JwtTokenUtil jwtTokenUtil) {this.jwtTokenUtil = jwtTokenUtil;this.jwtUserDetailsService = jwtUserDetailsService;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String requestTokenHeader = request.getHeader("Authorization");String username = null;String jwtToken = null;// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Tokenif (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {jwtToken = requestTokenHeader.substring(7);try {username = jwtTokenUtil.getUserInfoByToken(jwtToken).getUsername();} catch (IllegalArgumentException e) {log.error("Unable to get JWT Token");} catch (ExpiredJwtException e) {log.error("JWT Token has expired"); //token 过期抛出异常 用于统一异常处理ExceptionConfig捕获  throw e;}} else {logger.warn("JWT Token does not begin with Bearer String");}//Once we get the token validate it.if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {//根据用户名获取库里的用户信息UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);// if token is valid configure Spring Security to manually set authenticationif (jwtTokenUtil.validateToken(jwtToken, userDetails)) { //校验用户信息 用户名一致并且token没过期/* 创建了一个UsernamePasswordAuthenticationToken实例,并通过调用setDetails方法设置了额外的细节。这个细节可以是任何对象,通常用于传递额外的信息,如客户端IP地址、会话ID等。请注意,setDetails方法不是为了设置用户名和密码,而是为了设置与认证过程相关的其他信息。如果你需要设置用户名和密码,应该在创建UsernamePasswordAuthenticationToken实例时提供。*/UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 设置SecurityContext中的Authentication对象 在上下文中设置Authentication,以指定当前用户已通过身份验证SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}

七、增加配置Spring Security类 WebSecurityConfig

package com.murg.bootdemo.config;import com.murg.bootdemo.filter.JwtAuthenticationEntryPoint;
import com.murg.bootdemo.filter.JwtRequestFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;private final UserDetailsService jwtUserDetailsService;private final JwtRequestFilter jwtRequestFilter;public WebSecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,UserDetailsService jwtUserDetailsService,JwtRequestFilter jwtRequestFilter) {this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;this.jwtUserDetailsService = jwtUserDetailsService;this.jwtRequestFilter = jwtRequestFilter;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/***认证管理器的建造器,如果要使用该功能需要配置一个userDetailsService和PasswordEncoder;为什么要配置UserDetailService,他的作用是什么?因为我们需要被认证的用户作为管理,那么UserDetailService作用就是:在认证器中根据传过来的用户名进行查找,PasswordEncoder就是将密码进行比对;认证成功后返回一个Authentication对象自定义用户认证的service 用于实现自己的认证逻辑passwordEncoder定义密码解密方式**/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// We don't need CSRF for this examplehttpSecurity.csrf().disable()// 不验证此特定请求 该请求为获取token的接口.authorizeRequests().antMatchers("/authenticate").permitAll()// 所有其他请求都需要经过身份验证.anyRequest().authenticated().and()//Spring Security中用于配置认证入口点异常处理的一部分。当Spring Security遇到认证异常时,// 它会通过配置的AuthenticationEntryPoint来处理。.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()// 所有的Rest服务一定要设置为无状态,以提升操作性能.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Add a filter to validate the tokens with every requesthttpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);}}

八、测试接口鉴权

重启服务不带token测试helloWord接口,返回鉴权失败

创建GetJwtController

调用上述预留放开的/authenticate接口获取token数据

package com.murg.bootdemo.business.controller;import com.murg.bootdemo.common.WebResult;
import com.murg.bootdemo.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;import java.util.Objects;/**使用JwtAuthenticationController暴露/authenticate。使用Spring Authentication Manager验证用户名和密码。如果凭据有效,则会使用JWTTokenUtil创建一个JWT令牌并将其提供给客户端。*/
@RestController
public class GetJwtController {@Value("${jwt.clientid}")private String CLIENTID;private final AuthenticationManager authenticationManager;private final JwtTokenUtil jwtTokenUtil;private final UserDetailsService jwtInMemoryUserDetailsService;public GetJwtController(AuthenticationManager authenticationManager,JwtTokenUtil jwtTokenUtil,UserDetailsService jwtInMemoryUserDetailsService) {this.authenticationManager = authenticationManager;this.jwtTokenUtil = jwtTokenUtil;this.jwtInMemoryUserDetailsService = jwtInMemoryUserDetailsService;}@RequestMapping(value = "/authenticate", method = RequestMethod.POST)public WebResult createAuthenticationToken(@RequestBody JwtRequest authenticationRequest)throws Exception {if(!CLIENTID.equals(authenticationRequest.getClientid())) {throw new Exception( "客户端未授权");}//   authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());final UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());final String token = jwtTokenUtil.generateToken(userDetails,authenticationRequest.getClientid());return WebResult.ok(token);}/***  这个方法会调用UserDetailsServiceImpl.loadUserByUsername,此过程中调用的流程如下:* 1. AuthenticationManager是个接口,ProviderManager是他的实现类。* 2. authenticationManager.authenticate()调用其实就是ProviderManager.authenticate()* 3. ProviderManager代码中找到provider.authenticate(authentication);* 4. provider.authenticate(authentication)是AuthenticationProvider.authenticate(authentication)方法。* 5. AuthenticationProvider也是一个接口,他的实现类是AbstractUserDetailsAuthenticationProvider。* 6. AbstractUserDetailsAuthenticationProvider的子类是DaoAuthenticationProvider。* 7. 在DaoAuthenticationProvider中调用了UserDetailsService8.写一个UserDetailsServiceImpl实现类JwtUserDetailsService ,实现UserDetailsService ,并重写loadUserByUsername()直接调用UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());*/private void authenticate(String username, String password) throws Exception {Objects.requireNonNull(username);Objects.requireNonNull(password);try {authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));} catch (DisabledException e) {throw new Exception("USER_DISABLED", e);} catch (BadCredentialsException e) {throw new Exception("INVALID_CREDENTIALS", e);}}
}

 

拿到token之后 重新带 Bearer 类型 token 测试 helloWord接口 正常返回

九、配置鉴权白名单

通过application.yaml配置放行鉴权路由白名单

1.增加配置 放行getTt26接口。

oauth:whitelist:- /getTt26

2.创建配置类 AuthwhitelistConfig 定义属性 whitelist 

参数配置在 application.yaml 文件中,通过 @ConfigurationProperties 注解注入参数whitelist

package com.murg.bootdemo.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;import java.util.List;
@Data
@Configuration
@ConfigurationProperties("oauth")
public class AuthwhitelistConfig {private List<String> whitelist;
}

3修改 WebSecurityConfig方法

引入AuthwhitelistConfig 

private final AuthwhitelistConfig authwhitelist;

 增加白名单不验证

 .antMatchers(authwhitelist.getWhitelist().toArray(new String[0])).permitAll()

@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// We don't need CSRF for this examplehttpSecurity.csrf().disable()// 不验证此特定请求.authorizeRequests().antMatchers("/authenticate").permitAll().antMatchers(authwhitelist.getWhitelist().toArray(new String[0])).permitAll()// 所有其他请求都需要经过身份验证.anyRequest().authenticated().and()//Spring Security中用于配置认证入口点异常处理的一部分。当Spring Security遇到认证异常时,// 它会通过配置的AuthenticationEntryPoint来处理。.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()// 所有的Rest服务一定要设置为无状态,以提升操作性能.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Add a filter to validate the tokens with every requesthttpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);}

不带token测试接口

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

LightRAG成功跑通:Ollama+Qwen2.5+bge-large-zh-v1.5

目录 LightRAG跑通1.安装环境2.示例跑通&#xff1a;&#xff08;1&#xff09;准备样例数据&#xff08;2&#xff09;源码修改&#xff08;3&#xff09;中文示例跑通 4.Neo4j可视化图谱5.问题解决&#xff1a;跑通后感受&#xff1a; LightRAG跑通 继GraphRAG之后&#xff…

qt QMovie详解

1、概述 QMovie 是 Qt 框架中用于处理动画文件的类。它支持多种动画格式&#xff0c;包括 GIF 和一些常见的视频格式&#xff08;尽管对视频格式的支持依赖于底层平台&#xff09;。QMovie 类主要用于在 QLabel 或 QGraphicsView 等控件中显示动画。通过加载动画文件&#xff…

二叉树的练习题(上)

1. 前序遍历 题目解析: 题目: . - 力扣&#xff08;LeetCode&#xff09; 解题步骤: 题目给定的返回值是一个链表,也就是我们每一次前序遍历都要把遍历结果保存到顺序表里面进行返回. 前序遍历: 根结点 -> 左子树 -> 右子树 我们的遍历过程如图 就相当于所有的结点 …

LabVIEW高效数据采集与信号处理系统

开发一个基于LabVIEW软件的数据采集与信号处理系统&#xff0c;实现高效的数据采集和信号处理。系统通过优化数据流处理过程和直观的图形化界面&#xff0c;提高了操作效率和数据准确性&#xff0c;特别适合工业和科研应用。 ​ 项目背景 在现代工业和科研领域&#xff0c;数…

ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源 或者超时失效

数据治理过程中&#xff0c;有字段长度不够&#xff0c;扩展字段&#xff0c;报&#xff1a;ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源 或者超时失效 ALTER TABLE LAPD_RSJ_CXJMYLBXCBXX MODIFY HKXZ VARCHAR2(10);错误表示当前会话在试图访问的资源&#xff08;通常…

上海亚商投顾:创业板指冲高回落 全市场成交超2.5万亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 市场全天冲高回落&#xff0c;创业板指尾盘跌超1%&#xff0c;北证50一度涨超7%&#xff0c;盘中再创历史新高…

多维视角下的知识管理:Spring Boot应用

2 开发技术 2.1 VUE框架 Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的渐进式框架。 Vue 只关注视图层&#xff0c; 采用自底向上增量开发的设计。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 2.2 Mysql数据库 …

x-cmd pkg | gum - 轻松构建美观实用的终端界面,解锁命令行新玩法

目录 简介快速上手安装使用 功能特点竞品和相关作品进一步探索 简介 gum 是由 Charm 团队于 2022 年使用 Go 开发的终端 UI 组件工具箱&#xff0c;能帮用户在终端中快速构建交互式 TUI 界面&#xff08;如表单、菜单、提示框等&#xff09;&#xff0c;简化命令行应用程序的开…

前端学习Day13 CSS盒子的定位(固定定位篇“附练习”)

一、固定定位 固定定位 &#xff08;position:fixed&#xff09;其实是绝对定位的子类别&#xff0c;一个设置了 position:fixed 的元素是相对于视窗固定的&#xff0c;就算页面文档发生了滚动&#xff0c;它也会一直待在相同的地方。 ⚠️&#xff1a;固定定位会脱离文档流。…

基于python多准则决策分析的汽车推荐算法设计与实现

摘要 随着汽车市场的快速发展和消费者需求的多样化&#xff0c;汽车选择变得愈加复杂。为了帮助消费者在众多汽车选项中做出明智的决策&#xff0c;基于多准则决策分析&#xff08;MCDA&#xff09;的汽车推荐算法应运而生。本研究旨在设计和实现一种基于 Python 的汽车推荐系…

基于SpringBoot的“校园交友网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园交友网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 网站首页界面图 用户注册界面图 线下活动界面图 交…

SD-WAN技术怎样与运营商网络无缝集成

随着企业对网络性能和灵活性的要求不断提升&#xff0c;SD-WAN&#xff08;软件定义广域网&#xff09;技术成为优化企业网络架构的重要解决方案。SD-WAN不仅能提升网络的灵活性和可管理性&#xff0c;还能提供更高的性能。要实现SD-WAN的高效部署&#xff0c;必须与运营商的网…

Node.js——fs模块-路径补充说明

1、相对路径&#xff1a; ./座右铭.txt 当前目录下的座右铭.txt座右铭.txt 等效于上面的写法../座右铭.txt 当前目录的上一级目录中的座右铭.txt 2、绝对路径 D&#xff1a;/Program File Windows系统下的绝对路径/usr/bin Linux系统…

SparkSQL的自定义函数

目录 一、关于自定义函数 1、自定义函数分为&#xff1a; 2、pyspark中自定义函数的三种写法&#xff1a; 二、 regeister方式自定义函数&#xff08;SQL和DSL中使用&#xff09; 三、udf注册方式定义UDF函数&#xff08;DSL中使用&#xff09; 一、关于自定义函数 1、自…

实践决定认识

“不登高山&#xff0c;不知天之高也;不临深溪&#xff0c;不知地之厚也。”这句话说明: A.人的意识具有创造性【无关题义】 B.人的认识是独立于实践之外的【错误&#xff0c;实践决定认识】 C.实践在认识过程中具有决定作用【正确】 D.人的一切知识都是从直接经验中获得的 这里…

十一,D O M 获取

1、DOM初相识 1.1、DOM简介 文档对象模型&#xff08;Document Object Model &#xff0c;简称DOM&#xff09;&#xff0c;它就是一些系列编程接口&#xff0c;有了这些接口&#xff0c;就可以改变页面内容&#xff0c;结构和样式 名称描述DOM文档对象模型(Document Object…

SpringBoot04-SpringBoot配置文件

4.Springboot配置文件 4.1配置文件 SpringBoot使用一个全局的配置文件 &#xff0c; 配置文件名称是固定的 application.properties 语法结构 &#xff1a;keyvalue server.port8081application.yaml 语法结构 &#xff1a;key:空格value server:port: 80814.2yaml概述 YAML…

scratch计算台阶 2024年9月scratch四级真题 中国电子学会 图形化编程 scratch四级真题和答案解析

目录 scratch计算台阶 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、p…

什么是量子计算机?

量子计算机是一种利用量子力学原理进行计算的新型计算机。 一、工作原理 传统计算机使用二进制位&#xff08;比特&#xff09;来存储和处理信息&#xff0c;每个比特只能处于 0 或 1 两种状态之一。而量子计算机使用量子比特&#xff0c;量子比特可以同时处于 0 和 1 的叠加…

DevOps业务价值流:需求设计最佳实践

DevOps实践正推动着产品快速迭代与高质量交付&#xff0c;但需求设计作为产品开发的关键起点&#xff0c;往往被忽视。它不仅是收集与分析需求的过程&#xff0c;更是将需求转化为可实施产品特性的核心。本文深入探讨DevOps业务价值流中的需求设计&#xff0c;从调研、整理、原…