最近跟着学长再写河南师范大学附属中学图书馆的项目,学长交给了我一个任务,把本项目的权限管理给吃透,然后应用到下一个项目上。

我当然是偷着乐呐,因为读代码的时候,总是莫名给我一种公费旅游的感觉。
本来就想去了解图书管理这个项目的全貌。但一直腾不出时间。
现在正巧,我要写一个权限管理,正好可以拐回来细细品读图书管理系统的代码( ̄﹃ ̄)。

熟悉的配方,本项目使用的是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 等端口…