SpringBoot 3.2.0 基于Spring Security+JWT实现动态鉴权

依赖版本

  • JDK 17
  • Spring Boot 3.2.0
  • Spring Security 6.2.0

工程源码:Gitee

为了能够不需要额外配置就能启动项目,看到配置效果。用例采用模拟数据,可自行修改为对应的ORM操作

编写Spring Security基础配置

导入依赖

<properties><java-jwt.version>4.4.0</java-jwt.version><guava.version>33.0.0-jre</guava.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>${java-jwt.version}</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>${guava.version}</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency>
</dependencies>

测试Spring Security

默认配置下,Spring Security form表单登录的用户名为user,密码启动时在控制台输出。

编写测试Controller

package com.yiyan.study.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** 测试接口*/
@RestController
public class SecurityController {@GetMapping("/hello")public String hello() {return "hello spring security";}
}

访问接口测试
springboot3-security-default-test

编写Spring Security基础文件

创建Spring Security模拟数据

package com.yiyan.study.config;import lombok.Getter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** Spring Security 模拟数据*/
public class SecurityConstant {/*** 模拟用户数据。key:用户名,value:密码*/public static final Map<String, String> USER_MAP = new ConcurrentHashMap<>();/*** 模拟权限数据。key:接口地址,value:所需权限*/public static final Map<String, ConfigAttribute> PERMISSION_MAP = new ConcurrentHashMap<>();/*** 用户权限数据。key:用户名,value:权限*/public static final Map<String, List<PERMISSION>> USER_PERMISSION_MAP = new ConcurrentHashMap<>();/*** 白名单*/public static final String[] WHITELIST = {"/login"};static {// 填充模拟用户数据USER_MAP.put("admin", "$2a$10$KOvypkjLRv/iJo/hU5GOSeFsoZzPYnh2B4r7LPI2x8yBTBZhPLkhy");USER_MAP.put("user", "$2a$10$KOvypkjLRv/iJo/hU5GOSeFsoZzPYnh2B4r7LPI2x8yBTBZhPLkhy");// 填充用户权限USER_PERMISSION_MAP.put("admin", List.of(PERMISSION.ADMIN, PERMISSION.USER));USER_PERMISSION_MAP.put("user", List.of(PERMISSION.USER));// 填充接口权限PERMISSION_MAP.put("/user", new SecurityConfig(PERMISSION.USER.getValue()));PERMISSION_MAP.put("/admin", new SecurityConfig(PERMISSION.ADMIN.getValue()));}/*** 模拟权限*/@Getterpublic enum PERMISSION {ADMIN("admin"), USER("user");private final String value;private PERMISSION(String value) {this.value = value;}}
}

实现 UserDetails

package com.yiyan.study.config;import lombok.Builder;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;/*** Spring Security用户信息*/
@Data
@Builder
public class SecurityUserDetails implements UserDetails {private String username;private String password;private List<SecurityConstant.PERMISSION> permissions;public SecurityUserDetails(String username, String password, List<SecurityConstant.PERMISSION> permissions) {this.username = username;this.password = password;this.permissions = permissions;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return permissions.stream().map(permission -> new SimpleGrantedAuthority(permission.getValue())).collect(Collectors.toList());}@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;}
}

实现UserDetailsService,重写loadUserByUsername()方法

package com.yiyan.study.config;import io.micrometer.common.util.StringUtils;
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.util.List;@Service
public class SecurityUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 获取用户信息String password = SecurityConstant.USER_MAP.get(username);if (StringUtils.isBlank(password)) {throw new UsernameNotFoundException("用户名或密码错误");}// 获取用户权限List<SecurityConstant.PERMISSION> permission = SecurityConstant.USER_PERMISSION_MAP.get(username);// 返回SecurityUserDetailsreturn SecurityUserDetails.builder().username(username).password(password).permissions(permission).build();}
}

创建自定义过滤器,用于实现对TOKEN进行鉴权

JWT工具类

