JWT整合Gateway实现鉴权(RSA与公私密钥工具类)

一.业务流程

1.使用RSA生成公钥和私钥。私钥保存在授权中心,公钥保存在网关(gateway)和各个信任微服务中。
2.用户请求登录。
3.授权中心进行校验,通过后使用私钥对JWT进行签名加密。并将JWT返回给用户
4.用户携带JWT访问
5.gateway直接通过公钥解密JWT进行验证
 

二.RSA测试Demo

JWT包含三部分数据
header头部分-Payload载荷(包含用户信息身份)-Signature签名


一.工具类

用户基本信息

package entity;/*** 载荷对象*/
public class UserInfo {private Long id;private String username;public UserInfo() {}public UserInfo(Long id, String username) {this.id = id;this.username = username;}public Long getId() {return this.id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}
}

公钥私钥生成读取类

package utils;import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;public class RsaUtils {/*** 从文件中读取公钥** @param filename 公钥保存路径,相对于classpath* @return 公钥对象* @throws Exception*/public static PublicKey getPublicKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPublicKey(bytes);}/*** 从文件中读取密钥** @param filename 私钥保存路径,相对于classpath* @return 私钥对象* @throws Exception*/public static PrivateKey getPrivateKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPrivateKey(bytes);}/*** 获取公钥** @param bytes 公钥的字节形式* @return* @throws Exception*/public static PublicKey getPublicKey(byte[] bytes) throws Exception {X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePublic(spec);}/*** 获取密钥** @param bytes 私钥的字节形式* @return* @throws Exception*/public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePrivate(spec);}/*** 根据密文,生存rsa公钥和私钥,并写入指定文件** @param publicKeyFilename  公钥文件路径* @param privateKeyFilename 私钥文件路径* @param secret             生成密钥的密文* @throws IOException* @throws NoSuchAlgorithmException*/public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");SecureRandom secureRandom = new SecureRandom(secret.getBytes());keyPairGenerator.initialize(1024, secureRandom);KeyPair keyPair = keyPairGenerator.genKeyPair();// 获取公钥并写出byte[] publicKeyBytes = keyPair.getPublic().getEncoded();writeFile(publicKeyFilename, publicKeyBytes);// 获取私钥并写出byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();writeFile(privateKeyFilename, privateKeyBytes);}private static byte[] readFile(String fileName) throws Exception {return Files.readAllBytes(new File(fileName).toPath());}private static void writeFile(String destPath, byte[] bytes) throws IOException {File dest = new File(destPath);if (!dest.exists()) {dest.createNewFile();}Files.write(dest.toPath(), bytes);}
}

公钥私钥加解密类

package utils;import entity.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;import java.security.PrivateKey;
import java.security.PublicKey;public class JwtUtils {/*** 私钥加密token** @param userInfo      载荷中的数据* @param privateKey    私钥* @param expireMinutes 过期时间,单位秒* @return* @throws Exception*/public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {return Jwts.builder().claim(JwtConstans.JWT_KEY_ID, userInfo.getId()).claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername()).setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate()).signWith(SignatureAlgorithm.RS256, privateKey).compact();}/*** 私钥加密token** @param userInfo      载荷中的数据* @param privateKey    私钥字节数组* @param expireMinutes 过期时间,单位秒* @return* @throws Exception*/public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {return Jwts.builder().claim(JwtConstans.JWT_KEY_ID, userInfo.getId()).claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername()).setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate()).signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey)).compact();}/*** 公钥解析token** @param token     用户请求中的token* @param publicKey 公钥* @return* @throws Exception*/private static Jws<Claims> parserToken(String token, PublicKey publicKey) {return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);}/*** 公钥解析token** @param token     用户请求中的token* @param publicKey 公钥字节数组* @return* @throws Exception*/private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey)).parseClaimsJws(token);}/*** 获取token中的用户信息** @param token     用户请求中的令牌* @param publicKey 公钥* @return 用户信息* @throws Exception*/public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {Jws<Claims> claimsJws = parserToken(token, publicKey);Claims body = claimsJws.getBody();return new UserInfo(ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME)));}/*** 获取token中的用户信息** @param token     用户请求中的令牌* @param publicKey 公钥* @return 用户信息* @throws Exception*/public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception {Jws<Claims> claimsJws = parserToken(token, publicKey);Claims body = claimsJws.getBody();return new UserInfo(ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME)));}
}

辅助类:
 

