學習筆記--基于Sa-Token 實現Java項目單點登錄+同端互斥檢測

目錄

同端互斥登錄

單點登錄SSO

架構選型

模式二: URL重定向傳播

前后端分離

整體流程

準備工作

搭建客戶端

搭建認證中心SSO Server

環境配置

開放認證接口

啟動類

跨域處理

同端互斥登錄

同端互斥登陸 模塊

同端互斥登錄指:同一類型設備上只允許單地點登錄,在不同類型設備上允許同時在線。比如 QQ 可以手機電腦同時在線,但是不能在兩個手機上同時登錄一個賬號。具體步驟如下

sa-token:# token 名稱(同時也是 cookie 名稱)token-name: praxisAI# token 有效期(單位:秒) 默認30天,-1 代表永久有效timeout: 2592000# token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統就會被凍結,默認-1 代表不限制,永不凍結active-timeout: -1# 是否開啟同端互斥登錄 (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
  1. 在配置文件中,將 isConcurrent 配置為false

  2. DeviceUtils工具類:判斷前端請求傳來的設備信息,比如pc,后續設置到Sa-Token登錄態中

//設備工具類
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;}
}

?3.?登錄時傳入參數

// 使用 Sa-Token 登錄,并指定設備,同端登錄互斥
StpUtil.login(user.getId(), DeviceUtils.getRequestDevice(request));//例如 調用此方法登錄后,同設備的會被頂下線(不同設備不受影響)
StpUtil.login(10001, "PC");     

Sa-Token 在背后做了大量的工作,包括

  1. 檢查此賬號是否之前已有登錄,為賬號生成 Token(uuid 風格) 憑證與 Session 會話

  2. 記錄 Token 活躍時間;

  3. 通知全局偵聽器,xx 賬號登錄成功;

  4. Token 注入到請求上下文返回給前端 等等其它工作……(利用cookie自動注入特性)

    1. Cookie 可以從后端控制往瀏覽器中寫入 token 值。

    2. Cookie 會在前端每次發起請求時自動提交 token 值。

JWT token模式

//1、登錄成功后:后端將 token 返回到前端
SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); ?//包括tokenName和tokenValue
return SaResult.data(tokenInfo); ?//返回給前端 
?
//2.前端保存到localstorge中,下次請求帶上
//3.后端Sa-Token 就能像傳統PC端一樣自動讀取到 token 值,進行鑒權

記住我模式

原理:調用StpUtil.login(10001, true),在瀏覽器寫入一個持久Cookie儲存 Token,此時用戶即使重啟瀏覽器 Token 依然有效。

Sa-Token的登錄授權,默認就是[記住我]模式,為了實現[非記住我]模式,你需要在登錄時如下設置

// 設置登錄賬號id為10001,第二個參數指定是否為[記住我],當此值為false后,關閉瀏覽器后再次打開需要重新登錄
StpUtil.login(10001, false);

注銷模塊

// 檢驗當前會話是否已經登錄, 如果未登錄,則拋出異常:`NotLoginException`:代表當前會話暫未登錄
StpUtil.checkLogin();
// 移除登錄態
StpUtil.logout();

獲取當前登錄用戶

// 獲取當前會話賬號id, 如果未登錄,則返回 null 
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;
單點登錄SSO

Sa-Token

單點登錄舉個例子理解

假設系統被切割為多個部分:商城、論壇、直播、社交…… 如果用戶每訪問一個模塊都要登錄一次,那么這樣就很麻煩了, 為了優化用戶體驗,需要一套機制將這N個系統的認證授權互通共享,讓用戶在一個系統登錄之后,可以暢通無阻的訪問其它所有系統。

凡是稍微上點規模的系統,統一認證中心都是繞不過去的檻。

