功能性的安全性保障:TOKEN鉴权校验

1. 引言

在软件开发过程中,确保系统的安全性是至关重要的一环。它不仅关乎保护用户数据的完整性和隐私性,也是维护系统稳定运行的基石。我认为,从宏观角度审视,软件开发的安全性保障主要可分为两大类:功能性的安全性保障和系统性的安全性校验。

功能性的安全性保障专注于应用程序层面,它着眼于那些直接影响用户数据和交互过程安全的特性。这些特性是构建用户信任和保障数据安全的关键。

而系统性的安全性校验则放眼于更为广阔的视角,它涵盖了整个系统架构和网络层面,确保从服务器到网络的每一环节都具备足够的防御能力,以抵御各种潜在的攻击。

在前文功能性的安全性保障:实现强制登录和密码加密功能,我们已经讨论了功能性的安全性保障中的两个核心议题:身份验证和密码加密。所以在本文中,我们将继续从功能性的安全性保障这个话题展开,通过对概念的细致解读、实施策略的全面分析,以及实际代码实现的展示,深入讨论其中的另外一个核心议题:TOKEN鉴权校验。

2. Token的定义

当我们准备使用Token进行鉴权校验时,首先得先了解Token是什么,我们才能扩展下去。这里简单讲下,所谓Token,是一种数据对象,通常由一组字符组成,主要用户身份验证和授权信息。通常所说的Token可以是多种形式,例如Session ID、Bearer Token、Access Token等。它们的安全性取决于如何生成、存储和传输。

3. JWT(Json Web Token):用户的身份和权限验证

在实际项目中,常用JWT作为Token来验证用户的身份和权限,确保请求的合法性。这种机制允许系统在不直接存储用户敏感信息的情况下,对用户进行身份验证和授权。

JSON Web Tokens (JWT) 是一种广泛采用的、用于在不同实体间安全传输信息的开放标准(RFC
7519)。它定义了一种紧凑且自包含的方式,允许通过JSON对象在通信双方之间传递安全性声明。这种机制不仅确保了信息的完整性和可验证性,还通过使用数字签名技术,保障了其安全性。

在JWT的框架下,令牌(Token)本身封装了一系列声明(Claims),这些声明可以被应用程序用来控制对资源的访问权限。这些声明包括但不限于用户的身份信息、权限级别以及令牌的有效期等。JWT的令牌通常采用JSON Web Signature (JWS) 进行签名,确保了其内容的不可篡改性和验证的可靠性。

3.1 JWT的构成

一个JWT由三部分组成,用点 . 分隔:

  • Header(头部):通常包含令牌的类型(即JWT)和所使用的签名算法(如HS256或RS256)。
  • Payload(负载):包含所谓的Claims(声明),它们是关于实体(通常是用户)和其他数据的声明。例如,用户ID、用户名、角色信息等。
  • Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。

3.2 签名机制

特别地,JWT的签名过程常采用RSA公钥-私钥对机制。这种方式不仅提供了强大的安全保障,还使得JWT在各种应用场景中,如单点登录(SSO)、移动应用认证等,都能发挥其独特的优势。通过这种方式,JWT成为了一种理想的解决方案,用于发布和验证接入令牌(Access Tokens),从而在不同的服务和应用程序之间实现安全、高效的用户认证和授权。

3.3 过期时间

JWT通常包含一个过期时间(exp),这有助于限制Token的生命周期,增加安全性。其他类型的Token可能也需要设置过期时间,但它们的管理方式可能不同。

4. Spring Security:基于令牌的认证机制

另外,还有个话题我们不得不关注,那就是Spring Security。Spring Security是一个功能强大且高度可自定义的认证和访问控制框架,是Spring项目的一部分。它为JVM(Java虚拟机)语言提供了一套全面的安全服务,主要用于保护基于Spring的应用程序。

简单来说,当用户尝试访问一个需要认证的网站或应用时,Spring Security会要求提供Token。只有提供了正确的Token,Spring Security才会让请求通过。