package com.yiyan.study.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;import java.util.Collections;
import java.util.Date;
import java.util.List;/*** JWT工具类*/
public class JwtUtils {/*** 默认JWT标签头*/public static final String HEADER = "Authorization";/*** JWT配置信息*/private static JwtConfig jwtConfig;private JwtUtils() {}/*** 初始化参数** @param header         JWT标签头* @param tokenHead      Token头* @param issuer         签发者* @param secretKey      密钥 最小长度:4* @param expirationTime Token过期时间 单位:秒* @param issuers        签发者列表 校验签发者时使用* @param audience       接受者*/public static void initialize(String header, String tokenHead, String issuer, String secretKey, long expirationTime, List<String> issuers, String audience) {jwtConfig = new JwtConfig();jwtConfig.setHeader(StringUtils.isNotBlank(header) ? header : HEADER);jwtConfig.setTokenHead(tokenHead);jwtConfig.setIssuer(issuer);jwtConfig.setSecretKey(secretKey);jwtConfig.setExpirationTime(expirationTime);if (CollectionUtils.isEmpty(issuers)) {issuers = Collections.singletonList(issuer);}jwtConfig.setIssuers(issuers);jwtConfig.setAudience(audience);jwtConfig.setAlgorithm(Algorithm.HMAC256(jwtConfig.getSecretKey()));}/*** 初始化参数*/public static void initialize(String header, String issuer, String secretKey, long expirationTime) {initialize(header, null, issuer, secretKey, expirationTime, null, null);}/*** 初始化参数*/public static void initialize(String header, String tokenHead, String issuer, String secretKey, long expirationTime) {initialize(header, tokenHead, issuer, secretKey, expirationTime, null, null);}
​
​/*** 生成 Token** @param subject 主题* @return Token*/public static String generateToken(String subject) {return generateToken(subject, jwtConfig.getExpirationTime());}/*** 生成 Token** @param subject        主题* @param expirationTime 过期时间* @return Token*/public static String generateToken(String subject, long expirationTime) {Date now = new Date();Date expiration = new Date(now.getTime() + expirationTime * 1000);return JWT.create().withSubject(subject).withIssuer(jwtConfig.getIssuer()).withAudience(jwtConfig.getAudience()).withIssuedAt(now).withExpiresAt(expiration).sign(jwtConfig.getAlgorithm());}/*** 获取Token数据体*/public static String getTokenContent(String token) {if (StringUtils.isNotBlank(jwtConfig.getTokenHead())) {token = token.substring(jwtConfig.getTokenHead().length()).trim();}return token;}/*** 验证 Token** @param token token* @return 验证通过返回true,否则返回false*/public static boolean isValidToken(String token) {try {token = getTokenContent(token);Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecretKey());JWTVerifier verifier = JWT.require(algorithm).build();verifier.verify(token);return true;} catch (JWTVerificationException exception) {// Token验证失败return false;}}/*** 判断Token是否过期** @param token token* @return 过期返回true,否则返回false*/public static boolean isTokenExpired(String token) {try {token = getTokenContent(token);Algorithm algorithm = Algorithm.HMAC256(jwtConfig.secretKey);JWTVerifier verifier = JWT.require(algorithm).build();verifier.verify(token);Date expirationDate = JWT.decode(token).getExpiresAt();return expirationDate != null && expirationDate.before(new Date());} catch (JWTVerificationException exception) {// Token验证失败return false;}}/*** 获取 Token 中的主题** @param token token* @return 主题*/public static String getSubject(String token) {token = getTokenContent(token);return JWT.decode(token).getSubject();}/*** 获取当前Jwt配置信息*/public static JwtConfig getCurrentConfig() {return jwtConfig;}@Datapublic static class JwtConfig {/*** JwtToken Header标签*/private String header;/*** Token头*/private String tokenHead;/*** 签发者*/private String issuer;/*** 密钥*/private String secretKey;/*** Token 过期时间*/private long expirationTime;/*** 签发者列表*/private List<String> issuers;/*** 接受者*/private String audience;/*** 加密算法*/private Algorithm algorithm;}
}

配置JWT

application.yml 添加配置

server:port: 8080# ======== JWT配置 ========
jwt:secret: 1234567890123456expirationTime: 604800issuer: springboot3-securityheader: AuthorizationtokenHead: Bearer

配置JWT启动时加载配置项

package com.yiyan.study.config;
​
​
import com.yiyan.study.utils.JwtUtils;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** JWT 配置*/
@Slf4j
@Component
public class JwtConfig {@Value("${jwt.secret}")private String secretKey;@Value("${jwt.issuer}")private String issuer;@Value("${jwt.expirationTime}")private long expirationTime;@Value("${jwt.header}")private String header;@Value("${jwt.tokenHead}")private String tokenHead;@PostConstructpublic void jwtInit() {JwtUtils.initialize(header, tokenHead, issuer, secretKey, expirationTime);log.info("JwtUtils初始化完成");}
}

自定义拦截器

package com.yiyan.study.config;import com.yiyan.study.utils.JwtUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;/*** 自定义过滤器*/
@Component
public class MyAuthenticationFilter extends OncePerRequestFilter {@Resourceprivate SecurityUserDetailsService securityUserDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {String requestToken = request.getHeader(JwtUtils.getCurrentConfig().getHeader());// 读取请求头中的tokenif (StringUtils.isNotBlank(requestToken)) {// 判断token是否有效boolean verifyToken = JwtUtils.isValidToken(requestToken);if (!verifyToken) {filterChain.doFilter(request, response);}// 解析token中的用户信息String subject = JwtUtils.getSubject(requestToken);if (StringUtils.isNotBlank(subject) && SecurityContextHolder.getContext().getAuthentication() == null) {SecurityUserDetails userDetails = (SecurityUserDetails) securityUserDetailsService.loadUserByUsername(subject);// 保存用户信息到当前会话UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());// 将authentication填充到安全上下文authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}}filterChain.doFilter(request, response);}
}

修改Controller 的登录接口

package com.yiyan.study.controller;import com.yiyan.study.utils.JwtUtils;
import jakarta.annotation.Resource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** 测试接口*/
@RestController
public class SecurityController {@Resourceprivate AuthenticationManager authenticationManager;
​
​@GetMapping("/hello")public String hello() {return "hello spring security";}@GetMapping("/user")public String helloUser() {return "Hello User";}@GetMapping("/admin")public String helloAdmin() {return "Hello Admin";}@PostMapping("/login")public String doLogin(@RequestParam("username") String username,@RequestParam("password") String password) {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);Authentication authentication = authenticationManager.authenticate(authenticationToken);// 判断是否验证成功if (null == authentication) {throw new UsernameNotFoundException("用户名或密码错误");}return JwtUtils.generateToken(username);}
}

编写Spring Security配置文件

Spring Security 升级到6.x后,配置方式与前版本不同,多个旧的配置类被启用。新版本采用lambda表达式的方式进行配置,核心配置项没变化。

package com.yiyan.study.config;import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;/*** Spring Security配置类*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {@Resourceprivate UserDetailsService userDetailsService;@Resourceprivate MyAuthenticationFilter myAuthenticationFilter;/*** 鉴权管理类*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}/*** 加密类*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** Spring Security 过滤链*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http// 禁用明文验证.httpBasic(AbstractHttpConfigurer::disable)// 关闭csrf.csrf(AbstractHttpConfigurer::disable)// 禁用默认登录页.formLogin(AbstractHttpConfigurer::disable)// 禁用默认登出页.logout(AbstractHttpConfigurer::disable)// 禁用session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 配置拦截信息.authorizeHttpRequests(authorization -> authorization// 允许所有的OPTIONS请求.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 放行白名单.requestMatchers(SecurityConstant.WHITELIST).permitAll()// 根据接口所需权限进行动态鉴权.anyRequest().access((authentication, object) -> {// 获取当前的访问路径String requestURI = object.getRequest().getRequestURI();PathMatcher pathMatcher = new AntPathMatcher();// 白名单请求直接放行for (String url : SecurityConstant.WHITELIST) {if (pathMatcher.match(url, requestURI)) {return new AuthorizationDecision(true);}}// 获取访问该路径所需权限Map<String, ConfigAttribute> permissionMap = SecurityConstant.PERMISSION_MAP;List<ConfigAttribute> apiNeedPermissions = new ArrayList<>();for (Map.Entry<String, ConfigAttribute> config : permissionMap.entrySet()) {if (pathMatcher.match(config.getKey(), requestURI)) {apiNeedPermissions.add(config.getValue());}}// 如果接口没有配置权限则直接放行if (apiNeedPermissions.isEmpty()) {return new AuthorizationDecision(true);}// 获取当前登录用户权限信息Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();// 判断当前用户是否有足够的权限访问for (ConfigAttribute configAttribute : apiNeedPermissions) {// 将访问所需资源和用户拥有资源进行比对String needAuthority = configAttribute.getAttribute();for (GrantedAuthority grantedAuthority : authorities) {if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {// 权限匹配放行return new AuthorizationDecision(true);}}}return new AuthorizationDecision(false);}))// 注册重写后的UserDetailsService实现.userDetailsService(userDetailsService)// 注册自定义拦截器.addFilterBefore(myAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).build();}
}

测试

在这里插入图片描述

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

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

相关文章

java练习之abstract (抽象) final(最终) static(静态) 练习

1&#xff1a;分析总结&#xff1a;写出private、abstract、static、final之间能否联动使用&#xff0c;并写出分析原因 private static final 之间可以任意结合 abstract 不可以与private static final 结合使用 2&#xff1a;关于三个修饰符描述不正确的是(AD) A. static …

Linux操作系统基础知识点

Linux是一种计算机操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年首次发布。Linux操作系统通常与GNU套件一起使用&#xff0c;因此也被称为GNU/Linux。它是一种类UNIX的操作系统&#xff0c;设计为多用户、多任务…

计算机组成原理综合6

补码表示&#xff1a; X&#xff1a;1111 1111 1111 1101 Y&#xff1a;1111 1111 1101 1111 Z&#xff1a;0111 1111 1111 1100 转原码表示&#xff1a;从右往左找第一个“1”&#xff0c;左边的所有数值位按位取反 X&#xff1a;1111 1111 1111 1101 1000 0000 00…

OGG-MySQL无法正常同步数据问题分析

问题背景: 用户通过OGG从源端一个MySQL从库将数据同步到目标端的另一个MySQL数据库里面&#xff0c;后面由于源端的从库出现了长时间的同步延时&#xff0c;由于延时差距过大最后选择通过重建从库方式进行了修复 从库重建之后&#xff0c;源端的OGG出现了报错ERROR OGG-0014…

关于Sneaky DogeRAT特洛伊木马病毒网络攻击的动态情报

一、基本内容 作为复杂恶意软件活动的一部分&#xff0c;一种名为DogeRAT的新开源远程访问特洛伊木马&#xff08;RAT&#xff09;主要针对位于印度的安卓用户发动了网络安全攻击。该恶意软件通过分享Opera Mini、OpenAI ChatGOT以及YouTube、Netfilx和Instagram的高级版本等合…

《PySpark大数据分析实战》-19.NumPy介绍ndarray介绍

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

饥荒Mod 开发(二三):显示物品栏详细信息

饥荒Mod 开发(二二)&#xff1a;显示物品信息 源码 前一篇介绍了如何获取 鼠标悬浮物品的信息&#xff0c;这一片介绍如何获取 物品栏的详细信息。 拦截 inventorybar 和 itemtile等设置字符串方法 在modmain.lua 文件中放入下面代码即可实现鼠标悬浮到 物品栏显示物品详细信…

适合引流源码声音鉴定神器网站源码,轻松吸引用户关注

源码介绍 声鉴卡HTML5网页源码&#xff0c;完整可运转&#xff0c;调用wx录音&#xff0c;自动判断声音属性&#xff0c;输出结果 安装教程 只需要把源码上传至主机空间就可以 支持上传二级目录访问&#xff01;提示一下&#xff1a;wxvx打开效果是最佳的源码里面生成二维码…

测试服务器带宽(ubuntu)

apt install python3 python3-pippip3 install speedtest-clispeestest-cli

Hive05_DML 操作

1 DML 数据操作 1.1 数据导入 1.1.1 向表中装载数据&#xff08;Load&#xff09; 1&#xff09;语法 hive> load data [local] inpath 数据的 path [overwrite] into table student [partition (partcol1val1,…)];&#xff08;1&#xff09;load data:表示加载数据 &…

Redis数据结构(常用5+4种特殊数据类型)

1、Redis 数据类型以及使用场景分别是什么&#xff1f; Redis 提供了丰富的数据类型&#xff0c;常见的有五种数据类型&#xff1a;String&#xff08;字符串&#xff09;&#xff0c;Hash&#xff08;哈希&#xff09;&#xff0c;List&#xff08;列表&#xff09;&#xff…

119. 杨辉三角 II(Java)

给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1]示例 2: 输入: rowIndex 0 输出: [1]示例 3: 输入: rowIndex 1 输出: [1,1]提示…

通过自然语言处理增强推荐系统:协同方法

一、介绍 自然语言处理 (NLP) 是人工智能的一个分支&#xff0c;专注于使机器能够以有意义且有用的方式理解、解释和响应人类语言。它包含一系列技术&#xff0c;包括情感分析、语言翻译和聊天机器人。 另一方面&#xff0c;推荐系统&#xff08;RecSys&#xff09;是旨在向用户…

Android笔记(二十一):Room组件实现Android应用的持久化处理

一、Room组件概述 Room是Android JetPack架构组件之一&#xff0c;是一个持久处理的库。Room提供了在SQLite数据库上提供抽象层&#xff0c;使之实现数据访问。 &#xff08;1&#xff09;实体类&#xff08;Entity&#xff09;&#xff1a;映射并封装了数据库对应的数据表中…

彻底卸载Keil4

彻底卸载Keil4 双击 然后回到该软件的文件夹位置&#xff0c;把该文件夹删除即可&#xff0c;然后清一下回收站。

【Midjourney】Midjourney提示词格式详解

目录 &#x1f347;&#x1f347;Midjourney是什么&#xff1f; &#x1f349;&#x1f349;Midjourney怎么用&#xff1f; &#x1f514;&#x1f514;Midjourney提示词格式 &#x1f341; 1.模型版本提示词&#x1f341; 参数 参数详解 应用示例 &#x1f343; 2.风格…

基于双闭环PI的SMO无速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于双闭环PI的SMO无速度控制系统simulink建模与仿真&#xff0c;基于双闭环PI的SMO无速度控制系统主要由两个闭环组成&#xff1a;一个是电流环&#xff0c;另一个是速度环。…

AssertionError: The environment must specify an action space. 报错 引发的惨案

起因是&#xff1a;从github上下载了一个代码&#xff0c;运行出错。 整体流程&#xff1a; 1. AssertionError: The environment must specify an action space. 报错&#xff0c;解决方案是 降级gym到 gym0.18.0 2.为了降级gym gym0.18.0 报错&#xff0c;发现需要降级 setup…

k8s实战之ELK日志管理

首先查看总体流程 首先创建namespace apiVersion: v1 kind: Namespace metadata:name: kube-logging 一、首先创建es.yaml --- apiVersion: v1 #kubernetes API版本,采用最新版本v1 kind: Service #资源类型定义为Service metadata: name: elasticsearch-logging # …

vue3 全局配置Axios实例

目录 前言 配置Axios实例 页面使用 总结 前言 Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于浏览器和 Node.js 环境。它提供了一种简单、一致的 API 来处理HTTP请求&#xff0c;支持请求和响应的拦截、转换、取消请求等功能。关于它的作用&#xff1a; 发起 HTTP …