Sa-Token

簡介

Sa-Token 是一個輕量級 Java 權限認證框架,主要解決:登錄認證、權限認證、單點登錄、OAuth2.0、分布式Session會話、微服務網關鑒權 等一系列權限相關問題。
官方文檔

常見功能

請添加圖片描述

登錄認證

本框架

  1. 用戶提交 name + password 參數,調用登錄接口。
  2. 登錄成功,返回這個用戶的 Token 會話憑證
  3. 用戶后續的每次請求,都攜帶上這個 Token。
  4. 服務器根據 Token 判斷此會話是否登錄成功。
    請添加圖片描述
測試
/*** 登錄測試 */
@RestController
@RequestMapping("/acc/")
public class LoginController {// 測試登錄  ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456@RequestMapping("doLogin")public SaResult doLogin(String name, String pwd) {// 此處僅作模擬示例,真實項目需要從數據庫中查詢數據進行比對 if("zhang".equals(name) && "123456".equals(pwd)) { 會話登錄:參數填寫要登錄的賬號id,建議的數據類型:long | int | String, 不可以傳入復雜類型,如:User、Admin 等等。Sa-Token 為這個賬號創建了一個Token憑證,且通過 Cookie 上下文返回給了前端。StpUtil.login(10001);return SaResult.ok("登錄成功");}return SaResult.error("登錄失敗");}// 查詢登錄狀態  ---- http://localhost:8081/acc/isLogin@RequestMapping("isLogin")public SaResult isLogin() {return SaResult.ok("是否登錄:" + StpUtil.isLogin());}// 查詢 Token 信息  ---- http://localhost:8081/acc/tokenInfo@RequestMapping("tokenInfo")public SaResult tokenInfo() {return SaResult.data(StpUtil.getTokenInfo());}// 測試注銷  ---- http://localhost:8081/acc/logout@RequestMapping("logout")public SaResult logout() {StpUtil.logout();return SaResult.ok();}}

Session

基于 Session 的認證是一種服務器端維護用戶會話狀態的機制,旨在解決 HTTP 協議無狀態的問題

  1. 用戶登錄:客戶端提交用戶名和密碼至服務器,服務器驗證身份后生成唯一的 Session ID,并將用戶信息(如角色、權限等)存儲在服務器的Session 對象中
  2. Session 傳遞:服務器通過響應頭的 Set-Cookie 字段將 Session ID返回給客戶端,客戶端瀏覽器自動保存此 Cookie
  3. 會話驗證:后續請求中,客戶端自動攜帶該 Cookie(包含 SessionID),服務器通過 Session ID 查找對應的 Session 對象,驗證用戶身份
  4. 會話管理:若 Session請添加圖片描述
    超時或用戶主動登出,服務器銷毀 Session 對象,客戶端 Cookie 失效

JWT

JWT(JSON Web Token) 是一種開放標準(RFC 7519),用于在網絡應用間安全傳輸 JSON 格式的信息。其核心設計為 緊湊性(體積小)和 自包含性(信息完整無需額外存儲),由三部分組成Header 、Payload、Signature

組成
  1. Header(頭部)
    包含令牌類型(typ)和簽名算法(如 HS256 或 RSA)
{ "alg": "HS256", "typ": "JWT" }

2.Payload(載荷)
存儲用戶身份信息及其他聲明(Claims),分為三類:

  • 注冊聲明(標準字段):如 sub(用戶標識)、exp(過期時間)、iat(簽發時間)等。
  • 公共聲明(自定義但建議標準化):如用戶姓名、角色等。
  • 私有聲明(業務自定義):如用戶偏好設置。

注意:Payload 內容雖可驗證但非加密,避免存儲敏感信息(如密碼)

  • Signature(簽名)
    使用密鑰對 Header 和 Payload 的編碼結果進行簽名,確保數據完整性和真實性。簽名是 JWT 安全的核心,密鑰泄露將導致偽造風險
流程

請添加圖片描述