4.1 核心功能

  • 认证(Authentication)
    确认用户身份,通常通过用户名和密码,但也支持多种其他认证方法(如OAuth2、JWT、LDAP等)。
    提供内置的表单登录和HTTP基本认证。
  • 授权(Authorization,也称为访问控制)
    决定已认证的用户是否有权访问某些资源或执行某些操作。
    支持基于角色和权限的访问控制。
  • 保护Web应用程序
    通过声明式的URL访问配置,定义哪些URL需要认证,哪些URL可以匿名访问。
    支持防止常见的Web攻击,如CSRF(跨站请求伪造)、XSS(跨站脚本)等。
  • 方法级安全
    使用注解(如 @Secured, @PreAuthorize, @PostAuthorize)对方法调用进行访问控制。
    整合其他Spring项目
  • 无缝集成Spring Boot、Spring MVC、Spring Data等项目。

5. 代码实现:登录生成TOKEN,并使用TOKEN进行权限校验

5.1 前端代码实现

首先,我们基于之前开发的前端代码进行扩展,在主页创建一个头部导航栏、侧边栏。

<template><el-container style="height: 100vh;"><el-header style="padding: 0; background-color:#545c64; height: 60px;"><TopNavbar/></el-header><el-container style="flex: 1; display: flex;"><SideNavbar /><el-main style="flex: 1; display: flex; justify-content: center; align-items: center;"><router-view class="my"></router-view></el-main></el-container></el-container>
</template><script>
import TopNavbar from './TopNavbar.vue';
import SideNavbar from "@/views/SideNavbar.vue";export default {name: 'HomePage',components: {SideNavbar,TopNavbar,}
};
</script><style>
.el-main {padding: 0;
}
</style>

头部导航栏主要定义两个按钮,分别是登录按钮和登出按钮,主要为了实现对TOKEN的创建和销毁。

<template><div class="navbar"><div class="navbar-links"><div class="nav-item" v-if="true"><el-button @click="goToLogin">登录</el-button></div><div class="nav-item" v-if="true"><el-button @click="loginOut">退出</el-button></div></div></div>
</template>  <script >
import { ref } from 'vue';
export default {methods: {goToLogin() {this.$router.push({name: 'Login'});},loginOut() {fetch('http://localhost:8081/user/logout', {method: 'POST',headers: {Authorization: localStorage.getItem('token')}}).then(response => {if (response.ok) {localStorage.removeItem('token');this.$router.push('/login');}}).catch(error => {console.log(error);});}}
}
</script>
<style scoped>
.navbar {display: flex;justify-content: flex-end; /* 确保内容靠右对齐 */align-items: center; /* 垂直居中对齐 */width: 100%;
}.navbar-links {display: flex;justify-content: flex-end; /* 确保按钮靠右对齐 */
}.nav-item {margin-right: 15px; /* 可选:增加按钮之间的间距 */margin-top: 10px;
}
</style>

左侧导航菜单包含两个主项:“首页” 和 “我的”,其中"我的"菜单项下包含个人信息。实现用户界面与路由的集成。