package utils;import org.apache.commons.lang3.StringUtils;/*** 从jwt解析得到的数据是Object类型,转换为具体类型可能出现空指针,* 这个工具类进行了一些转换*/
public class ObjectUtils {public static String toString(Object obj) {if (obj == null) {return null;}return obj.toString();}public static Long toLong(Object obj) {if (obj == null) {return 0L;}if (obj instanceof Double || obj instanceof Float) {return Long.valueOf(StringUtils.substringBefore(obj.toString(), "."));}if (obj instanceof Number) {return Long.valueOf(obj.toString());}if (obj instanceof String) {return Long.valueOf(obj.toString());} else {return 0L;}}public static Integer toInt(Object obj) {return toLong(obj).intValue();}
}
package utils;public abstract class JwtConstans {public static final String JWT_KEY_ID = "id";public static final String JWT_KEY_USER_NAME = "username";
}
二.测试代码
import entity.UserInfo;
import org.junit.Before;
import org.junit.Test;
import utils.JwtUtils;
import utils.RsaUtils;import java.security.PrivateKey;
import java.security.PublicKey;public class JwtTest {private static final String pubKeyPath = "D:\\tmp\\rsa\\rsa.pub";private static final String priKeyPath = "D:\\tmp\\rsa\\rsa.pri";private PublicKey publicKey;private PrivateKey privateKey;@Testpublic void testRsa() throws Exception {RsaUtils.generateKey(pubKeyPath, priKeyPath, "234");}//在运行生成token之前,获取公钥和私钥对象@Beforepublic void testGetRsa() throws Exception {this.publicKey = RsaUtils.getPublicKey(pubKeyPath);this.privateKey = RsaUtils.getPrivateKey(priKeyPath);}//通过私钥加密@Testpublic void testGenerateToken() throws Exception {// 生成tokenString token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5);System.out.println("token = " + token);}//通过公钥解析token@Testpublic void testParseToken() throws Exception {String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTY3MDAzOTI0M30.Y6MstaAuWwgsNenMRYQSBeG-zx9kHmh75PJuGrJuyPPuetebwqS6Xl2NQGmMYx1mQII-Tq6M-vGGMvQJ8q2Dd8GXL1u-RMC9-e7SKkAgVFP0YzwY3YJRgw9q62snWZqZllmNIgp_jFu14HHHktCS49V-bd1HR9W2PMQoWOeWquI";// 解析tokenUserInfo user = JwtUtils.getInfoFromToken(token, publicKey);System.out.println("id: " + user.getId());System.out.println("userName: " + user.getUsername());}
}
三.测试流程


指定路径一定包括rsa







三.Spring Cloud+Gateway+RSA


一.gateway模块:
yml文件

定义白名单(不需要鉴权的路径),公钥地址(解密鉴权),以及cookieName(获取加密的token)

配置类:

获取公钥

package com.yigou.gw.config;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import utils.RsaUtils;import javax.annotation.PostConstruct;
import java.security.PublicKey;@ConfigurationProperties(prefix = "yigou.jwt")
@Data
@Slf4j
public class JwtProperties {private String pubKeyPath;// 公钥private PublicKey publicKey; // 公钥private String cookieName;//@PostConstruct注解的方法将会在依赖注入完成后被自动调用。//执行顺序:Constructor >> @Autowired >> @PostConstruct@PostConstructpublic void init(){try {// 获取公钥和私钥this.publicKey = RsaUtils.getPublicKey(pubKeyPath);} catch (Exception e) {log.error("初始化公钥失败!", e);throw new RuntimeException();}}
}

获取定义的白名单对象

package com.yigou.gw.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;@ConfigurationProperties(prefix = "yigou.filter")
@Data
public class FilterProperties {private List<String> allowPaths;
}
自定义拦截:
package com.yigou.gw.filter;import com.yigou.gw.config.FilterProperties;
import com.yigou.gw.config.JwtProperties;
import entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import utils.JwtUtils;import java.util.List;@Component
@EnableConfigurationProperties({JwtProperties.class, FilterProperties.class})
@Slf4j
public class LoginFilter implements GlobalFilter, Ordered {@Autowiredprivate JwtProperties prop;@Autowiredprivate FilterProperties fprop;//核心过滤器方法@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//设置白名单,白名单里面的请求路径,直接放行String path = exchange.getRequest().getPath().toString();List<String> allowPaths = fprop.getAllowPaths();for (String allowPath : allowPaths) {//判断path是否以allowPath开头if (path.startsWith(allowPath)){//放行return chain.filter(exchange);}}//1.获取请求中的tokenHttpCookie cookie = exchange.getRequest().getCookies().getFirst(prop.getCookieName());String token=null;if (cookie!=null){token=cookie.getValue();}log.info("token:",token);try {//2.解析tokenUserInfo userInfo = JwtUtils.getInfoFromToken(token, prop.getPublicKey());//3.放行if (userInfo!=null){return chain.filter(exchange);}} catch (Exception e) {e.printStackTrace();//如果出现异常,设置异常状态exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);}return exchange.getResponse().setComplete();}//过滤器执行顺序@Overridepublic int getOrder() {return 0;}
}
功能总结:

