Go語言企業級權限管理系統設計與實現

最近跟著學長再寫河南師范大學附屬中學圖書館的項目,學長交給了我一個任務,把本項目的權限管理給吃透,然后應用到下一個項目上。

我當然是偷著樂吶,因為讀代碼的時候,總是莫名給我一種公費旅游的感覺。
本來就想去了解圖書管理這個項目的全貌。但一直騰不出時間。
現在正巧,我要寫一個權限管理,正好可以拐回來細細品讀圖書管理系統的代碼( ̄﹃ ̄)。

熟悉的配方,本項目使用的是RBAC模型來管理權限。

目錄

一、RBAC

1、傳統方案

2、如何通過RBAC改進?

3、如何設計代碼?

二、中間件設計

1、理論設計方式

2、圖書館項目的設計

a.跳過重復路徑

b.獲取JWT憑證

可以拓展一下(AI):

三、基于圖書館的權限樹設計

1、權限樹設計

2、權限層級映射:

四、圖書館項目

1、整體架構:

2、核心組件解析

a、 用戶信息結構 (UserInfo)

b、角色模型 (Role)

c、權限樹結構 (Permission)

3、權限設計層級

a、三級權限結構:

五、前端如何進行權限控制

現實場景:

完整權限控制

首先從后端獲取權限數據

第一層防護:菜單不顯示

第二層防護:系統管理子菜單過濾

第三層防護:權限指令控制

第四層防護:組合式函數權限檢查

第五層防護:直接URL訪問

收獲:


一、RBAC

為什么需要RBAC來管理項目的呢

大家可以想象這樣一個場景:

想象你是一所大學圖書館的IT負責人。新學期開始了,
圖書館迎來了以下用戶:

學生小王:只想借書還書,查看自己的借閱記錄
老師張三:除了借書,還需要幫學生查詢圖書,管理班級借閱情況
管理員李四:需要添加新書、管理用戶賬號、查看所有借閱統計
系統管理員王五:擁有系統的完全控制權,包括備份數據、修改系統配置

假設沒有權限管理,可能會發生什么事情呢?

學生小王誤點了"刪除所有圖書"按鈕
老師張三想查看其他班級的借閱情況被拒絕了
管理員李四無法訪問系統設置,找你求助
......

所以權限管理,是非常必要的!!

現在問題來了:在咱們項目中,如何為他們添加權限?

1、傳統方案

傳統的解決方式是什么?在代碼里寫死:

// 傳統方式:硬編碼權限檢查
func DeleteBook(userType string) {if userType == "student" {return // 學生不能刪除}if userType == "teacher" {return // 老師也不能刪除}if userType == "admin" {// 只有管理員能刪除deleteBook()}
}

每次有新角色加入,你都要修改代碼...
這樣寫有什么問題?
1. 新增一個"圖書管理員"角色,要改遍所有函數
2. 權限規則散落在各處,難以維護
3. 想臨時給某個老師管理員權限?改代碼重新部署!

我在面向對象的七大設計原則一文中提到,接口的設計中的開閉原則中的,閉原則,就是為了解決解決每次有新改動,就要修改原有的代碼。

所以直接把代碼寫死,極其不合理,那該如何解決?

2、如何通過RBAC改進?

什么是RBAC呢?

大家可以想象到這樣一種場景:

公司的門禁卡系統
? ?- 員工卡:只能進辦公區
? ?- 管理卡:能進辦公區+會議室
? ?- 主管卡:能進所有區域

這就是靈感:能不能給用戶分配"權限卡"?

咱們可以這樣設計系統:

用戶(User) ←→ 角色(Role) ←→?權限(Permission)

這里的角色相當于上方公司的門禁卡

具體來說:
- 小王?→ 學生角色 → [借書, 還書, 查看個人記錄]
- 張三 → 教師角色 → [借書, 還書, 查看班級記錄, 推薦圖書]
- 李四 → 管理員角色 → [所有學生權限 + 添加圖書 + 用戶管理]

3、如何設計代碼?

第一步:定義角色權限

// 不再硬編碼,而是用數據庫存儲
1、定義角色
type Role struct {ID          uint   `json:"id"`Name        string `json:"name"`        // "學生", "教師", "管理員"Description string `json:"description"` // "普通學生用戶"
}2、定義權限
type Permission struct {ID     uint   `json:"id"`Name   string `json:"name"`   // "book:borrow", "user:create"Action string `json:"action"` // "借閱圖書", "創建用戶"
}

第二步:新方式如何檢查權限

// 現在的權限檢查
func DeleteBook(userID uint) error {if !permission.HasPermission(userID, "book:delete") {return errors.New("權限不足:您無法刪除圖書")}return deleteBook()
}新增角色?只需要配置數據,無需改代碼!
// 2. 權限檢查 - 如何工作的?
func (r *RoleService) HasPermission(roleID uint, permission string) bool {// 具體的權限驗證邏輯// 為什么這樣設計?
}

第三步:前后對比

// 傳統方式:硬編碼權限
if userType == "teacher" {// 教師相關操作
} else if userType == "student" {// 學生相關操作
}
// 問題:新增角色需要修改代碼// 你的方案:動態權限
if permission.HasRole(user.RoleID, "teacher") {// 教師相關操作
}
// 優勢:新增角色只需要配置數據

哈哈,這就像及了接口(interface)的設計方式。
讓人感覺賞心悅目。

二、中間件設計

雖然在引入RBAC后,確實能優化代碼,但是又遇到了新問題。

每個API都要手動檢查權限,代碼重復。

如下,這里是調用DeleteBook,需要permission認證

// 現在的權限檢查
func DeleteBook(userID uint) error {if !permission.HasPermission(userID, "book:delete") {return errors.New("權限不足:您無法刪除圖書")}return deleteBook()
}

如果咱們調用其他不同的函數,你都需要在每個函數上添加如下這段代碼:

 if !permission.HasPermission(userID, "book:delete") {return errors.New("權限不足:您無法刪除圖書")}

是不是特別麻煩(~ ̄▽ ̄)~

1、理論設計方式

咱們可以設計成如下,通過middleware中間件:

// 展示如何在路由中應用權限中間件
router.POST("/role", middleware.RequirePermission("role:create"), roleHandler.CreateRole)
router.GET("/role", middleware.RequirePermission("role:read"), roleHandler.GetRoles)

2、圖書館項目的設計


// - 1.  路徑跳過檢查 - 檢查當前請求路徑是否在跳過列表中,如果是則直接放行
// - 2. 獲取用戶信息 - 從請求頭 X-Userinfo 中獲取 Base64 編碼的用戶信息
// - 3.  解碼和反序列化 - 將 Base64 字符串解碼后,反序列化為 UserInfo 結構體
// - 4. 租戶ID處理 - 清理租戶ID格式(移除前導斜杠),從請求頭獲取目標租戶ID
// - 5. 權限驗證 - 檢查用戶是否有權限訪問請求的租戶(用戶的租戶列表中是否包含目標租戶)
// - 6. 設置上下文 - 驗證通過后,將用戶信息和租戶ID設置到 Gin 上下文中供后續使用
func Auth() gin.HandlerFunc {return AuthWithConfig(AuthConfig{})
}func AuthWithConfig(config AuthConfig) gin.HandlerFunc {notAuth := config.SkipPathsvar skip map[string]struct{}if len(notAuth) > 0 {skip = make(map[string]struct{})for _, path := range notAuth {skip[path] = struct{}{}}}return func(c *gin.Context) {if _, ok := skip[c.FullPath()]; ok {c.Next()return}userInfos := c.Request.Header.Get("X-Userinfo")if userInfos == "" {err := errs.NewUnauthorizedError("missing user information")response.BuildErrorResponse(err, c)c.Abort()return}userProfile := &userModel.UserInfo{}user, err := base64.StdEncoding.DecodeString(userInfos)if err != nil {logrus.Error("x-userinfo base64 decoding failed", err)err = errs.NewUnauthorizedError("invalid user info encoding")response.BuildErrorResponse(err, c)c.Abort()return}err = json.Unmarshal(user, &userProfile)if err != nil {logrus.Error("x-userinfo json unmarshal failed", err)err = errs.NewUnauthorizedError("invalid user info format")response.BuildErrorResponse(err, c)c.Abort()return}// Remove the leading slash from each tenant IDfor i, tenantId := range userProfile.TenantIds {if len(tenantId) > 0 && tenantId[0] == '/' {userProfile.TenantIds[i] = tenantId[1:]}}// Get tenantId from request headerrequestTenantId := c.GetHeader("tenantId")c.Set("tenantId", requestTenantId)if requestTenantId == "" && len(userProfile.TenantIds) > 0 {requestTenantId = userProfile.TenantIds[0]c.Set("tenantId", requestTenantId)}if requestTenantId == "" {logrus.Error("Missing tenantId in request header")err = errs.NewUnauthorizedError("missing tenantId in request header")response.BuildErrorResponse(err, c)c.Abort()return}// Check if the requested tenantId is in user's tenant listauthorized := falsefor _, tenantId := range userProfile.TenantIds {if tenantId == requestTenantId {authorized = truebreak}}if !authorized {logrus.Warnf("User attempted to access unauthorized tenant: %s", requestTenantId)err = errs.NewUnauthorizedError("unauthorized tenant access")response.BuildErrorResponse(err, c)c.Abort()return}// Authorized, continuec.Set("user", userProfile)c.Next()}
}

咱們在這里詳細解釋一下代碼:

a.跳過重復路徑
// 從配置中獲取,需要跳過的路徑	
// 通過map存儲實現O(1)查詢
notAuth := config.SkipPathsvar skip map[string]struct{}if len(notAuth) > 0 {skip = make(map[string]struct{})for _, path := range notAuth {skip[path] = struct{}{}}}
// 跳過
if _, ok := skip[c.FullPath()]; ok {c.Next()return
}
b.獲取JWT憑證
// 1. 獲取網關傳遞的用戶信息       userInfos := c.Request.Header.Get("X-Userinfo")....// 2. Base64解碼
userProfile := &userModel.UserInfo{}
user, err := base64.StdEncoding.DecodeString(userInfos)....// 3. JSON反序列化為用戶對象
err = json.Unmarshal(user, &userProfile)....// 4. 租戶權限驗證....

認證的思路如下:

客戶端 → 網關/認證服務 → 業務服務↓JWT驗證/登錄↓生成用戶信息↓Base64編碼后放入Header↓轉發到后端服務

這里的采用的是第三方驗證身份,并且采用Keycloak解決問題

Keycloak 是一個開源的身份和訪問管理(IAM)解決方案

可以拓展一下(AI):

1.單點登錄(SSO)

- 用戶只需登錄一次,即可訪問多個應用系統
- 支持SAML 2.0、OpenID Connect、OAuth 2.0等標準協議
2.身份認證

- 用戶名密碼認證
- 多因素認證(MFA)
- 社交登錄(Google、Facebook、GitHub等)
- LDAP/Active Directory集成
3.授權管理

- 基于角色的訪問控制(RBAC)
- 細粒度權限控制
- 資源和策略管理
4.用戶管理

- 用戶注冊、密碼重置
- 用戶組織和角色分配
- 用戶會話管理

圖書館項目生成用于驗證的JWT的方式

本項目JWT令牌的生成方式
通過對項目代碼的深入分析,我發現本項目的JWT令牌生成采用了以下架構:JWT令牌生成流程
1. Keycloak作為JWT令牌簽發中心- 項目使用 `keycloak.go` 中的 `GetAdminToken` 方法
- 通過調用 k.client.LoginAdmin() 向Keycloak服務器請求JWT令牌
- 使用配置文件中的管理員賬戶(AdminUser/AdminPass)進行認證
2. JWT令牌的具體生成過程```
token,?err?:=?k.client.LoginAdmin(k.ctx,?global.Config.Keycloak.
AdminUser,?global.Config.Keycloak.AdminPass,?"master")
```
3. 令牌使用場景- 管理操作 :在用戶創建、更新、刪除等管理操作中使用
- 權限驗證 :通過 `auth.go` 中間件驗證用戶身份
- API調用 :所有需要認證的API都通過JWT令牌進行權限控制

JWT是在創建角色的時候生成的,有興趣的可以了解一下:

// CreateUser 在 Keycloak 中創建新用戶
// 實現了完整的用戶創建流程,包括權限分配和事務回滾
func (k *KeycloakService) CreateUser(req *UserCreateRequest) (string, error) {// 步驟1: 獲取 Keycloak 管理員訪問令牌token, err := k.GetAdminToken()if err != nil {logrus.Error(err)return "", err}// 步驟2: 檢查用戶名(身份證號)是否已存在exists, err := k.CheckUsernameExists(req.IdNumber)if err != nil {logrus.Error(err)return "", err}if exists {logrus.Errorf("User with idNumber %s already exists", req.IdNumber)return "", errors.NewResourceAlreadyExistError("身份證重復!")}// 步驟3: 設置默認密碼(如果未提供)if len(req.Password) == 0 {req.Password = "Aa123456" // 建議:提取為配置項}// 步驟4: 構建 Keycloak 用戶對象keycloakUser := gocloak.User{Username: gocloak.StringP(req.IdNumber),    // 使用身份證作為用戶名Enabled:  gocloak.BoolP(true),              // 啟用用戶LastName: gocloak.StringP(req.Name),        // 設置姓名Credentials: &[]gocloak.CredentialRepresentation{{Type:      gocloak.StringP("password"),Value:     gocloak.StringP(req.Password),Temporary: gocloak.BoolP(false),        // 非臨時密碼},},}// 步驟5: 在 Keycloak 中創建用戶userID, err := k.client.CreateUser(k.ctx, token.AccessToken, k.realm, keycloakUser)if err != nil {logrus.Errorf("Failed to create user %s in realm %s: %v", req.Name, k.realm, err)return "", err}// 步驟6: 添加用戶到指定組(帶事務回滾)err = k.AddUserToGroup(userID, req.GroupName)if err != nil {logrus.Errorf("Failed to add user %s to group %s: %v", userID, req.GroupName, err)// 回滾:刪除已創建的用戶if rollbackErr := k.DeleteUser(userID); rollbackErr != nil {logrus.Errorf("Rollback failed: %v", rollbackErr)}return "", err}// 步驟7: 為用戶分配角色(帶事務回滾)err = k.AddRoleToUser(userID, req.Role)if err != nil {logrus.Errorf("Failed to add role %s to user %s: %v", req.Role, userID, err)// 回滾:刪除已創建的用戶if rollbackErr := k.DeleteUser(userID); rollbackErr != nil {logrus.Errorf("Rollback failed: %v", rollbackErr)}return "", err}return userID, nil
}

三、基于圖書館的權限樹設計

1、權限樹設計


// Permission 權限結構體
type Permission struct {Key      string       `json:"key"`Title    string       `json:"title"`Children []Permission `json:"children,omitempty"`
}// DefaultPermissions 默認權限樹結構
var DefaultPermissions = []Permission{{Key:   "home",Title: "首頁",},{Key:   "bookshelf",Title: "個人書架",},{Key:   "borrow-history",Title: "借閱記錄",},{Key:   "activity-center",Title: "活動中心",},{Key:   "message-center",Title: "消息中心",},{Key:   "system-manage",Title: "系統管理",Children: []Permission{{Key:   "book-manage",Title: "圖書管理",Children: []Permission{{Key: "book-entry", Title: "圖書錄入"},{Key: "book-list", Title: "圖書列表"},{Key: "book-recommend", Title: "圖書推薦"},{Key: "book-check", Title: "圖書清查"},},},{Key:   "borrow-manage",Title: "借閱管理",Children: []Permission{{Key: "book-borrow", Title: "圖書借閱"},{Key: "book-return", Title: "圖書歸還"},{Key: "flow-approve", Title: "漂流審批"},{Key: "reserve-list", Title: "候補列表"},{Key: "borrow-record", Title: "借閱記錄"},},},{Key:   "activity-manage",Title: "活動管理",Children: []Permission{{Key: "activity-create", Title: "活動創建"},{Key: "activity-approve", Title: "活動審批"},{Key: "activity-list", Title: "活動列表"},},},{Key:   "notice-manage",Title: "通知管理",Children: []Permission{{Key: "notice-create", Title: "通知創建"},{Key: "notice-list", Title: "通知列表"},},},{Key:   "system-setting",Title: "系統設置",Children: []Permission{{Key: "user-manage", Title: "讀者管理"},{Key: "role-manage", Title: "角色配置"},{Key: "system-configure", Title: "系統配置"},{Key: "grade-configure", Title: "年級配置"},{Key: "venue-configure", Title: "館場地配置"},{Key: "activity-configure", Title: "活動配置"},},},},},
}

2、權限層級映射:

大白話來說就是能快速找到子節點父節點之間的關系


// BuildPermissionParentMap 從DefaultPermissions構建權限層級關系映射
func BuildPermissionParentMap() map[string]string {parentMap := make(map[string]string)buildParentMapRecursive(DefaultPermissions, "", parentMap)return parentMap
}// buildParentMapRecursive 遞歸構建權限父子關系映射
// 能夠快速找到子權限的父權限
func buildParentMapRecursive(permissions []Permission, parentKey string, parentMap map[string]string) {for _, perm := range permissions {if parentKey != "" {parentMap[perm.Key] = parentKey}if len(perm.Children) > 0 {buildParentMapRecursive(perm.Children, perm.Key, parentMap)}}
}

四、圖書館項目

1、整體架構:

用戶(User) → 角色(Role) → 權限(Permission) → 資源(Resource)↓           ↓           ↓              ↓身份認證    角色分配    權限控制      資源訪問

2、核心組件解析

a、 用戶信息結構 (UserInfo)
type UserInfo struct {Name        string   // 用戶姓名Username    string   // 用戶名AccountId   string   // 賬戶IDRoles       []string // 用戶角色列表TenantIds   []string // 租戶ID列表(多租戶支持)// ... 其他字段
}
b、角色模型 (Role)
type Role struct {Name        string      // 角色名稱Description string      // 角色描述BorrowLimit int         // 借閱數量限制BorrowDays  int         // 借閱天數限制TenantId    string      // 租戶IDPermissions string      // 權限配置JSONStatus      enum.Status // 狀態
}
c、權限樹結構 (Permission)
type Permission struct {Key      string       // 權限標識Title    string       // 權限名稱Children []Permission // 子權限
}

3、權限設計層級

a、三級權限結構:

1.?一級權限 :模塊級別(如:系統管理)
2.二級權限 :功能級別(如:圖書管理)
3.三級權限 :操作級別(如:圖書錄入、圖書列表)

系統管理 (system-manage)
├── 圖書管理 (book-manage)
│   ├── 圖書錄入 (book-entry)
│   ├── 圖書列表 (book-list)
│   └── 圖書推薦 (book-recommend)
├── 借閱管理 (borrow-manage)
│   ├── 圖書借閱 (book-borrow)
│   └── 圖書歸還 (book-return)
└── 系統設置 (system-setting)├── 讀者管理 (user-manage)└── 角色配置 (role-manage)

五、前端如何進行權限控制

現實場景:

假設:
一個普通讀者(角色:student)
他的權限只有 ["home", "personal-bookshelf", "borrow-record", "activity-center", "message-center"] ,想要訪問"讀者管理"頁面。

完整權限控制

用戶登錄↓
后端返回用戶權限列表: ["home", "personal-bookshelf", "borrow-record", "activity-center", "message-center"]↓
前端存儲權限到 Pinia Store↓
菜單渲染時過濾權限↓
系統管理菜單不顯示(因為沒有任何系統管理權限)↓
用戶無法通過正常途徑訪問讀者管理頁面↓
即使通過直接URL訪問,組件內部也會進行權限檢查↓
最終被拒絕訪問或跳轉到403頁面
首先從后端獲取權限數據

當用戶登錄后,前端會調用 `user.ts` 中的 fetchAndSetStaffInfo() 方法:

async fetchAndSetStaffInfo() {try {this.isLoading = true;const response = await getCurrentStaff(); // 調用后端API獲取用戶信息if (response && (response as any).data && (response as any).code === 0) {const staffData = (response as any).data;// 設置用戶權限this.permissions = staffData.permissions || []; // 普通讀者只有基礎權限}} catch (error) {console.error('獲取用戶信息失敗:', error);}
}

結果:
普通讀者的 permissions 數組為: ["home", "personal-bookshelf", "borrow-record", "activity-center", "message-center"] , 不包含 "user-manage" 權限。

第一層防護:菜單不顯示

在 `index.vue` 中,菜單會根據權限進行過濾:

const filterRoute = (routeList: TRouter[], currentPermissions: string[]) => {// 檢查用戶是否有系統管理權限const hasSystemManagePermission = systemManagePermissions.some((permission) =>currentPermissions.includes(permission),);for (let i = routeList.length - 1; i >= 0; i--) {const route = routeList[i];const routeName = route.name as string;// 特殊處理系統管理菜單if (routeName === 'SystemManage') {if (!hasSystemManagePermission) {routeList.splice(i, 1); // 移除系統管理菜單}}}
};

結果:
由于普通讀者沒有任何系統管理相關權限(如 user-manage 、 role-manage 等),整個"系統管理"菜單都不會顯示在導航欄中。

第二層防護:系統管理子菜單過濾

即使用戶通過某種方式進入了系統管理頁面,在 `layout.vue` 中還有二級權限過濾:

// 菜單權限映射
const menuPermissionMap = {'user-manage': 'user-manage','role-manage': 'role-manage',// ... 其他權限映射
};// 根據權限過濾菜單組
const filteredMenuGroups = computed(() => {return menuGroups.map((group) => ({...group,items: group.items.filter((item) => {const requiredPermission = menuPermissionMap[item.key];return !requiredPermission || permissions.value.includes(requiredPermission);}),})).filter((group) => group.items.length > 0); // 過濾掉沒有可用菜單項的組
});

結果:
"讀者管理" 菜單項不會出現在系統管理的側邊欄中。

第三層防護:權限指令控制

在具體的頁面組件中,還可以使用權限指令 `permission.ts` 來控制元素顯示:

<!-- 在任何組件中使用權限指令 -->
<a-button v-permission="'user-manage'" type="primary">讀者管理
</a-button>

權限指令的實現:

const permission: Directive = {mounted(el: HTMLElement, binding) {const { value } = binding;const user = useUserStore();const { permissions } = user;if (value) {let hasPermission = false;if (typeof value === 'string') {hasPermission = permissions.includes(value); // 檢查是否有該權限}if (!hasPermission) {el.style.display = 'none'; // 沒有權限則隱藏元素}}},
};

結果: 任何帶有 v-permission="'user-manage'" 指令的元素都會被隱藏。

第四層防護:組合式函數權限檢查

在組件邏輯中,可以使用 `usePermission.ts` 進行權限檢查:

export function usePermission() {const user = useUserStore();// 檢查是否有指定權限const hasPermission = (permission: string): boolean => {return user.hasPermission(permission);};return {hasPermission,// ... 其他權限檢查方法};
}

在組件中使用:

<script setup>
import { usePermission } from '@/hooks/usePermission';const { hasPermission } = usePermission();// 檢查權限
if (!hasPermission('user-manage')) {// 沒有權限,執行相應邏輯router.push('/403'); // 跳轉到無權限頁面
}
</script>
第五層防護:直接URL訪問

如果用戶直接在瀏覽器地址欄輸入 /systemManage/user-manage :

1、路由存在 :路由配置中確實有這個路徑
2、組件加載 :UserManage 組件會被加載
3、權限檢查 :組件內部會進行權限檢查
4、訪問被拒絕 :如果沒有權限,會顯示無權限提示或跳轉到403頁面

收獲:

在學習權限控制的時候,由于我需要專門設計一套簡單的權限控制,我專門找來我們的前端。
想要深入了解一下,我后端傳遞數據到前端后,前端進行的權限控制流程

瀏覽器上的頁面是靜態頁面,當點擊發送url時,會被前端攔截(Vue Router)的工作原理;
然后經過代碼書寫的一系列操作之后,在傳遞到后端,
后端返回的具體數據,是先返回到前端,
經前端處理,才最終到顯示的頁面。


網站:

1、活動廣場 - 河南師范大學附屬中學圖書館


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

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

相關文章

Java應用快速部署Tomcat指南

將Java應用部署到Apache Tomcat服務器是開發Web應用過程中常見的任務。Tomcat是一個免費且開源的Servlet容器,它為Java應用提供了運行環境。本文將介紹如何準備你的Java應用,并將其部署到Tomcat服務器上。 Java 應用部署 tomcat 的根目錄結構 Tomcat中默認網站根目錄是$CAT…

Java 學習筆記(基礎篇2)

1. 分支結構① if 語句&#xff1a;(1) 雙分支&#xff1a;if (條件) {// 語句體1 } else {// 語句體2 }(2) 多分支if (條件1) {// 語句體1 } else if (條件2) {// 語句體2 } else {// 語句體N }② switch 語句&#xff1a;(1) 語法&#xff1a;如果都不是&#xff08;default&…

谷歌云代理商:用 AI 啟航,Gemini 重塑旅游酒店行業新體驗

本文由谷歌云谷歌地圖官方授權代理商、高級合作伙伴 CloudAce云一 整理發布。谷歌云谷歌地圖在中國授權代理商名單&#xff1a;Cloud Ace云一&#xff0c;全球20分公司&#xff0c;國內核心城市多個據點&#xff0c;谷歌云與谷歌地圖代理商、頂級合作伙伴&#xff08;Premier P…

springboot+vue實現通過poi完成excel

前端1、按鈕<el-buttontype"text"size"mini"click"handleExport">導出</el-button>2、方法//導出async handleExport() {if (!this.activityId) {this.$message.warning(活動ID不存在);return;}try {this.loading true;const res …

JMeter性能測試詳細版(適合0基礎小白學習--非常詳細)

01性能測試的概念 02性能測試的概念 基準測試 負載測試 穩定性測試 其他&#xff1a;并發測試、壓力測試、回歸測試等 壓力測試就是在系統強負載的情況下&#xff0c;是否會出現功能隱患問題&#xff0c;出現問題后是否可以盡快恢復 負載測試和壓力測試的區別: 1,核心目標不…

QT6(創建第一個QT項目)

編寫第一個QT項目 QT官網 安裝完QT后的界面 創建第一個項目 這里我們選擇第一個就好 下一步 下一步 選擇CMake&#xff0c;QMake是QT的CMAKE&#xff08;現在官方自己都不推薦了&#xff09; 下一步 選擇QWidget我們先創建一個最簡單的窗口程序 QMainWindow&#xff1a;主窗…

Golang指針操作

在 Go 語言&#xff08;Golang&#xff09;中&#xff0c;* 和 & 是與指針相關的兩個重要操作符。 理解它們對于掌握 Go 的內存管理和函數參數傳遞機制非常關鍵。 文章目錄一、& 操作符&#xff1a;取地址&#xff08;Address-of&#xff09;示例&#xff1a;二、* 操…

微服務從0到1

微服務從0到1實施步驟與注意事項一、核心實施步驟??需求分析與架構設計??明確業務邊界?&#xff1a;根據業務模塊&#xff08;如用戶管理、訂單系統&#xff09;劃分服務職責&#xff0c;避免服務職責重疊或耦合?。?定義接口契約?&#xff1a;通過 OpenAPI/Swagger 規范…

小程序排名優化:功能迭代如何助力排名攀升

小程序的功能不是一成不變的&#xff0c;持續的功能迭代不僅能滿足用戶不斷變化的需求&#xff0c;也是提升排名的重要途徑。平臺更傾向于推薦那些不斷更新、功能完善的小程序&#xff0c;因為它們能為用戶提供更優質的服務。合理規劃功能迭代方向和節奏&#xff0c;能讓小程序…

Unity TextMeshPro(二)優化

文章目錄前言一、字體打包優化二、ab打包冗余1、問題1、解決方法三、字體靜態優化四、擴展總結前言 優化TextMeshPro包體大小的方法記錄。 一、字體打包優化 游戲開發階段通常使用Fast打包方式&#xff0c;在正式項目發布的時候需要切換一下打包方式&#xff0c;重新將字體打…

C++ 之 【簡介 set、multiset、map、multimap 的使用】

目錄 1.序列式、關聯式容器 2.鍵值對 3.set 3.1set的簡介 3.2set的常用函數 4.multiset 5.map 5.1map的簡介 5.2map的常用函數 6.multimap 7.練習題 1.序列式、關聯式容器 vector、deque、list、forward_list、array等是CSTL中的序列式容器 其核心特性是 元素按插入…

數據結構——排序(升級篇:快速排序、堆排序、希爾排序、計數排序)

1. 快速排序&#xff08;Quick Sort&#xff09; 原理&#xff1a; 選擇一個基準值&#xff08;pivot&#xff09;將數組分成兩部分&#xff1a;小于 pivot 的放左邊&#xff0c;大于 pivot 的放右邊。然后遞歸處理 工作過程示例&#xff1a; 示例數組&#xff1a;[5, 3, 8, 4,…

C++:淺嘗gdb

hp window11 wsl ubuntu what is gdb&#xff1f; GNU調試器&#xff08;英語&#xff1a;GNU Debugger&#xff0c;縮寫&#xff1a;GDB&#xff09;&#xff0c;是GNU軟件系統中的標準調試器&#xff0c;此外GDB也是個具有移攜性的調試器&#xff0c;經過移攜需求的調修與…

Android輸入法一些常用的命令

Android開發過程可能會遇到Android輸入法異常的問題&#xff0c;可以通過如下命令來查看和修改系統的輸入法。方便調試。 獲取當下系統的所有輸入法 adb shell ime list獲取當前的可用輸入法 adb shell ime list -s獲取當前的輸入法 adb shell settings get secure default_inp…

Sklearn 機器學習 手寫數字識別 加載并查看數據

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 Sklearn 機器學習 手寫數字識別:加載并查看數據 在機器學習入門案例中,手寫數字識別…

衛星通信鏈路預算之七:上行載噪比計算

在前面的文章中我們介紹了衛星通信鏈路計算的基礎知識&#xff0c;包括&#xff1a; 信噪比分配&#xff1b; 帶寬和功帶平衡原則&#xff1b; EIRP和G/T&#xff1b; 輸入回退&#xff1b; 輸入飽和通量密度SFD&#xff1b; 輸出回退&#xff1b; 這次我們正式進入正題…

一文讀懂PDB格式

最近在做分子對接和分子模擬&#xff0c;涉及到了一些盲區&#xff0c;必去pdb文件是按照列位數儲存信息的&#xff0c;跟其他文件的空格或者制表符分割很不同&#xff0c;所以也可能出現一些錯誤&#xff0c;比如信息錯位&#xff0c;因此有必要了深入解下結構相關的格式pdb、…

進階:PGCE中級專家認證精要

PGCE中級認證的核心價值技術深度&#xff1a;掌控未來生態PostgreSQL不僅是傳統關系型數據庫的標桿&#xff0c;更是云原生、AI大模型訓練、物聯網平臺等前沿場景的核心支撐。通過PGCE認證&#xff0c;你將掌握&#xff1a;萬億級數據性能調優&#xff1a;從查詢優化器原理到執…

AI增強SEO關鍵詞表現

內容概要 隨著人工智能技術的不斷演進&#xff0c;其在搜索引擎優化領域展現出顯著潛力&#xff0c;尤其在關鍵詞表現優化方面發揮著核心作用。本文將從基礎概念入手&#xff0c;系統探討AI如何智能提升關鍵詞的搜索可見性、流量吸引力和轉化效率&#xff0c;從而驅動整體SEO策…

PG靶機 - PayDay

一、 初步偵察與服務探測 1.1 端口掃描與服務識別 首先&#xff0c;對目標主機 192.168.163.39 進行一次全面的端口掃描&#xff0c;以識別其上運行的各項服務。 sudo nmap 192.168.163.39 -p- --min-rate5000 -A圖 1: Nmap 掃描結果&#xff0c;顯示開放 80、445 和 995 等端口…