架構選型
系統架構采用模式簡介文檔鏈接
前端同域 + 后端同 Redis模式一共享 Cookie 同步會話文檔、示例
前端不同域 + 后端同 Redis模式二URL重定向傳播會話文檔、示例
前端不同域 + 后端不同 Redis模式三Http請求獲取會話文檔、示例
  1. 前端同域:指多個系統可以部署在同一個主域名之下,比如:c1.domain.comc2.domain.comc3.domain.com

  2. 后端同Redis:指多個系統可以連接同一個Redis,PS:這里并不需要把所有項目的數據都放在同一個Redis中,Sa-Token提供了 [權限緩存與業務緩存分離] 的解決方案,詳情戳: Alone獨立Redis插件。

  3. 模式三:Http請求獲取會話(Sa-Token對SSO提供了完整的封裝,只需要復制幾段代碼便可以輕松集成)

根據本項目場景,后端同Redis,所以選擇模式二+ 前后端分離架構

SaToken SSO優勢

  1. API 簡單易用,且官方文檔介紹很詳細。

  2. 支持三種模式,是否跨域、是否共享Redis、是否前后端分離 等場景很輕松解決

  3. 內置域名校驗、密鑰校驗、Token防竊取,安全性很高

  4. 不丟參數,相比其他單點登錄框架,Sa-Token-SSO有專門的算法保證了參數不丟失,登錄成功之后原路返回頁面。

模式二: URL重定向傳播

如果多個系統部署在不同的域名之下,但是后端可以連接同一個Redis,那么便可以使用 [URL重定向傳播會話] 的方式做到單點登錄

首先我們再次復習一下,多個系統之間為什么無法同步登錄狀態?

  1. 前端的Token無法在多個系統下共享。

  2. 后端的Session無法在多個系統間共享。

第二點官方已使用 Alone獨立Redis插件 做到權限緩存直連 SSO-Redis 數據中心,不再贅述。

  1. 未登錄,客戶端頁面顯示“登錄”鏈接。

  2. 點擊登錄,攜帶當前頁面back參數(包含客戶端host),跳轉到當前客戶端的/sso/login接口

    1. 未登錄時,重定向到SSO服務器的登錄頁

    2. 用戶在SSO登錄頁登錄成功后,SSO服務器返回ticket,重定向回指定客戶端

  3. 客戶端通過SaSsoClientProcessor向SSO服務器驗證ticket的有效性,獲取用戶ID

  4. 使用StpUtil.login(userId)在本地登錄用戶

在跨域模式下, "共享Cookie方案" 的失效,所以必須采用一種新的方案來傳遞Token。

前后端分離

首先理解對于一個前后端分離項目,即我們的系統,整體流程是這樣的