  1. 用戶登錄:
    用戶提交憑證(如用戶名/密碼),服務器驗證成功后生成 JWT,包含用戶身份信息和有效期
  2. Token 下發:服務器將 JWT 返回客戶端,客戶端需存儲于 Cookie、LocalStorage 或 SessionStorage 中。推薦通過Authorization 請求頭傳遞
  3. Token 驗證:服務器接收請求后: 解碼并驗證簽名:使用密鑰驗證數據是否被篡改。 檢查有效期:如 exp 字段是否過期。 提取用戶信息:直接從
    Payload 中獲取用戶身份,無需查詢數據庫
實戰

JWT工具類:生成和解析JWT

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;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;}}

定義攔截器

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*** jwt令牌校驗的攔截器*/
@Component //生成Bean
@Slf4j
public class JwtTokenAdminInterceptor 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.getAdminTokenName());//2、校驗令牌try {log.info("jwt校驗:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("當前員工id:{}", empId);//前端發一次請求,是一次線程(攔截器,controller,service,mapper共有同一線程) (threadLocal)BaseContext.setCurrentId(empId);//3、通過,放行return true;} catch (Exception ex) {//4、不通過,響應401狀態碼response.setStatus(401);return false;}}
}

注冊攔截器

/*** 配置類,注冊web層相關組件*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {@Autowiredprivate JwtTokenAdminInterceptor jwtTokenAdminInterceptor;@Autowiredprivate JwtTokenUserInterceptor jwtTokenUserInterceptor;/*** 注冊自定義攔截器  配置路徑** @param registry*/protected void addInterceptors(InterceptorRegistry registry) {log.info("開始注冊自定義攔截器...");registry.addInterceptor(jwtTokenAdminInterceptor)//在以下路徑判斷.addPathPatterns("/admin/**")//排除以下路徑.excludePathPatterns("/admin/employee/login");}/*** 設置靜態資源映射* @param registry*/protected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("開始設置靜態資源映射...");registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}//擴展springmvc框架的消息轉化器@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {log.info("擴展消息轉化器...");//創建一個消息轉化器對象MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();//需要為消息轉化器設置一個對象轉換器,對象轉換器可以將Java對象序列化為json數據converter.setObjectMapper(new JacksonObjectMapper());//將自己的消息轉化器加入容器中converters.add(0,converter);}
}

權限認證

自定義注解+AOP實現

實戰(判斷管理員)

自定義注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {/*** 必須有某個角色** @return*/String mustRole() default "";}

編寫切面類

@Aspect
@Component
public class AuthInterceptor {@Resourceprivate UserService userService;/*** 執行攔截** @param joinPoint* @param authCheck* @return*/@Around("@annotation(authCheck)")public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {String mustRole = authCheck.mustRole();RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();// 當前登錄用戶User loginUser = userService.getLoginUser(request);UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);// 不需要權限,放行if (mustRoleEnum == null) {return joinPoint.proceed();}// 必須有該權限才通過UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());if (userRoleEnum == null) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR);}// 如果被封號,直接拒絕if (UserRoleEnum.BAN.equals(userRoleEnum)) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR);}// 必須有管理員權限if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) {// 用戶沒有管理員權限,拒絕if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR);}}// 通過權限校驗,放行return joinPoint.proceed();}
}

補充AOP
切面(Aspect):封裝橫切邏輯的模塊(如日志切面類)
連接點(Join Point):程序執行過程中的特定點(如方法調用、異常拋出)
通知(Advice):切面在連接點執行的操作,分為:

  • 前置通知(Before):方法執行前觸發(如權限校驗)。
  • 后置通知(After):方法執行后觸發(無論是否異常)。
  • 返回通知(AfterReturning):方法正常返回后觸發。
  • 異常通知(AfterThrowing):方法拋出異常后觸發。
  • 環繞通知(Around):包裹目標方法,控制執行流程(如事務管理)。在連接點執行目標方法調用前后可以自己編寫邏輯。
@Aspect
@Component
public class LoggingAspect {@Around("execution(* com.example.service.*.*(..))")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {// 前置邏輯(如參數校驗)Object result = pjp.proceed(); // 調用目標方法// 后置邏輯(如日志記錄)return result; // 可修改返回值}
}

切點(Pointcut):定義哪些連接點會被切面攔截(通過表達式指定),在本業務中只有有自定義注解并是admin,AOP就會攔截。

利用Sa-token來進行權限校驗

鏈接

實戰

1.引入依賴

<!-- Sa-Token 權限認證 -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.39.0</version>
</dependency>

2.編寫配置文件

# Sa-Token 配置
sa-token:# token 名稱(同時也是 cookie 名稱)token-name: mianshiya# token 有效期(單位:秒) 默認30天,-1 代表永久有效timeout: 2592000# token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統就會被凍結,默認-1 代表不限制,永不凍結active-timeout: -1# 是否允許同一賬號多地同時登錄(為 true 時允許一起登錄, 為 false 時新登錄擠掉舊登錄)is-concurrent: false# 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token, 為 false 時每次登錄新建一個 token)is-share: true# token 風格(默認可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)token-style: uuid# 是否輸出操作日志 is-log: true

3.注冊Sa-token攔截器

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {// 注冊 Sa-Token 攔截器,打開注解式鑒權功能 @Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注冊 Sa-Token 攔截器,打開注解式鑒權功能 registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    }
}

4.定義權限與角色獲取邏輯
實現 StpInterface 接口。該接口提供了獲取當前登錄用戶的權限和角色的方法,在每次調用鑒權代碼時,都會執行接口中的方法。

@Component // 保證此類被 SpringBoot 掃描,完成 Sa-Token 的自定義權限驗證擴展 
public class StpInterfaceImpl implements StpInterface {/*** 返回一個賬號所擁有的權限碼集合 (目前沒用)*/@Overridepublic List<String> getPermissionList(Object loginId, String s) {return new ArrayList<>();}/*** 返回一個賬號所擁有的角色標識集合 (權限與角色可分開校驗)*/@Overridepublic List<String> getRoleList(Object loginId, String s) {// 從當前登錄用戶信息中獲取角色User user = (User) StpUtil.getSessionByLoginId(loginId).get(USER_LOGIN_STATE);return Collections.singletonList(user.getUserRole());}
}

5.設備信息獲取工具類

/*** 設備工具類*/
public class DeviceUtils {/*** 根據請求獲取設備信息**/public static String getRequestDevice(HttpServletRequest request) {String userAgentStr = request.getHeader(Header.USER_AGENT.toString());// 使用 Hutool 解析 UserAgentUserAgent userAgent = UserAgentUtil.parse(userAgentStr);ThrowUtils.throwIf(userAgent == null, ErrorCode.OPERATION_ERROR, "非法請求");// 默認值是 PCString device = "pc";// 是否為小程序if (isMiniProgram(userAgentStr)) {device = "miniProgram";} else if (isPad(userAgentStr)) {// 是否為 Paddevice = "pad";} else if (userAgent.isMobile()) {// 是否為手機device = "mobile";}return device;}/*** 判斷是否是小程序* 一般通過 User-Agent 字符串中的 "MicroMessenger" 來判斷是否是微信小程序**/private static boolean isMiniProgram(String userAgentStr) {// 判斷 User-Agent 是否包含 "MicroMessenger" 表示是微信環境return StrUtil.containsIgnoreCase(userAgentStr, "MicroMessenger")&& StrUtil.containsIgnoreCase(userAgentStr, "MiniProgram");}/*** 判斷是否為平板設備* 支持 iOS(如 iPad)和 Android 平板的檢測**/private static boolean isPad(String userAgentStr) {// 檢查 iPad 的 User-Agent 標志boolean isIpad = StrUtil.containsIgnoreCase(userAgentStr, "iPad");// 檢查 Android 平板(包含 "Android" 且不包含 "Mobile")boolean isAndroidTablet = StrUtil.containsIgnoreCase(userAgentStr, "Android")&& !StrUtil.containsIgnoreCase(userAgentStr, "Mobile");// 如果是 iPad 或 Android 平板,則返回 truereturn isIpad || isAndroidTablet;}
}

6.編寫方法


// Sa-Token 登錄,并指定設備,同端登錄互斥
StpUtil.login(user.getId(), DeviceUtils.getRequestDevice(request));
StpUtil.getSession().set(USER_LOGIN_STATE, user);
@Override
public User getLoginUser(HttpServletRequest request) {// 先判斷是否已登錄Object loginUserId = StpUtil.getLoginIdDefaultNull();if (loginUserId == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);}// 從數據庫查詢(追求性能的話可以注釋,直接走緩存)User currentUser = this.getById((String) loginUserId);if (currentUser == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);}return currentUser;
}

7.在方法上添加注解

@SaCheckRole(UserConstant.ADMIN_ROLE)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899367.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899367.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899367.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于javaweb的SSM航班機票預訂平臺系統設計與實現(源碼+文檔+部署講解)

技術范圍&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬蟲、數據可視化、小程序、安卓app、大數據、物聯網、機器學習等設計與開發。 主要內容&#xff1a;免費功能設計、開題報告、任務書、中期檢查PPT、系統功能實現、代碼編寫、論文編寫和輔導、論…

格雷碼、漢明碼,CRC校驗的區別

格雷碼、漢明碼和CRC校驗都是用于數據傳輸和存儲中的編碼技術。 它們在原理、功能和應用場景上存在顯著區別。 1.格雷碼&#xff08;Gray Code&#xff09; ? 定義&#xff1a;格雷碼是一種特殊的二進制編碼&#xff0c;任意兩個相鄰的碼字之間僅有一位不同。 ? 功能&#x…

【報錯】 /root/anaconda3/conda.exe: cannot execute binary file: Exec format error

背景: 安裝Anaconda3 bash Anaconda3-****-Linux-x86_64.sh 報錯: /root/anaconda3/conda.exe: cannot execute binary file: Exec format error 原因分析: 安裝包(如

JAVA實現動態IP黑名單過濾

一些惡意用戶(可能是黑客、爬蟲、DDoS 攻擊者)可能頻繁請求服務器資源&#xff0c;導致資源占用過高。因此需要一定的手段實時阻止可疑或惡意的用戶&#xff0c;減少攻擊風險。 通過 IP 封禁&#xff0c;可以有效拉黑攻擊者&#xff0c;防止資源被濫用&#xff0c;保障合法用戶…

開源的CMS建站系統可以隨便用嗎?有什么需要注意的?

開源CMS建站系統雖然具有許多優點&#xff0c;但并非完全“隨便用”。無論選哪個CMS系統&#xff0c;大家在使用的時候&#xff0c;可以盡可能地多注意以下幾點&#xff1a; 1、版權問題 了解開源許可證&#xff1a;不同的開源CMS系統采用不同的開源許可證&#xff0c;如GPL、…

故障識別 | 基于改進螂優化算法(MSADBO)優化變分模態提取(VME)結合稀疏最大諧波噪聲比解卷積(SMHD)進行故障診斷識別,matlab代碼

基于改進螂優化算法&#xff08;MSADBO&#xff09;優化變分模態提取&#xff08;VME&#xff09;結合稀疏最大諧波噪聲比解卷積&#xff08;SMHD&#xff09;進行故障診斷識別 一、引言 1.1 機械故障診斷的背景和意義 在工業生產的宏大畫卷中&#xff0c;機械設備的穩定運行…

探究 CSS 如何在HTML中工作

2025/3/28 向全棧工程師邁進&#xff01; 一、CSS的作用 簡單一句話——美化網頁 <p>Lets use:<span>Cascading</span><span>Style</span><span>Sheets</span> </p> 對于如上代碼來說&#xff0c;其顯示效果如下&#xff1…

硬件老化測試方案的設計誤區

硬件老化測試方案設計中的常見誤區主要包括測試周期不足、測試條件過于單一、樣品選擇不當等方面。其中&#xff0c;測試周期不足尤為突出&#xff0c;容易導致潛在缺陷未被完全暴露。老化測試本質上是通過加速產品老化來模擬長期使用狀況&#xff0c;因此測試周期不足會嚴重削…

無錫零碳園區“三年突圍”安科瑞源網荷儲充系統如何破解“綠電難、儲能貴、調度亂”困局?

零碳園區建設如火如荼&#xff0c;為何企業“不敢投、不會用”&#xff1f; 無錫市政府3月27日發布《零碳園區建設三年行動方案》&#xff0c;目標到2027年建成10家以上零碳園區、20家零碳工廠、10個源網荷儲一體化項目。但企業仍存疑慮&#xff1a; 綠電消納難&#xff1a;光…

docker torcherve打包mar包并部署模型

使用Docker打包深度網絡模型mar包到服務端 參考鏈接&#xff1a;Docker torchserve 部署模型流程——以WSL部署YOLO-FaceV2為例_class myhandler(basehandler): def initialize(self,-CSDN博客 1、docker拉取環境鏡像命令 docker images出現此提示為沒有權限取執行命令&…

Redis 分布式鎖實現深度解析

Redis 分布式鎖是分布式系統中協調多進程/服務對共享資源訪問的核心機制。以下從基礎概念到高級實現進行全面剖析。 一、基礎實現原理 1. 最簡實現&#xff08;SETNX 命令&#xff09; # 加鎖 SET resource_name my_random_value NX PX 30000# 解鎖&#xff08;Lua腳本保證原…

kubernetes》》k8s》》 kubeadm、kubectl、kubelet

kubeadm 、kubectl 、kubelet kubeadm、kubectl和kubelet是Kubernetes中不可或缺的三個組件。kubeadm負責集群的快速構建和初始化&#xff0c;為后續的容器部署和管理提供基礎&#xff1b;kubectl作為命令行工具&#xff0c;提供了與Kubernetes集群交互的便捷方式&#xff1b;而…

linux 硬盤擴展

場景&#xff1a; [rootlocalhost ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 40G 0 disk ├─sda1 8:1 0 1M 0 part ├─sda2 8:2 0 1G 0 part /boot └─sda3 …

Docker Desktop 界面功能介紹

Docker Desktop 界面功能介紹 左側導航欄 Containers(容器): 用于管理容器,包括查看運行中或已停止的容器,檢查容器狀態、日志,執行容器內命令,啟動、停止、刪除容器等操作。Images(鏡像): 管理本地 Docker 鏡像,可查看鏡像列表、從 Docker Hub 拉取新鏡像、刪除鏡…

C++細節知識for面試

1. linux上C程序可用的棧和堆大小分別是多少&#xff0c;為什么棧大小小于堆&#xff1f; 1. 棧&#xff08;Stack&#xff09;大小 棧默認為8MB&#xff0c;可修改。 為什么是這個大小&#xff1a; ?安全性&#xff1a;限制棧大小可防止無限遞歸或過深的函數調用導致內存…

數據設計(范式、步驟)

文章目錄 數據設計1.數據庫設計的三大范式2、數據庫設計的具體步驟 數據設計 1.數據庫設計的三大范式 關系型數據庫的三大范式&#xff0c;指導如何設計一個關系型數據庫。 1NF&#xff1a; 關系表的每個字段&#xff0c;都應該是不可再分的&#xff0c;——保證原子性。 字…

PhotoShop學習03

1.更改圖像大小 通常情況下&#xff0c;如果我們想在某些上傳圖片&#xff0c;會發現我們的圖片可能會過大或者過小&#xff0c;為此&#xff0c;我們需要調整圖像的大小&#xff0c;使之符合網站的規則。 首先打開photoshop&#xff0c;打開一張圖片。首先我們需要了解這張圖…

Vue 項目中使用$refs來訪問組件實例或 DOM 元素,有哪些注意事項?

大白話Vue 項目中使用$refs來訪問組件實例或 DOM 元素&#xff0c;有哪些注意事項&#xff1f; 在 Vue 項目里&#xff0c;$refs 是個超實用的工具&#xff0c;它能讓你直接訪問組件實例或者 DOM 元素。不過使用的時候&#xff0c;有一些地方可得注意&#xff0c;下面咱就詳細…

【安全運營】關于攻擊面管理相關概念的梳理(二)

CYNC&#xff08;持續可見性和網絡控制&#xff09; CYNC&#xff08;Continuous Visibility and Network Control&#xff09;即“持續可見性和網絡控制”&#xff0c;是一個與網絡安全和IT運營管理相關的概念。它強調的是在一個組織的數字環境中&#xff0c;確保對所有資產、…

【區塊鏈安全 | 第二篇】區塊鏈概念詳解

文章目錄 概述1. 區塊鏈類型2 區塊鏈五層架構3 賬本模型4. 節點&#xff08;Node&#xff09;5. 區塊&#xff08;Block&#xff09;6. 區塊鏈&#xff08;Blockchain&#xff09;7. 區塊鏈工作流程 核心技術1. 共識機制2. 智能合約 主要組件1. 交易&#xff08;Transaction&am…