用户登陆实现前后端JWT鉴权

目录

一、JWT介绍

二、前端配置

三、后端配置

四、实战


一、JWT介绍

1.1 什么是jwt

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间以安全的方式传输信息。JWT 是一种紧凑、自包含的信息载体,可以被解码和验证。它通常用于身份验证和授权服务,特别是在无状态的 Web 应用程序中,比如那些基于 REST 的 API。

1.2 jwt的结构

JWT 由三部分组成,每一部分都由点号(.)分隔开:

  1. 头部 (Header): 包含关于类型和签名算法的信息。例如:

{"alg":"HS256","typ":"JWT"}

这个头部通常表明使用 HMAC SHA-256 算法签名。

  • 负载 (Payload): 也称为“声明”(Claims),包含了要传输的信息。这些信息可以是任意的 JSON 数据,但通常包括一些标准的字段,例如:

{"sub":"1234567890","name":"John Doe","admin":true}

这里 "sub" 是主题(Subject),"name" 是姓名,"admin" 是权限声明。

  • 签名 (Signature): 用于验证数据的完整性和确认发送者的身份。签名是通过一个密钥对头部和负载进行加密得到的。

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret
)

如果使用非对称加密,则密钥可以是公钥或私钥。

1.3 jwt工作流程

  1. 创建和签发: 服务器创建一个 JWT,其中包含用户的身份信息和/或其他数据,然后使用一个秘密密钥或私钥对其进行签名。

  2. 传输: JWT 通过网络发送给客户端,通常作为 HTTP Authorization header 的一部分。

  3. 验证和使用: 当客户端向服务器发送请求时,它将 JWT 作为身份验证的一部分。服务器验证 JWT 的签名,以确保它没有被篡改,并从中读取信息。

  4. 过期: JWT 可以设置一个过期时间,在此之后,它将不再有效。

下面将通过Vue + SpringBoot 实现一个jwt鉴权的项目


二、前端配置

2.1 引入axios

npm install axios

通过添加前端拦截器配置axios

在src下创建一个utils包,再创建一个axios.js文件

import axios from 'axios';// 创建axios实例
const instance = axios.create();// 添加请求拦截器
instance.interceptors.request.use(function (config) {// 在这里添加token到请求头const token = localStorage.getItem('token') || ''; // 从本地存储获取tokenif (token) {config.headers.Authorization = `${token}`;}return config;},function (error) {// 请求错误时的处理return Promise.reject(error);}
);export default instance;

 在main.js中配置应用axios

import axios from './utils/axios';Vue.prototype.$axios = axios;

 

2.3 使用axios

在配置全局后,使用axios就并不需要单独引入axios了,直接使用this.$axios即可调用

 this.$axios.get('/api/forum/getAllForumPost', {params: {pageSize: 1,pageNumber: 10}}).then((response) => {console.log(response.data.data);this.posts = response.data.data;});

创建一个TestView.vue测试发送请求时候是否会携带请求头

<template><div><!-- 测试是否会携带请求头 --><button @click="Test"> 发送测试</button></div>
</template><script>
export default {data() {return {};},methods: {Test(){// 假设有登录成功后的tokenlocalStorage.setItem('token', '1234567890');this.$axios.get('/api/Test').then((response) => {console.log(response.data.data);});}},};
</script>

在控制台的网络中查看是否有对应的请求头

已经成功携带,并且名称为Authorization


三、后端配置

3.1 引入依赖

<!--        JWT依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version></dependency>

3.3 由于jwt需要三个属性 密钥 有效期 Token的名称

所以需要配置对应的资源类

@Component
@ConfigurationProperties(prefix = "paitool.jwt")
@Data
public class JwtProperties {private String SecretKey;private long Ttl;private String TokenName;}

application.yml:

paitool:jwt:secret-key: Alphamilkttl: 10800000token-name: Authorization

3.4 创建配置Jwt的工具类 实现快速创建Jwt与解密Jwt方法

public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims    设置的信息* @return*/public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);return builder.compact();}/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token     加密后的token* @return*/public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}}

3.5  通过ThreadLocal实现后端存储用户信息

public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}

3.6 配置jwt的拦截器

注意:这里的HandlerMehtod是org.springframework.web.method包下的