<template><el-aside style="width: 200px; background-color: #545c64;"><el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo"text-color="#fff" @select="handleSelect" :default-active="activeIndex"><el-sub-menu index="1"><template #title><span>首页</span></template></el-sub-menu><el-sub-menu index="2"><template #title><span>我的</span></template><el-menu-item index="2-1" @click="navigateTo('my-page')">个人信息</el-menu-item></el-sub-menu></el-menu></el-aside>
</template>
<script>
export default {name: 'SideNavbar',data() {return {activeIndex: '2' // 默认激活的菜单项};},methods: {navigateTo(route) {this.$router.push({ name: route });},handleSelect(index, indexPath) {this.activeIndex = index;}}
};
</script>

接着定义一个个人信息页面,用于用户信息展示和头像上传功能。通过调用后端接口获取和上传数据,并使用 Element Plus 组件进行表单和消息提示的展示。这里目的是展示从本地存储中获取 token,将token作为接口携带参数,调用接口从服务器获取用户信息。如果 token 不存在,则通过 Element Plus 显示错误提示,提示用户需要登录,并重定向到登录页面。

<!-- MePage.vue -->
<template><div class="me"><el-form :model="user" label-width="auto" style="max-width: 600px" enctype="multipart/form-data"><el-form-item label="头像"><el-uploadclass="avatar-uploader":action="uploadUrl"accept="image/*"show-file-list="false":before-upload="beforeUpload":on-success="handleSuccess":on-error="handleError":on-change="handleFileChange"><img v-if="imageUrl" style="height: 200px; width: 200px" alt="头像" class="avatar" :src="imageUrl"/><el-icon class="avatar-uploader-icon" v-else-if="!islook"/></el-upload></el-form-item><el-form-item label="用户名"><el-input v-model="user.username"/></el-form-item><el-form-item label="性别"><el-input v-model="user.sex"/></el-form-item><el-form-item label="年龄"><el-input v-model="user.age"/></el-form-item><el-form-item label="邮箱"><el-input v-model="user.mailbox"/></el-form-item><el-form-item label="个人简介"><el-input v-model="user.introduce" type="textarea"/></el-form-item></el-form></div>
</template><script>
import axios from 'axios';
import { ElMessage } from 'element-plus';export default {name: 'MePage',data() {return {user: {userId: '',sex: '',age: '',mailbox: '',username: '',introduce: '',headPortrait: ''},uploadUrl: 'http://localhost:8081/upload/avatar',islook: false,imgUrl: '../assets/logo.png',imageUrl: ''}},components: {},created() {this.getuser();},methods: {getuser() {const token = localStorage.getItem('token');console.log('token:', token);if (!token) {ElMessage.error('请先登录');this.$router.push('/login');}axios.get('http://localhost:8081/user/getById?userId=123', {headers: {Authorization: `Bearer ${token}`}}).then(response => {console.log('服务器返回的数据:', response.data);this.user = response.data.body;this.imageUrl = this.user.headPortrait ? `${this.user.headPortrait}` : '@/assets/logo.png';console.log('图片 URL:', this.imageUrl);this.islook = true;}).catch(error => {console.log(error);ElMessage.error('获取用户信息失败');});},beforeUpload(file) {const isImage = file.type.startsWith('image/');if (!isImage) {ElMessage.error('只能上传图片文件!');return false;}return isImage;},handleSuccess(response) {console.log('图片 上传成功响应:', response);if (response.code === 200) {ElMessage.success('上传成功');console.log('上传成功');// 重新调用 getuser 方法以获取最新的头像地址this.getuser();} else {ElMessage.error('上传失败: ' + response.msg);}},handleError(err) {console.error('图片上传失败:', err);ElMessage.error('上传失败: ' + (err.message || '未知错误'));},handleFileChange(file) {// 更新显示的图片const fileURL = file.raw;this.imageUrl = URL.createObjectURL(fileURL);}}
}
</script><style scoped>
.me {display: flex;justify-content: center;align-items: center;height: 100%;
}.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);height: 200px;width: 200px
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}
</style>

路由这里我们要改下配置,在主页面下配置子页面路径为 '/my-page'

const routes = [{path: '/',name: 'HomePage',component: HomePage,meta: {requiresAuth: true}, // 添加元信息,表示需要认证children: [{path: '/my-page',name: 'my-page',component: MePage,}]},{path: '/login',name: 'Login',component: Login},{path: '/register',name: 'Register',component: Register},{path: '/:pathMatch(.*)*',redirect: '/login' // 捕获所有未定义的路由并重定向到登录页面}
];

Login.vue组件也需要改下,若登录成功,则存储 TOKEN 到本地并跳转到主页;若登录失败,则显示相应的错误提示,捕获异常以便出错时给予用户反馈。

async login() {try {const params = new URLSearchParams();params.append('username', this.form.username);params.append('password', this.form.password);const response = await axios.post('http://127.0.0.1:8081/user/login', params);console.log("login response:", response);if (response.data.code === 200) {this.$message.success('登录成功');console.log("登录成功,获取到的TOKEN是: " + JSON.stringify(response.data.body))localStorage.setItem('token', response.data.body);this.$router.push('/');} else {if (response.data.extension && response.data.extension.error) {this.$message.error('登录失败: ' + response.data.extension.error);console.log(response.data.extension.error);} else {this.$message.error('登录失败');console.log('未知错误');}}} catch (error) {console.log(error);this.$message.error('登录失败');}}

5.2后端功能实现

首先引入jwt和Spring Security的依赖

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

接着我们定义一个JWT工具类,用于生成 JWT Token、验证JWT Token以及反向解析Token,获取用户名。

/*** 功能描述:JWT工具类**/
public class JwtUtils {private static final String SECRET = "USER_JWT_SECRET";/*** 功能描述:根据用户名生成JWT token** @param username 用户名* @return {@code String }*/public static String generateToken(String username) {return Jwts.builder().setSubject(username).signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, SECRET).setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) // 24 小时过期.compact();}/*** 功能描述: 验证JWT token是否有效** @param token Jwt Token* @return boolean*/public static boolean validateToken(String token) {try {Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);return true;} catch (Exception e) {return false;}}/*** 功能描述: 从JWT token中获取用户名** @param token Jwt Token* @return {@code String }*/public static String getUsername(String token) {return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject();}
}

定义好JwtUtils工具类后,预期是在前端请求后端时,校验是否传入Token以及传入Token的有效性。我们可以在每个接口定义Token校验,当时这样子无疑增加了工作量和代码复杂度,所以这里我使用AOP的形式,利用注解的形式,在接口被请求之前,自动去校验Token。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenListener {
}
@Aspect
@Component
public class JwtAspect {@Before("@annotation(com.example.interestplfm.annotation.TokenListener) || @within(com.example.interestplfm.annotation.TokenListener)")public void validateToken() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("Authorization");HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();assert response != null;if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);// 抛出异常,返回401状态码if (!JwtUtils.validateToken(token)) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);throw new IllegalArgumentException("用户未成功登录,请先登录!");}} else {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);throw new IllegalArgumentException("用户未成功登录,请先登录!");}}
}

最后,我们在控制层实现用户相关接口,并对必要的接口进行Token权限校验。

@RestController
@RequestMapping("/user")
@CrossOrigin(origins = "http://localhost:8080")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/register")public RestResult<String> register(@RequestParam String username, @RequestParam String password) {if (userService.registerUser(username, password)) {return RestResult.build("User registered successfully");} else {Map<String, Object> extension = new HashMap<>();extension.put("error", "User already exists");return RestResult.buildFailure(extension);}}@PostMapping("/login")public RestResult<String> login(@RequestParam String username, @RequestParam String password) {User user = userService.verifyPassword(username, password);if (user != null) {String token = JwtUtils.generateToken(user.getUsername());return RestResult.build(token);} else {Map<String, Object> extension = new HashMap<>();extension.put("error", "Invalid username or password");return RestResult.buildFailure(extension);}}@GetMapping("/getById")@TokenListenerpublic RestResult<User> uploadPicture(@RequestParam("userId") Long userId) {return RestResult.build(userService.selectByUserId(userId));}@PostMapping("/logout")public void logout(HttpServletRequest request, HttpServletResponse response) {// 清空当前用户的Tokenresponse.setHeader("Authorization", "");response.setStatus(HttpServletResponse.SC_OK);}
}

一切准备就绪,让我们来验证下功能是否达到预期吧。

哦吼~通过点击登录,可以发现并没有反应,一看控制台,提示前端访问后端跨域请求被阻止。 这时候可能有人就疑问了,明明在控制层已经设置了@CrossOrigin(origins = "http://localhost:8080"),允许前端的跨域请求,可为何现在失效了呢?
在这里插入图片描述

5.3 Spring Security 的坑

其实,这是由于Spring Security引起的问题,当我们在项目集成了Spring Security功能后,基本会遇到一些常见的“坑”或需要注意的问题。比如说:

  1. 出现无法登录问题
  2. 静态资源请求被拦截
  3. 前端访问后端的请求被拦截
  4. 出现跨域问题
  5. 认证和授权逻辑混乱
  6. 角色和权限的误用

就比如上面跨域的情况,实际上是因为默认情况下,Spring Security启用了CSRF保护,所以在使用REST API时,可能就会导致问题。

要解决这个问题也很简单,可以根据具体情况决定是否禁用CSRF保护,或为特定API端点配置CSRF保护。这里我们定义了一个Spring Security配置类SecurityConfig,配置URL访问权限,允许特定路径或用户进行访问。同时启用CORS配置,允许来自特定域的请求,并允许所有HTTP方法和头部信息,同时允许发送Cookie。也在配置中明确允许访问静态资源。

package com.example.interestplfm.config;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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").permitAll() // 所有以 /user/ 开头的路径都允许匿名访问.antMatchers("/upload/**").permitAll() // 所有以 /upload/ 开头的路径都允许匿名访问.antMatchers("/static/**").permitAll() // 允许静态资源访问.antMatchers("/css/**", "/js/**", "/image/**").permitAll() // 允许特定静态资源目录访问.anyRequest().authenticated().and().formLogin().and().logout().logoutUrl("/logout"); // 配置注销URL}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedOrigin("http://localhost:8080"); // 允许的前端域configuration.addAllowedMethod("*"); // 允许的HTTP方法configuration.addAllowedHeader("*"); // 允许的头部信息configuration.setAllowCredentials(true); // 允许发送CookieUrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}
}

配置完后,我们再次验证下:可以发现,可以正常登录成功,且可以跳转到个人信息页面进行头像上传和用户信息展示,图片也正常显示,同时点击退出清空了Token后,直接通过输入URL路径请求接口的形式,会直接提示登录失败并回到登录页面。

至此,我们的预期效果已全部实现。
在这里插入图片描述

6. 总结

TOKEN鉴权校验作为一种行之有效的安全机制,确保了用户请求的合法性,为用户身份和权限验证提供了坚实的基础。通过使用JWT(JSON Web Tokens)加密用户信息,结合Spring Security进行细粒度的权限认证,我们可以为用户数据提供双重保障,从而实现更高级别的安全性。

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

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

相关文章

Golang | Leetcode Golang题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {n : len(citations)return n - sort.Search(n, func(x int) bool { return citations[x] > n-x }) }

JDBC操作MySQL数据

一准备、 1、首先在IDEA中导入导入包&#xff1a;mysql-connector-java-8.0.23 2、写初始化语句 &#xff08;1&#xff09;在目录下找到driver类 &#xff08;2&#xff09;在JDBCUtil函数中把驱动器的类路径改掉 ①打开driver类 ②按住类名 Driver用快捷键 CtrlAltshiftC …

AIGC的神秘面纱——利用人工智能生成内容改变我们的生活

近年来&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;正在迅速改变我们与数字世界互动的方式。从自动写作到图像生成&#xff0c;AIGC正逐渐走进我们的日常生活。它不仅提高了效率&#xff0c;还为创意和商业活动带来了新的可能性。让我们一起来探索AIGC的世界&…

17.jdk源码阅读之LinkedBlockingQueue

1. 写在前面 LinkedBlockingQueue 是 Java 并发包中的一个重要类&#xff0c;常用于生产者-消费者模式等多线程编程场景。上篇文章我们介绍了ArrayBlockingQueue&#xff0c;并且与LinkedBlockingQueue做了简单的对比&#xff0c;这篇文章我们来详细分析下LinkedBlockingQueue…

从零开始构建你的第一个Python Web应用

在本文中&#xff0c;我们将带领你从零开始构建一个简单的Python Web应用。不需要任何先验知识&#xff0c;我们会一步步地指导你完成设置、框架选择、代码编写到部署的整个过程。无论你是Web开发新手还是希望扩展技能的老手&#xff0c;这篇文章都将为你提供一个实践操作的起点…

Spring-Aop源码解析(二)

书接上文&#xff0c;上文说到&#xff0c;specificInterceptors 不为空则执行createProxy方法创建代理对象&#xff0c;即下图的createProxy方法开始执行&#xff0c;生成代理对象&#xff0c;生成代理对象有两种方式&#xff0c;JDK和CGLIB。 createAopProxy就是决定使用哪…

【数据结构 | 哈希表】一文了解哈希表(散列表)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

昇思学习打卡-22-生成式/DCGAN生成漫画头像

文章目录 DCGAN网络数据处理构造网络生成器判别器损失函数优化器 结果展示 我们将学习DCGAN网络如何数据处理、设置网络&#xff0c;包括生成器、判别器、损失函数、优化器等。 DCGAN网络 DCGAN&#xff08;深度卷积对抗生成网络&#xff0c;Deep Convolutional Generative Ad…

windows下运行sh文件

1、打开git bash 2、进入sh文件所在文件夹&#xff0c;使用sh xx.sh运行

普发Pfeiffer TPG300手侧配置安装操作技术资疗包含

普发Pfeiffer TPG300手侧配置安装操作技术资疗包含

学习笔记:MySQL数据库操作2

1. 建库建表 创建数据库 mydb8_worker。使用该数据库 mydb8_worker。创建职工表 t_worker&#xff0c;字段包括&#xff1a; department_id: 部门号&#xff0c;整型&#xff0c;不允许为空。worker_id: 职工号&#xff0c;主键&#xff0c;整型&#xff0c;不允许为空。worke…

硬盘数据恢复的基本原理是什么 硬盘数据恢复教程

无论是电脑硬盘&#xff0c;还是日常办公过程中使用系统硬盘&#xff0c;都是由多个存储空间组成的。如果这些存储空间中的信息被删除了&#xff0c;那内部的文件也会跟着消失。下面&#xff0c;小编就以“硬盘数据恢复工具恢复原理&#xff0c;硬盘数据恢复教程”这两个问题为…

昇思25天学习打卡营第18天 | DCGAN生成漫画头像

探索DCGAN在生成动漫头像的实用性 通过深入学习和实践DCGAN&#xff08;Deep Convolutional Generative Adversarial Networks&#xff09;&#xff0c;我对这种深度学习模型在生成动漫头像方面的应用有了更全面的理解。DCGAN作为一种改进的GAN模型&#xff0c;通过在生成器和…

MP的使用

1、MP简介 MyBatis-Plus&#xff08;简称MP&#xff09;是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生 官网&#xff1a;MyBatis-Plus &#x1f680; 为简化开发而生 参考教程&#xff1a;https://baomidou.c…

土地规划中的环境影响评估:守护绿水青山的科学指南

土地规划&#xff0c;作为指导区域开发与保护的蓝图&#xff0c;其决策不仅关乎经济发展&#xff0c;更与生态环境息息相关。环境影响评估&#xff08;EIA&#xff09;作为土地规划不可或缺的一环&#xff0c;旨在预测、评估规划项目对自然环境和社会环境的潜在影响&#xff0c…

英语科技写作 希拉里·格拉斯曼-蒂(英文版)pdf下载

下载链接&#xff1a; 链接1&#xff1a;https://pan.baidu.com 链接2&#xff1a;/s/1fxRUGnlJrKEzQVF6k1GmBA 提取码&#xff1a;b69t 由于是英文版&#xff0c;可能有些看着不太方便&#xff0c;可以在网页版使用以下软件中英文对照着看&#xff0c;看着更舒服&#xff0c;…

【echarts区域地图】

背景&#xff1a;我们在制作大屏的时候&#xff0c;经常会使用到echarts制作各种图表&#xff0c;饼图&#xff0c;柱状图&#xff0c;折线图。有时候也会用到地图的交互&#xff0c;使大屏效果看起来更加高级。 我们要完成上面的效果需要准备什么呢&#xff1f; 首先是需要我…

Gson的基本使用:解析Json格式数据 序列化与反序列化

目录 一&#xff0c;Gson和Json 1&#xff0c;Gson 2&#xff0c;Json 3&#xff0c;Gson处理对象的几个重要点 4&#xff0c;序列化和反序列化 二&#xff0c;Gson的使用 1&#xff0c;Gson的创建 2&#xff0c;简单对象序列化 3&#xff0c;对象序列化&#xff0c;格…

基于ansible进行运维自动化的研究以及相关的属性

一、ansible-简介 介绍 ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c; 实现了批量系统配置、批量程序部署、批量运行命令等功能。 无客户端。 …

【LeetCode】71.简化路径

1. 题目 2. 分析 3. 代码 我写了一版很复杂的代码&#xff1a; class Solution:def simplifyPath(self, path: str) -> str:operator [] # 操作符的栈dir_name [] # 文件名的栈idx 0cur_dir_name ""while(idx < len(path)):if path[idx] /:operator.ap…