1.使用yml配置获取公钥存储路径以及白名单路径。
2.通过路径得到公钥,自定义拦截器先去对比是否为白名单路径,若为白名单路径直接放行。
3.若不为白名单  通过cookieName得到存储的token,并使用公钥解析是否可以获得userinfo对象来判断是否放行。

二.鉴权中心模板:
yml文件:
server:port: 8087
spring:application:name: auth-servicecloud:nacos:discovery:server-addr: http://192.168.147.129:8848yigou:jwt:secret: yigou@Login(Auth}*^31)&javaman% # 登录校验的密钥pubKeyPath: D:\\tmp\\rsa\\rsa.pub # 公钥地址priKeyPath: D:\\tmp\\rsa\\rsa.pri # 私钥地址expire: 1800 # 过期时间,单位秒cookieName: YG_TOKENcookieMaxAge: 1800
配置文件:
package com.yigou.auth.config;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import utils.RsaUtils;import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;@ConfigurationProperties(prefix = "yigou.jwt")
@Data
@Slf4j
public class JwtProperties {private String secret; // 密钥private String pubKeyPath;// 公钥private String priKeyPath;// 私钥private int expire;// token过期时间private PublicKey publicKey; // 公钥private PrivateKey privateKey; // 私钥private String cookieName;private Integer cookieMaxAge;/*** @PostContruct:在构造方法执行之后执行该方法*/@PostConstructpublic void init(){try {File pubKey = new File(pubKeyPath);File priKey = new File(priKeyPath);if (!pubKey.exists() || !priKey.exists()) {// 生成公钥和私钥RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);}// 获取公钥和私钥this.publicKey = RsaUtils.getPublicKey(pubKeyPath);this.privateKey = RsaUtils.getPrivateKey(priKeyPath);} catch (Exception e) {log.error("初始化公钥和私钥失败!", e);throw new RuntimeException();}}// getter setter ...
}
controller
package com.yigou.auth.controller;import com.yigou.auth.config.JwtProperties;
import com.yigou.auth.service.AuthService;
import com.yigou.common.enums.ExceptionEnum;
import com.yigou.common.exception.YgException;
import com.yigou.common.utils.CookieUtils;
import com.yigou.user.entity.TbUser;
import entity.UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import utils.JwtUtils;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@RestController
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {@Autowiredprivate JwtProperties prop;@Autowiredprivate AuthService authService;//接收用户名和密码,通过密钥加密生成JWT,写入到cookie中保存@PostMapping("/accredit")public ResponseEntity<Void> authentication(@RequestParam("username") String username,@RequestParam("password") String password,HttpServletResponse response,HttpServletRequest request){//1.验证之后,生成JWT-tokenString token=authService.authentication(username,password);//2.如果token是空的,验证失败if(StringUtils.isEmpty(token)){return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();}//3.如果不为空,说明不为空,返回token存放到cookie中CookieUtils.setCookie(request,response,prop.getCookieName(),token,prop.getCookieMaxAge(),true);return ResponseEntity.ok().build();}//验证用户信息,如果用户信息验证成功,返回用户信息@GetMapping("verify")public ResponseEntity<UserInfo> verify(@CookieValue("YG_TOKEN") String token,HttpServletRequest request,HttpServletResponse response){//1.根据token解析获取到的cookie中的tokentry {UserInfo userInfo = JwtUtils.getInfoFromToken(token, prop.getPublicKey());//重新生成tokenString newToken = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());//重新把Token设置到cookie中,覆盖原来的cookieCookieUtils.setCookie(request,response,prop.getCookieName(),newToken, prop.getCookieMaxAge());return ResponseEntity.ok(userInfo);} catch (Exception e) {throw new YgException(ExceptionEnum.USER_TOKEN_EXISTS_FALL);}}
}
service:
package com.yigou.auth.service.impI;import com.yigou.auth.client.UserClient;
import com.yigou.auth.config.JwtProperties;
import com.yigou.auth.service.AuthService;
import com.yigou.common.enums.ExceptionEnum;
import com.yigou.common.exception.YgException;
import com.yigou.user.entity.TbUser;
import entity.UserInfo;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import utils.JwtUtils;@Service
public class AuthServiceImpl implements AuthService {@Autowiredprivate UserClient userClient;@Autowiredprivate JwtProperties prop;//验证用户名和密码之后,生成jwt-token@Overridepublic String authentication(String username, String password) {//1.通过用户名,密码验证是否存在TbUser user = userClient.getUserInfo(username, password);if (user==null){return null;}//2.生成tokenUserInfo userInfo = new UserInfo();userInfo.setId(user.getId());userInfo.setUsername(user.getUsername());try {String token = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());return token;} catch (Exception e) {throw new YgException(ExceptionEnum.JWT_TOKEN_CREATE_fALL);}}
}
调用接口
package com.yigou.auth.client;import com.yigou.user.api.UserApi;
import org.springframework.cloud.openfeign.FeignClient;@FeignClient("user-service")
public interface UserClient extends UserApi {
}
三.注意点:
1.使用Feign调用其他模块方法:

将提供模块的对外接口包以及httpclient依赖到调用模块

 <!--httpclient支持--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>

调用模块的接口类继承提供模块对外开放的接口类

注释@FeignClient的参数名和提供模块在注册中心的名相同。

2.提供模块建立对外接口包

对外接口类 所提供方法 要与controller方法行映射

3.调用模块主函数开启FeignClients
package com.yigou.auth;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class    YgAuthServiceApplication {public static void main(String[] args) {SpringApplication.run(YgAuthServiceApplication.class, args);}}
四.总结

1.登录请求通过网关(登录为白名单 因此不会拦截校验),网关转发到授权中心模块
2.授权中心模块通过前端发送的用户信息feign调用用户模块查询用户信息。根据是否有该用户来判断是否使用该用户信息以及拿到yml定义的私钥路径去生成token存入cookie中。


3.若登陆成功 那么随后每次请求都会携带cookie中的token 在网关进行拦截以及公钥解密。每次鉴权都会在鉴权模块将token重新刷新一遍。

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

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

相关文章

最好用的智能猫砂盆存在吗?自用分享智能猫砂盆测评!

在现代都市的忙碌生活中&#xff0c;作为一名上班族&#xff0c;经常因为需要加班或频繁出差而忙碌得不可开交。急匆匆地出门&#xff0c;却忘了给猫咪及时铲屎。但是大家要知道&#xff0c;不及时清理猫砂盆会让猫咪感到不适&#xff0c;还会引发各种健康问题&#xff0c;如泌…

「6.18更新日志」JVS·智能BI、规则引擎功能更新说明

项目介绍 JVS是企业级数字化服务构建的基础脚手架&#xff0c;主要解决企业信息化项目交付难、实施效率低、开发成本高的问题&#xff0c;采用微服务配置化的方式&#xff0c;提供了 低代码数据分析物联网的核心能力产品&#xff0c;并构建了协同办公、企业常用的管理工具等&am…

从视频创意到传播策略 | 医药产品TVC新媒体传播方案

作为营销策划人&#xff0c;你一定在寻找能够激发创意灵感、拓展策划视野的实战案例。这份最新传播方案由Unithought精心打造&#xff0c;不仅是一份详尽的策划指南&#xff0c;更是一次深入患者心灵的品牌传播实践。 何策网&#xff0c;每日收录全网方案PPT &#xff01; 方…

Ubuntu server 24 (Linux) 安装客户端(windows/linux) Zabbix 7.0 LTS Zabbix agent2

一 Ubuntu(linux)安装客户端 1 Ubuntu 24 安装Zabbix agent2 #安装agent库 sudo wget https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_7.0-1ubuntu24.04_all.deb sudo dpkg -i zabbix-release_7.0-1ubuntu24.04_all.deb sudo apt u…

Windows下MySQL数据库定期备份SQL文件与删除历史备份文件.bat脚本

目录 一、功能需求 二、解决方案 (1)新建文件夹及批处理文件 (2)编写备份脚本 ①完整脚本 ②参数修改 (3)编写定期删除备份脚本 ①根据文件名识别日期进行删除 ② 根据文件的修改日期删除 (4)设置定时器 (5)常见报错与处理 一、功能需求 在Windows系统下…

对比4090及4090D:国区“特供”与原版相比有何区别?

2023年12月28日 英伟达宣布正式发布GeForce RTX 4090D&#xff0c;对比于一年前上市的4090芯片&#xff0c;两者的区别与差异在哪&#xff1f;而在当前比较火热的大模型推理、AI绘画场景方面 两者各自的表现又如何呢&#xff1f; 规格与参数信息对比现在先来看看GeForce RT…

手把手教你创建并启动一个Vue3项目(Windows版)