@Component
@Slf4j
public class JwtTokenInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), token);// 获取JWT的过期时间并转换为可读格式Date expirationDate = claims.getExpiration();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String formattedExpiration = sdf.format(expirationDate);log.info("JWT过期时间:{}", formattedExpiration);Long userId = Long.valueOf(claims.get("userId").toString());log.info("当前用户id:", userId);//通过ThreadLocal保存员工idBaseContext.setCurrentId(userId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

3.7 将配置好的拦截器加入到webMvc配置中(由于本次实战通过用户登陆获取token,记得排除用户登陆时候进行校验的过程)

@Configuration
@Slf4j
public class WebMvcConfig extends WebMvcConfigurationSupport {@Autowiredprivate JwtTokenInterceptor jwtTokenInterceptor;@Overrideprotected void addInterceptors(InterceptorRegistry registry) {log.info("开始注册自定义拦截器...");registry.addInterceptor(jwtTokenInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login").excludePathPatterns("/user/GetCaptcha");}

四、实战

1.创建User表单

create table paitool.user
(id                int auto_incrementprimary key,account           varchar(255)                                          not null,password          varchar(255)                                          not null,phone             varchar(20)                                           null,address           varchar(255)                                          null,isVip             tinyint(1)                  default 0                 null,email             varchar(255)                                          null,registration_date datetime                    default CURRENT_TIMESTAMP null,last_login        datetime                                              null,status            enum ('active', 'inactive') default 'active'          null,constraint account_UNIQUEunique (account),constraint email_UNIQUEunique (email),constraint phone_UNIQUEunique (phone)
);

通过MyBatisPlusX自动生成架构

 2.创建返回结果实体类

//结果类
public class Result<T> {// 状态码常量public static final int SUCCESS = 200;public static final int ERROR = 500;private int code; // 状态码private String message; // 消息private T data; // 数据// 构造函数,用于创建成功的结果对象private Result(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}// 成功结果的静态方法public static <T> Result<T> success(T data) {return new Result<>(SUCCESS, "Success", data);}// 错误结果的静态方法public static <T> Result<T> error(String message) {return new Result<>(ERROR, message, null);}// 错误结果的静态方法,可以传入自定义的状态码public static <T> Result<T> error(int code, String message) {return new Result<>(code, message, null);}// 获取状态码public int getCode() {return code;}// 设置状态码public void setCode(int code) {this.code = code;}// 获取消息public String getMessage() {return message;}// 设置消息public void setMessage(String message) {this.message = message;}// 获取数据public T getData() {return data;}// 设置数据public void setData(T data) {this.data = data;}// 用于转换为Map类型的方法,方便序列化为JSONpublic Map<String, Object> toMap() {Map<String, Object> map = new HashMap<>();map.put("code", code);map.put("message", message);map.put("data", data);return map;}
}

3.创建验证码(防止密码爆破)工具类 与 Md5加密与解密工具类(防止数据库密码信息泄露)

public class CaptchaUtil {private static final int WIDTH = 200;private static final int HEIGHT = 75;private static final int FONT_SIZE = 36;private static final String DEFAULT_FONT = "Arial";/*** 生成验证码图像.** @param captchaText 验证码原始文本* @return Base64编码的图像字符串*/public static String generateCaptchaImage(String captchaText) {if (captchaText == null || captchaText.isEmpty()) {throw new IllegalArgumentException("Captcha text cannot be null or empty.");}// 创建图像和图形上下文BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);Graphics2D g = (Graphics2D) image.getGraphics();// 设置背景颜色g.setColor(Color.WHITE);g.fillRect(0, 0, WIDTH, HEIGHT);// 绘制验证码文本g.setFont(new Font(DEFAULT_FONT, Font.BOLD, FONT_SIZE));g.setColor(getRandomColor());g.drawString(captchaText, 45, 50);// 添加随机线条作为干扰addNoiseLines(g);// 关闭图形上下文g.dispose();// 将图像转换为Base64编码的字符串try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {ImageIO.write(image, "png", baos);return Base64.getEncoder().encodeToString(baos.toByteArray());} catch (Exception e) {throw new RuntimeException("Error generating captcha image", e);}}private static void addNoiseLines(Graphics2D g) {for (int i = 0; i < 5; i++) {g.setColor(getRandomColor());g.drawLine(getRandomNumber(WIDTH),getRandomNumber(HEIGHT),getRandomNumber(WIDTH),getRandomNumber(HEIGHT));}}private static Color getRandomColor() {return new Color((int) (Math.random() * 255),(int) (Math.random() * 255),(int) (Math.random() * 255));}private static int getRandomNumber(int bound) {return (int) (Math.random() * bound);}
}
public final class MD5Util {/*** 使用MD5算法对字符串进行加密。** @param input 待加密的字符串* @return 加密后的MD5散列值字符串*/public static String encryptToMD5(String input) {try {MessageDigest md = MessageDigest.getInstance("MD5");byte[] hashInBytes = md.digest(input.getBytes());// 将字节数组转换成十六进制字符串StringBuilder sb = new StringBuilder();for (byte b : hashInBytes) {sb.append(String.format("%02x", b));}return sb.toString();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("MD5 algorithm not found", e);}}public static void main(String[] args) {String originalString = "Hello World";String encryptedString = encryptToMD5(originalString);System.out.println("Original: " + originalString);System.out.println("Encrypted: " + encryptedString);}
}

 4.创建数据传输与视图的实体类

登陆时候,前端传入数据

@Data
public class LoginDTO {private String account;private String password;//    验证码private String captcha;}

 验证通过后传给前端的数据

@Data
public class loginVo {private Integer id;private String account;private Integer isvip;private Object status;private String token;}

4.UserController实现登陆功能

@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {@AutowiredUserService userService;@Autowiredprivate JwtProperties jwtProperties;// 登陆时候获取验证码@ApiOperation("获取验证码功能")@GetMapping("/GetCaptcha")public String GetCaptcha(HttpSession session) {//        随机生成四位验证码原始数据String allowedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";String randomString = generateRandomString(allowedChars, 4);System.out.println("captchaCode " + randomString);// 将验证码保存到session中session.setAttribute("captcha", randomString); // 使用方法参数sessionString ImageByBase64 = CaptchaUtil.generateCaptchaImage(randomString);return ImageByBase64;}// 实现登陆功能@ApiOperation("用户登陆功能")@PostMapping("/login")public Result<loginVo> Login(@RequestBody LoginDTO loginDTO, HttpSession session) { // 使用同一个HttpSession参数String captcha = (String) session.getAttribute("captcha");log.info("用户调用login方法");if (loginDTO.getCaptcha() == null || !loginDTO.getCaptcha().equalsIgnoreCase(captcha)) {session.removeAttribute("captcha");return Result.error("验证码出错了噢!");}// 对密码进行md5加密String encryptToMD5 = MD5Util.encryptToMD5(loginDTO.getPassword());LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getAccount, loginDTO.getAccount()).eq(User::getPassword, encryptToMD5);User user = userService.getOne(lambdaQueryWrapper);if (user == null) {return Result.error("很抱歉,查不到此用户");}loginVo loginVo = new loginVo();BeanUtils.copyProperties(user,loginVo);Map<String,Object> claims = new HashMap<>();claims.put("userId",user.getId());String token = JwtUtil.createJWT(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims);loginVo.setToken(token);return Result.success(loginVo);}
}

前端账户操作View.vue:

<template><div id="Header"><h3>--PaiTool--</h3><div class="header-avatar"><el-popover placement="bottom" :visible-arrow="false" :visible.sync="showUserInfo"><div class="userInfo"><p>用户名:{{ account }}</p><p>邮箱:{{ email }}</p><p>是否是vip: {{ isVip }}</p><p>账号状态:{{ status }}</p><!-- 登录按钮 --><el-button type="primary" @click="showDialog">登录/注册</el-button><!-- 退出按钮 --><el-button type="text" @click="confirmQuit">退出</el-button><!-- 登录对话框 --><el-dialog title="登录与注册" :visible.sync="dialogLoginVisible" width="30%" @close="resetLoginForm" append-to-body:modal-append-to-body="false"><el-tabs v-model="activeName" @tab-click="handleClick"><el-tab-pane label="登陆" name="first"><el-form :model="loginForm" ref="loginFormRef" label-width="80px"><el-form-item label="用户名:"><el-input v-model="loginForm.account"></el-input></el-form-item><el-form-item label="密码:"><el-input v-model="loginForm.password" show-password></el-input></el-form-item><el-form-item label="验证码"><el-input v-model="loginForm.captcha" style="width: 20%;"></el-input><img :src="captchaImageUrl" alt="验证码" @click="refreshCaptcha" id="captchaImage"></el-form-item></el-form></el-tab-pane><el-tab-pane label="注册" name="second"><el-form :model="loginForm" ref="registerFormRef" label-width="80px"><el-form-item label="注册用户:"><el-input v-model="registerFormRef.account"></el-input></el-form-item><el-form-item label="注册密码:"><el-input v-model="registerFormRef.password" show-password></el-input></el-form-item><el-form-item label="验证码"><el-input v-model="registerFormRef.captcha" style="width: 20%;"></el-input><img :src="captchaImageUrl" alt="验证码" @click="refreshCaptcha" id="captchaImage"></el-form-item></el-form></el-tab-pane></el-tabs><span slot="footer" class="dialog-footer"><el-button @click="dialogLoginVisible = false">取消</el-button><el-button type="primary" @click="submitLogin">登录|注册</el-button></span></el-dialog><!-- 退出确认对话框 --><el-dialog title="确认退出" :visible.sync="dialogConfirmVisible" width="30%" @close="dialogConfirmVisible = false"append-to-body :modal-append-to-body="false"><span>您确定要退出吗?</span><span slot="footer" class="dialog-footer"><el-button @click="dialogConfirmVisible = false">取消</el-button><el-button type="primary" @click="quit">确定退出</el-button></span></el-dialog></div><el-avatar slot="reference" :src="circleUrl" :size="40" class="clickable-avatar"></el-avatar></el-popover></div></div>
</template><script>
import axios from 'axios';
import Cookies from 'js-cookie';export default {data() {return {showUserInfo: false, // 控制个人信息弹窗的显示状态circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",isVip: '否',account: '未登录',status: '正常',email: 'none',activeName: 'first',loginOrRegistFlag: true,dialogLoginVisible: false,dialogConfirmVisible: false,loginForm: {username: '',password: '',},registerFormRef: {username: '',password: '',},captchaImageUrl: '', // 初始化为一个空字符串}},mounted() {this.loadUserDataFromCookie();},methods: {loadUserDataFromCookie() {// 从cookie中读取accountconst account = Cookies.get('account');if (account) {this.account = account;}// 从cookie中读取isVipconst isVip = Cookies.get('isVip');if (isVip !== undefined) {// 注意:从cookie读取的数据是字符串类型,需要转换成布尔型this.isVip = isVip === 'true';}// 从cookie中读取statusconst status = Cookies.get('status');if (status) {this.status = status;}// 从cookie中读取emailconst email = Cookies.get('email');if (email) {this.email = email;}},// 打开登录对话框open() {this.dialogLoginVisible = true;},resetLoginForm() {this.$refs.loginFormRef.resetFields();},// 提交登录submitLogin() {// 判断是注册还是登录if (this.loginOrRegistFlag == true) {// 这里添加验证逻辑(如果需要)console.log('登录表单提交:', this.loginForm);this.dialogLoginVisible = false;// 将this.loginForm作为参数上传axios.post("/api/user/login", this.loginForm).then(response => {console.log(response.data);if (response.data.code === 500) {// 重新获取验证码this.refreshCaptcha();this.$message.error(response.data.message);} else if (response.data.code === 200) {this.$message({showClose: true,message: '登陆成功!',type: 'success'});// 设置cookie,可以设置过期时间Cookies.set('account', response.data.data.account, { expires: 7 });Cookies.set('isVip', response.data.data.isVip, { expires: 7 });Cookies.set('status', response.data.data.status, { expires: 7 });Cookies.set('email', response.data.data.email, { expires: 7 });Cookies.set('userId', response.data.data.id, { expires: 7 })localStorage.setItem('token', response.data.data.token);this.account = response.data.data.account;this.isVip = response.data.data.isVip;this.status = response.data.data.status;this.email = response.data.data.email;}}).catch(error => {// 处理错误响应console.error('登录失败:', error);this.$message.error('登陆错了哦,这是一条错误消息')});} else {axios.post('/api/user/register', this.registerFormRef).then(response => {if (response.data.code === 200) {this.$message({showClose: true,message: '注册成功!',type: 'success'});this.dialogLoginVisible = false;} else {this.$message.error(response.data.message);}});}},// 打开退出确认对话框confirmQuit() {this.dialogConfirmVisible = true;},// 执行退出操作quit() {// 这里执行实际的退出逻辑console.log('执行退出操作');this.dialogConfirmVisible = false;// 将Cookie所有字段删除Cookies.remove('account');Cookies.remove('isVip');Cookies.remove('status');Cookies.remove('email');Cookies.remove('userId');this.account = '未登录';this.isVip = '否';this.status = '离线';this.email = 'none';this.$message({showClose: true,message: '退出成功!',type: 'success'});},// 刷新验证码的示例函数refreshCaptcha() {// 实现刷新验证码的逻辑console.log('刷新验证码');this.fetchCaptcha();},fetchCaptcha() {axios.get('/api/user/GetCaptcha').then(response => {this.captchaImageUrl = 'data:image/png;base64,' + response.data;}).catch(error => {console.error('获取验证码失败:', error);});},showDialog() {this.fetchCaptcha(); // 先获取验证码this.dialogLoginVisible = true; // 然后显示登录对话框},handleClick(tab) {if (tab.name === 'first') {this.loginOrRegistFlag = true;} else {this.loginOrRegistFlag = false;}}}
}
</script><style scoped>
h3 {color: #E9EEF3;float: left;width: 1307px;height: 60px;margin-left: 15%;
}.header-avatar {position: relative;/* 为绝对定位的子元素提供上下文 */float: right;z-index: 1000;/* 设置一个较高的 z-index 值以确保其位于其他元素之上 */margin-top: 10px;
}.clickable-avatar {/* 添加点击手势效果 */cursor: pointer;
}.userInfo {text-align: left;padding: 10px;
}#captchaImage {cursor: pointer;width: 136px;height: 45px;border: 1px solid black;float: right;margin-right: 54%;
}
</style>

 数据库创建用户与(123456)加密后的密码

account: admin

password: e10adc3949ba59abbe56e057f20f883e

进入前端并进行登陆

 查看返回结果的token,前端的login函数已经自动存入了token中了

使用其它功能,查看是否有效

这里看到,后端正常识别到并解析出来了。


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

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

相关文章

【Android面试八股文】组件化在项目中有什么意义?

一、没有组件化会出现什么问题? 早期的单一分层模式 问题一:无论分包怎么做,随着项目增大,项目失去层次感,后面接手的人扑街问题二:包名约束太弱,稍有不注意,就会不同业务包直接互相调用,代码高耦合问题三:多人开发在版本管理中,容易出现代码覆盖冲突等问题二、组件…

【Linux】Linux的账号和用户组

管理员的工作中&#xff0c;相当重要的一环就是【管理账号】。 因为整个系统都是你在管理&#xff0c;并且所有一般用户的账号申请&#xff0c;都必须要通过你的协助才行&#xff0c;所以你就必须要了解一下如何管理好一个服务器主机的账号。 在管理Linux主机的账号时&#xff…

Django 删除单行数据

1&#xff0c;添加模型 from django.db import modelsclass Post(models.Model):title models.CharField(max_length200)content models.TextField()pub_date models.DateTimeField(date published)class Book(models.Model):title models.CharField(max_length100)author…

121. 小红的区间翻转(卡码网周赛第二十五期(23年B站笔试真题))

题目链接 121. 小红的区间翻转&#xff08;卡码网周赛第二十五期&#xff08;23年B站笔试真题&#xff09;&#xff09; 题目描述 小红拿到了两个长度为 n 的数组 a 和 b&#xff0c;她仅可以执行一次以下翻转操作&#xff1a;选择a数组中的一个区间[i, j]&#xff0c;&#x…

顺序表算法 - 移除元素

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/remove-element/description/思路: 代码: // numsSize表示数组的长度 …

网络安全——防御课实验二

在实验一的基础上&#xff0c;完成7-11题 拓扑图 7、办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 首先&#xff0c;按照之前的操作&#xff0c;创建新的安全区&#xff08;电信和移动&#xff09;分别表示两个外网…

Readiris PDF Corporate / Business v23 解锁版安装教程 (PDF管理软件)

前言 Readiris PDF Corporate / Business 是一款高性能的 OCR&#xff08;光学字符识别&#xff09;软件&#xff0c;能够帮助用户将纸质文档、PDF 文件或图像文件转换为可编辑和可搜索的电子文本。该软件提供专业级的功能和特性&#xff0c;非常适合企业和商业使用。使用 Rea…

基于lstm的股票Volume预测

LSTM&#xff08;Long Short-Term Memory&#xff09;神经网络模型是一种特殊的循环神经网络&#xff08;RNN&#xff09;&#xff0c;它在处理长期依赖关系方面表现出色&#xff0c;尤其适用于时间序列预测、自然语言处理&#xff08;NLP&#xff09;和语音识别等领域。以下是…

LabVIEW人工模拟肺控制系统开发

开发了一种创新的主被动一体式人工模拟肺模型&#xff0c;通过LabVIEW开发的上位机软件&#xff0c;实现了步进电机驱动系统的精确控制和多种呼吸模式的模拟。该系统不仅能够在主动呼吸模式下精确模拟快速呼吸、平静呼吸和深度呼吸&#xff0c;还能在被动模式下通过PID控制实现…

C++20中的consteval说明符

在C20中&#xff0c;立即函数(immediate function)是指每次调用该函数都会直接或间接产生编译时常量表达式(constant expression)的函数。这些函数在其返回类型前使用consteval关键字进行声明。 立即函数是constexpr函数&#xff0c;具体情况取决于其要求。与constexpr相同&…

神经网络以及简单的神经网络模型实现

神经网络基本概念&#xff1a; 神经元&#xff08;Neuron&#xff09;&#xff1a; 神经网络的基本单元&#xff0c;接收输入&#xff0c;应用权重并通过激活函数生成输出。 层&#xff08;Layer&#xff09;&#xff1a; 神经网络由多层神经元组成。常见的层包括输入层、隐藏层…

springboot健身房预约管理系统-计算机毕业设计源码75535

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.1.1经济可行性 2.1.2技术可行性 2.1.3操作可行性 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 …

Gradle学习-6 APT 实现一个路由跳转框架(APT、发布maven仓库)

Annotation 注解&#xff1a;注解是元数据&#xff0c;即描述数据的数据APT&#xff08;Annotation Processing Tool&#xff09;注解处理器 APT工作原理 Demo介绍 APT项目地址 使用APT maven仓库地址 &#xff08;1&#xff09;项目配置 Gradle 8.2AGP 8.2.0Java jdk 17…

调整网络安全策略以适应不断升级的威胁形势

关键网络安全统计数据和趋势 当今数字时代网络安全的重要性

Python爬虫速成之路(2):爬天气情况

hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;绝命Coding-CSDN博客 &a…

nginx的四层负载均衡实战

目录 1 环境准备 1.1 mysql 部署 1.2 nginx 部署 1.3 关闭防火墙和selinux 2 nginx配置 2.1 修改nginx主配置文件 2.2 创建stream配置文件 2.3 重启nginx 3 测试四层代理是否轮循成功 3.1 远程链接通过代理服务器访问 3.2 动图演示 4 四层反向代理算法介绍 4.1 轮询&#xff0…

docker 上传镜像到hub仓库

要将 Docker 镜像上传到 Docker Hub&#xff0c;你需要按照以下步骤操作&#xff1a; 登录 Docker Hub 首先&#xff0c;你需要登录到 Docker Hub。打开终端并运行以下命令&#xff1a;docker login系统会提示你输入 Docker Hub 的用户名和密码。 如果密码忘记可以token登录&a…

MySQL复合查询(重点)

前面我们讲解的mysql表的查询都是对一张表进行查询&#xff0c;在实际开发中这远远不够。 基本查询回顾 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J mysql> select * from emp where (sal>500 or jobMANAGER) and ename l…

【数据结构初阶】详解 环形链表:链表的带环问题(判断是否带环、环形链表的入口点)

文章目录 一、链表的带环问题1.1、判断链表是否带环&#xff08;力扣 141.环形链表&#xff09;1.2 、证明&#xff1a;为什么带环时快慢指针一定相遇&#xff1f;1.3、证明&#xff1a;当slow走1步&#xff0c;fast可走3/4/5步&#xff08;fast的速度是slow的3/4/5倍&#xff…

Open3d入门 一文读懂三维点云

三维点云技术的发展始于20世纪60年代&#xff0c;随着激光雷达和三维扫描技术的进步&#xff0c;在建筑、考古、地理信息系统和制造等领域得到了广泛应用。20世纪90年代&#xff0c;随着计算机处理能力的提升&#xff0c;点云数据的采集和处理变得更加高效&#xff0c;推动了自…