前端==>后端==>重定向到SSO認證中心(展示頁面,所以SSO服務器代碼是前后端不分離的,主要作用就是:展示登錄頁,校驗登錄和重定向

這種情況要考慮跨域,即客戶端與 SSO 服務器部署在不同域,缺點:前端需主動調用 /sso/getSsoAuthUrl 獲取登錄地址,并處理重定向邏輯

整體流程
  1. 前端頁面點擊 [登錄] 后觸發調用登錄函數,調用后端接口/sso/login,并攜帶back參數( 包含客戶端host )

    • 形如:http://{sso-client}/sso/login?back=xxx

  2. 若子系統檢測到此用戶尚未登錄,則直接重定向到SSO認證中心,并攜帶redirect參數(記錄子系統的登錄頁URL

    • 形如:http://{sso-server}/sso/auth?redirect=xxx?back=xxx

  3. 用戶進入 SSO認證中心 的登錄頁面,開始登錄。

  4. 用戶 輸入賬號密碼 并 登錄成功,SSO認證中心下放ticket碼參數。重定向回客戶端的登錄接口/sso/login

    • 形如:http://{sso-client}/sso/login?back=xxx&ticket=xxxxxxxxx

  5. 客戶端通過SaSsoClientProcessor向SSO服務器驗證ticket的有效性,獲取用戶ID,使用StpUtil.login(userId)在本地登錄用戶

  6. 其他客戶端嘗試登錄后,前面步驟一樣,到了請求發到SSO服務器,會通過sso/auth判斷是否已經登錄(已登錄內部是通過redis查看用戶id來判斷的)然后直接返回Ticket進行登錄即可

整個過程,除了第四步用戶在SSO認證中心登錄時會被打斷,其余過程均是自動化的

當用戶在另一個子系統再次點擊[登錄]按鈕,由于此用戶在SSO認證中心已有會話存在, 所以第四步也將自動化,也就是單點登錄的最終目的 一次登錄,處處通行。

流程如下

準備工作

首先修改hosts文件(C:\Windows\System32\drivers\etc\hosts),添加以下IP映射,方便測試:

127.0.0.1 sa-sso-server.com ? ?#ip映射,用于測試多個客戶端單點登錄
127.0.0.1  sa-sso-client1.com ? 
127.0.0.1 sa-sso-client2.com
搭建客戶端

客戶端就是我們的后端服務器,考慮到前后分離架構,比如用VUE3前端,下面是一個模擬的前端例子

<!-- 項目首頁 -->
<template><h2> Sa-Token SSO-Client 應用端(前后端分離版-Vue3) </h2><p>當前是否登錄:<b>{{isLogin}}</b></p><p><router-link :to="loginUrl">登錄</router-link>&nbsp;&nbsp;  <!--點擊登錄后攜帶back參數請求后端sso/login接口--></p>
</template><script setup>
import { ref } from 'vue'
import {baseUrl, ajax} from './method-util.js'// 單點登錄地址
const loginUrl = '/api/sso/login?back=' + encodeURIComponent(location.href);
// 是否登錄
const isLogin = ref(false);// 1.查詢當前會話是否登錄
ajax('/api/sso/isLogin', {}, function (res) {console.log('/isLogin 返回數據:', res);isLogin.value = res.data;
})
</script>
<!-- Sa-Token-SSO-Client端-登錄頁 -->
<template>
</template>
<script setup>
import {onMounted} from "vue";
import {ajax, getParam} from './method-util.js';
import router from '../router';// 獲取參數
const back = getParam('back') || router.currentRoute.value.query.back;
const ticket = getParam('ticket') || router.currentRoute.value.query.ticket;console.log('獲取 back 參數:', back)
console.log('獲取 ticket 參數:', ticket)// 頁面加載后觸發
onMounted(() => {if(ticket) {    //2.如果ticket存在,則嘗試通過ticket登錄doLoginByTicket(ticket);} else {goSsoAuthUrl();  //3.不存在,則重定向至認證中心}
})// 重定向至認證中心方法
function goSsoAuthUrl() {ajax('/api/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {console.log('/api/sso/getSsoAuthUrl 返回數據', res);location.href = res.data;})
}// 根據ticket值登錄 方法
function doLoginByTicket(ticket) {ajax('/api/sso/doLoginByTicket', {ticket: ticket}, function(res) {console.log('/api/sso/doLoginByTicket 返回數據', res);if(res.code === 200) {localStorage.setItem('satoken', res.data);location.href = decodeURIComponent(back);} else {alert(res.msg);}})
}
</script>

搭建后端

環境配置

<!-- Sa-Token 權限認證,在線文檔:https://sa-token.cc --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.40.0</version></dependency><!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.40.0</version></dependency><!-- 提供Redis連接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- Sa-Token 插件:整合SSO --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-sso</artifactId><version>1.40.0</version></dependency><!-- Sa-Token插件:權限緩存與業務緩存分離 --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-alone-redis</artifactId><version>1.40.0</version></dependency>

配置文件中整合sso-client相關配置+API密鑰+redisticket

sa-token:# token 名稱(同時也是 cookie 名稱)token-name: praxisAI# token 有效期 2天(單位:秒) 默認30天,-1 代表永久有效timeout: 172800# token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統就會被凍結,默認-1 代表不限制,永不凍結active-timeout: -1# 是否開啟同端互斥登錄 (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# SSO-相關配置sso-client:# SSO-Server 端主機地址server-url: http://sa-sso-server.com:9000sign:# API 接口調用秘鑰,確保和服務端一致,sa-token官方文檔會給secret-key: xxxx# 配置Sa-Token單獨使用的Redis連接 (此處需要和SSO-Server端連接同一個Redis)alone-redis:# Redis數據庫索引 (默認為0)database: 1# Redis服務器地址host: xxx# Redis服務器連接端口port: 6379# Redis服務器連接密碼(默認為空)password: xxxtimeout: 10slettuce:pool:# 連接池最大連接數max-active: 200# 連接池最大阻塞等待時間(使用負值表示沒有限制)max-wait: -1ms# 連接池中的最大空閑連接max-idle: 10# 連接池中的最小空閑連接min-idle: 0
forest:# 關閉 forest 請求日志打印log-enabled: false

新建Controller作為客戶端接口,用于重定向到SSO服務器和接收服務器參數

//前后臺分離架構下集成SSO所需的代碼 (SSO-Client端)
@RestController
public class H5Controller {// 當前是否登錄@GetMapping("/sso/isLogin")public Object isLogin() {return SaResult.data(StpUtil.isLogin());}// 返回SSO認證中心登錄地址 @GetMapping("/sso/getSsoAuthUrl")public SaResult getSsoAuthUrl(String clientLoginUrl) {String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");return SaResult.data(serverAuthUrl);}// 根據ticket進行登錄@PostMapping("/sso/doLoginByTicket")public SaResult doLoginByTicket(String ticket) {SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/api/sso/doLoginByTicket");StpUtil.login(ctr.loginId, ctr.remainSessionTimeout);return SaResult.data(StpUtil.getTokenValue());}// 全局異常攔截 @ExceptionHandlerpublic SaResult handlerException(Exception e) {e.printStackTrace(); return SaResult.error(e.getMessage());}	
}
搭建認證中心SSO Server

新建一個springboot項目作為認證中心,本項目后端系統算做一個客戶端client,后續引入多個后端系統,也可以按照客戶端方式構建

環境配置
<!-- Sa-Token 權限認證,在線文檔:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.40.0</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-sso</artifactId><version>1.40.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.40.0</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><!-- 除此之外還需要引入mysql數據庫的一些依賴,因為要在認證中心實現登錄校驗 -->
# 端口
server:port: 9000
# Sa-Token 配置
sa-token:# ------- SSO-模式二相關配置 sso-server:# Ticket有效期 (單位: 秒),默認五分鐘 ticket-timeout: 300# 所有允許的授權回調地址allow-url: "http://sa-sso-client1/api/sso/login,http://sa-sso-client2/api/sso/login"sign:# API 接口調用秘鑰secret-key: xxx
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://xxx/xxxusername: xxxpassword: xxx# Redis配置 (SSO模式一和模式二使用Redis來同步會話)redis:# Redis數據庫索引(默認為0)database: 1# Redis服務器地址host: xxx# Redis服務器連接端口port: 6379# Redis服務器連接密碼(默認為空)password: xxx# 連接超時時間timeout: 10slettuce:pool:# 連接池最大連接數max-active: 200# 連接池最大阻塞等待時間(使用負值表示沒有限制)max-wait: -1ms# 連接池中的最大空閑連接max-idle: 10# 連接池中的最小空閑連接min-idle: 0forest: # 關閉 forest 請求日志打印log-enabled: false
開放認證接口

新建 SsoServerController,用于對外開放接口,先直接拉官方server-demo,在修改

//SSO Server端 Controller
@RestController
public class SsoServerController {//處理所有SSO相關請求:拆分式路由// SSO-Server:統一認證地址,接受參數:redirect=授權重定向地址// 作用: 用戶未登錄,重定向到登陸頁面//    ** 已登錄,生成Ticket重定向回客戶端**(已登錄內部是通過redis查看用戶id來判斷的)@RequestMapping("/sso/auth")public Object ssoAuth() {return SaSsoServerProcessor.instance.ssoAuth();}// SSO-Server:RestAPI 登錄接口,賬號密碼登錄接口,接受參數:name、pwd// 作用: 處理登錄表單提交,調用doLoginHandle進行驗證//      驗證成功后生成SSO票據并返回給客戶端@RequestMapping("/sso/doLogin")public Object ssoDoLogin() {return SaSsoServerProcessor.instance.ssoDoLogin();}/*** 鹽值,混淆密碼*/public static final String SALT = "kk";@Resourceprivate UserService userService;// 配置SSO相關參數 @Autowiredprivate void configSso(SaSsoServerConfig ssoServer) {// 自定義API地址,用于修改統一認證中心的地址//SaSsoServerProcessor.instance.ssoServerTemplate.apiName.ssoAuth = "/sso/auth2";// 配置:未登錄時返回的ViewssoServer.notLoginView = () -> {return new ModelAndView("sa-login.html");};// 配置:登錄處理函數  參數:賬號密碼ssoServer.doLoginHandle = (name, pwd) -> {// 1. 校驗if (StringUtils.isAnyBlank(name, pwd)) {return SaResult.error("參數為空");}if (name.length() < 4) {return SaResult.error( "賬號錯誤");}if (pwd.length() < 8) {return SaResult.error("密碼錯誤");}// 2. 加密String encryptPassword = DigestUtils.md5DigestAsHex((SALT + pwd).getBytes());// 查詢用戶是否存在QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userAccount", name);queryWrapper.eq("userPassword", encryptPassword);User user = userService.getUserInfo(queryWrapper);// 用戶不存在if (user == null) {// 登錄失敗,重定向到 /sso/auth 并攜帶錯誤參數return "redirect:/sso/auth?error=用戶不存在或密碼錯誤";}// 獲取當前請求HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();String device = DeviceUtils.getRequestDevice(request);// 使用 Sa-Token 登錄,并指定設備,同端登錄互斥StpUtil.login(user.getId(), device);StpUtil.getSession().set("user_login", user);return SaResult.ok("登錄成功!").setData(StpUtil.getTokenValue());};}// 在 SsoServerController 或全局配置中添加@Beanpublic SaServletFilter saServletFilter() {return new SaServletFilter().setBeforeAuth(obj -> {// 設置響應頭防止緩存SaHolder.getResponse().setHeader("Cache-Control", "no-cache, no-store, must-revalidate");SaHolder.getResponse().setHeader("Pragma", "no-cache");SaHolder.getResponse().setHeader("Expires", "0");});}
}
啟動類
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}System.out.println();System.out.println("---------------------- Sa-Token SSO 統一認證中心啟動成功 ----------------------");System.out.println("配置信息:" + SaSsoManager.getServerConfig());System.out.println();
}
跨域處理

配置項 sa-token.sso-server.allow-url=* 意為配置所有允許的Client端授權地址,不在此配置項中的URL將無法單點登錄成功

但是,在生產環境中,此配置項絕對不能配置為 * ,否則會有被Ticket劫持的風險

假設攻擊者根據模仿我們的授權地址,巧妙的構造一個URL

http://sa-sso-server.com:9000/sso/auth?redirect=https://www.baidu.com/

當不知情的小白被誘導訪問了這個URL時,它將被重定向至百度首頁,代表著用戶身份的Ticket碼也顯現到了URL之中

防范處理

redirect參數進行校驗,如果其不在指定的URL列表中時,拒絕下放ticket

#SSO服務器端進行配置
sa-token: sso-server: # 配置允許單點登錄的 url ? allow-url: "http://localhost:8101/sso/login" ?#配置到詳細地址  

為什么不直接回傳 Token,而是先回傳 Ticket,再用 Ticket 去查詢對應的賬號id?

Token 作為長時間有效的會話憑證,任何時候都不應該直接暴露在 URL 之中(雖然 Token 很安全,但會直接暴露為很多漏洞提供可乘之機)

為了讓系統絕對安全,選擇先回傳 Ticket,再由Ticket獲取賬號id,而且 Ticket 一次性用完即廢,提高安全性。

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

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

相關文章

本地生活服務APP開發,市場發展全新商業機遇

隨著移動互聯網的快速發展&#xff0c;人們的消費和生活習慣發生了巨大改變&#xff0c;本地生活服務市場迎來了發展爆發期&#xff01;從外賣、團購等&#xff0c;人們越來越依賴通過手機APP解決日常生活中的各種需求。對于企業而言&#xff0c;一款完善、多樣、便捷的本地生活…

當科技業成為系統性壓榨的絞肉機

深夜的硅谷辦公室依然燈火通明&#xff0c;鍵盤敲擊聲此起彼伏。一位程序員在Slack上收到主管的緊急需求&#xff1a;“這個功能明早必須上線。”他苦笑一聲&#xff0c;關掉手機里名為“緩解焦慮”的冥想App——這已是本周第三次被迫服用公司提供的“心靈解藥”。此刻&#xf…

代碼隨想錄算法訓練營第五十六天 | 108.冗余連接 109.冗余連接II

108. 冗余連接 卡碼網題目鏈接&#xff08;ACM模式&#xff09;(opens new window) 題目描述 有一個圖&#xff0c;它是一棵樹&#xff0c;他是擁有 n 個節點&#xff08;節點編號1到n&#xff09;和 n - 1 條邊的連通無環無向圖&#xff08;其實就是一個線形圖&#xff09;…

什么是索引?為什么要使用B樹作為索引數據結構?

MySQL的事務特性 1.原子性:原子性就是這個事件要么執行完,要么沒執行,不會存在中間狀態,與C中華那個加鎖避免多線程競爭是一個道理; 2.一致性:保持事件的操作對象雙方某數據之和是不變的,就以轉賬為例,A轉給B100塊,那么A的余額多100,B的余額就必須少100; 3.隔離性:隔離就是獨…

pyqt5報錯:qt.qpa.plugin: Could not find the Qt platform plugin “xcb“(已解決)

我在使用pyqt庫的時候報錯&#xff1a; qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in \ "/mnt/private_disk/anaconda3/envs/aot-manip/lib/python3.8/site-packages/PyQt5/Qt5/plugins/platforms" even though it was found. This ap…

AI大模型全攻略:原理 · 部署 · Prompt · 場景應用

?? AI大模型全攻略:原理 部署 Prompt 場景應用 本文從基礎原理到實踐部署,再到 Prompt 工程與典型應用案例,全方位解析 AI 大模型的學習路徑與使用方法,適合開發者、產品經理、技術愛好者等不同背景讀者。 ?? 一、什么是 AI 大模型? AI 大模型(Large Language Mo…

2024年MathorCup數學建模D題量子計算在礦山設備配置及運營中的建模應用解題文檔與程序

2024年第十四屆MathorCup高校數學建模挑戰賽 D題 量子計算在礦山設備配置及運營中的建模應用 原題再現&#xff1a; 隨著智能技術的發展&#xff0c;智慧礦山的概念越來越受到重視。越來越多的設備供應商正在向智慧礦山整體解決方案供應商轉型&#xff0c;是否具備提供整體解…

Flink 流處理框架的核心特性

文章目錄 事件時間支持Flink狀態編程一、狀態的類型1. 托管狀態&#xff08;Managed State&#xff09;2. 原始狀態&#xff08;Raw State&#xff09; 二、狀態的管理和容錯 Flink端到端的一致性1、檢查點機制2、冪等3、事務 水位線窗口操作1、窗口類型2、窗口操作的時間語義 …

交換機(access端口)

任務&#xff1a;對access有更深入的理解 通過網盤分享的文件&#xff1a;交換機&#xff08;access&#xff09;.zip 鏈接: https://pan.baidu.com/s/1cMC6Na_1PLo6zOHazFplQQ?pwd23a5 提取碼: 23a5 SW1 <Huawei>sys [Huawei]dis vlan The total number of vlans …

《鳥哥的Linux私房菜基礎篇》---5 vim 程序編輯器

目錄 一、vim程序編輯器的簡介 二、命令模式快捷鍵&#xff08;默認模式&#xff09; 1、光標移動 2、編輯操作 3、搜索與替換 三、插入模式快捷鍵 四、底行模式快捷鍵&#xff08;按&#xff1a;進入&#xff09; 五、高級技巧 1、分屏操作 2、多文件編輯 3、可視化…

AI大白話(四):自然語言處理——AI是如何理解和生成人類語言的?

??引言: 專欄:《AI大白話》 AI大白話(一):5分鐘了解AI到底是什么? AI大白話(二):機器學習——AI是怎么“學習“的? AI大白話(三):深度學習——AI的‘大腦‘是如何構建的? 大家好!歡迎回到"AI大白話"系列。前面我們聊了AI的基本概念、機器學習的原理…

擴展卡爾曼濾波

1.非線性系統的線性化 標準卡爾曼濾波 適用于線性化系統&#xff0c;擴展卡爾曼濾波 則擴展到了非線性系統&#xff0c;核心原理就是將非線性系統線性化&#xff0c;主要用的的知識點是 泰勒展開&#xff08;我另外一篇文章的鏈接&#xff09;&#xff0c;如下是泰勒展開的公式…

安裝unsloth

我在llamafactory微調LLM&#xff0c;簡單測了一些&#xff08;很不精準&#xff09;&#xff0c;加速方法中unsloth比flash_attention速度快了40%&#xff0c;顯存占用減少15%&#xff1b; 創建虛擬環境&#xff1a;conda create -n env_name python3.10, 然后conda activate…

關于 51 單片機顯示多個數碼管時出現殘影

殘影現象&#xff1a; 出現殘影代碼&#xff1a; #include <REGX52.H> #include <INTRINS.H> void Delayxms(unsigned int x) //11.0592MHz {while(x){unsigned char i, j;_nop_();i 2;j 199; do{while (--j);} while (--i);x--;} } void DisplayDigitalNumb…

STM32學習筆記之常用外設接口(原理篇)

&#x1f4e2;&#xff1a;如果你也對機器人、人工智能感興趣&#xff0c;看來我們志同道合? &#x1f4e2;&#xff1a;不妨瀏覽一下我的博客主頁【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸對你有幫助&#xff0c;可點贊 &#x1f44d;…

InnoDB 引擎核心知識點

InnoDB 引擎核心知識點 6.1 邏輯存儲結構 表空間&#xff08;Tablespace&#xff09;&#xff1a;所有數據邏輯上存儲在一個表空間中&#xff0c;物理上可能由多個文件組成。段&#xff08;Segment&#xff09;&#xff1a;分為數據段&#xff08;B樹葉子節點&#xff09;、索引…

深度學習 Deep Learning 第9章 卷積網絡 CNN

深度學習 Deep Learning 第9章 卷積網絡 章節概述 本章深入探討了卷積網絡的原理、變體及其在深度學習中的應用。卷積網絡通過卷積操作實現了參數共享和稀疏連接&#xff0c;顯著提高了模型的效率和性能。本章首先介紹了卷積操作的基本形式及其在不同數據維度上的應用&#x…

基于MATLAB的渦旋光和高斯光疊加產生平頂光

強度疊加耦合成平頂光&#xff0c;不發生干涉 通過分別生成高斯光和渦旋光的強度分布&#xff0c;然后按合適的權重將它們疊加&#xff0c;得到近似平頂光&#xff08;flat‐top beam&#xff09;的效果。由于我們只是將強度相加&#xff08;而非復振幅疊加&#xff09;&#…

wordpress-網站百寶箱插件

含置頂,網頁寵物, 哀悼, 禁止復制, 禁止查看源碼, 彈幕, WP優化,媒體分類,預加載,定時發布,在線客服, 留言板, 手機客服, 網站背景, 公告, 跑馬燈, 水印, 分享, 打賞, 海報圖, 廣告,數據庫管理,圖片加載特效。等綜合功能插件

北斗導航 | 基于北斗三號短報文通信的北斗-YOLO融合系統原理,算法公式,系統流程框圖,matlab代碼,應用場景

以下是關于基于北斗三號短報文通信的北斗-YOLO融合系統的詳細解析,包含原理、算法公式、系統流程、Matlab代碼框架和應用場景。一、系統原理 北斗-YOLO融合系統結合了北斗三號短報文通信(雙向通信能力)和YOLO目標檢測算法,用于在無地面網絡覆蓋區域實現實時目標檢測與數據傳…