一、Node安装 1、下载地址&#xff1a;Node.js — Run JavaScript Everywhere 2、安装Node&#xff0c;双击启动一直Next 3、验证安装Node是否成功&#xff0c;打开CMD命令窗口&#xff0c;输入node -v&#xff0c;显示版本就表示成功 4、验证安装npm是否成功&#xff0c;npm是…

数据库大作业——音乐平台数据库管理系统

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; 《数据库系统》课程设计 &#xff1a;流行音乐管理平台数据库系统&#xff08;本数据库大作业使用软件sql server、dreamweaver、power designer&#xff09; 目录 系统需求设计 数据库概念结构设计 实体分析 属性分…

Go源码--sync库(3):sync.Pool(2)

回收 回收其实就是将 pool.local 置为空 可以让垃圾回收器回收 我们来看下 源码 func init() {// 将 poolCleanup 注册到 gc开始前的准备工作处理器中在 STW时执行runtime_registerPoolCleanup(poolCleanup) }这里注册了清理程序到GC前准备工作 也就是发生GC前需要执行这段代…

【吊打面试官系列-Mysql面试题】Myql 中的事务回滚机制概述 ?

大家好&#xff0c;我是锋哥。今天分享关于 【Myql 中的事务回滚机制概述 ?】面试题&#xff0c;希望对大家有帮助&#xff1b; Myql 中的事务回滚机制概述 ? 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个不可分割的工作单位…

flutter 打包 exe

采用官方的MSIX打包 原文链接 https://blog.csdn.net/weixin_44786530/article/details/135308360 第一步&#xff1a;安装依赖 在项目根目录&#xff0c;执行命令&#xff1a; flutter pub add --dev msix 等待安装完成&#xff0c;就好了 第二步&#xff1a;打包编译 当m…

数据库 | 试卷五试卷六试卷七

1. 主码不相同&#xff01;相同的话就不能唯一标识非主属性了 2.从关系规范化理论的角度讲&#xff0c;一个只满足 1NF 的关系可能存在的四方面问题 是&#xff1a; 数据冗余度大&#xff0c;插入异常&#xff0c;修改异常&#xff0c;删除异常 3.数据模型的三大要素是什么&…

Electron+vite+vuetify项目搭建

最近想用Electron来进行跨平台的桌面应用开发。同时想用vuetify作为组件&#xff0c;于是想搭建一个这样的开发环境。其中踩了不少坑&#xff0c;总是会出现各种的编译错误和问题&#xff0c;依赖的各种问题&#xff0c;搞了好久最终环境终于弄好可正常开发了。这里分享下快速搭…

关于接口多态,何时使用接口名创建对象?何时使用子类创建对象?

接口创建对象只能创建他的实现类&#xff0c;所以会出现两种创建方式&#xff1a; 1、接口 对象名 new 类名 2、子类对象 对象名 new 类名 举个例子&#xff0c;swimming是一个接口&#xff0c;flog是他的一个实现类&#xff0c;重写了swimming的eat()方法 子类对象 对象名…

【QT】信号与槽

目录 概述 Q_OBJECT 自定义信号 自定义槽 带参数的信号和槽 信号与槽断开 定义槽函数时&#xff0c;使用lambda表达式 概述 所谓的信号槽&#xff0c;要解决的问题&#xff0c;就是响应用户的操作&#xff0c;这是QT与其他GUI开发框架比较不同的地方。其他的GUI开发框…

Z世代职场价值观的重塑:从“班味”心态到个人成长的追求

近日&#xff0c;社交平台Soul APP联合上海市精神卫生中心&#xff08;俗称“宛平南路600号”&#xff09;发布《2024年Z世代职场心理健康报告》&#xff08;下称“报告”&#xff09;&#xff0c;发现今天的年轻人正以其独特的价值观和行为模式&#xff0c;重新定义成功与成就…

收银系统源码-千呼新零售2.0【线下促销】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…

百元内平价蓝牙耳机推荐,四款高热度平价耳机推荐!

在追求高品质音乐体验的同时&#xff0c;我们也不得不考虑预算的限制&#xff0c;不过市面上有不少百元内平价蓝牙耳机&#xff0c;它们在保证音质和舒适度的同时&#xff0c;也兼顾了价格的亲民性&#xff0c;身蓝牙耳机测评的达人&#xff0c;经手过不少的百元蓝牙耳机&#…

用android如何实现计算机计算功能

一.新建一个项目 步骤&#xff1a; 1.新建项目 2.选择 二.用户界面构建 找到项目的res的下面layout里面的activity.xml文件进行约束布局界面构建。 activity.xml代码如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.c…