一文上手SpringSecurity【八】

news2024/9/30 13:25:38

RBAC(Role-Based Access Control),基于角色的访问控制。通过用户关联角色,角色关联权限,来间接的为用户赋予权限。

一、RBAC介绍

RBAC(Role-Based Access Control),即基于角色的访问控制模型。

1.1 基本概念

  • 角色(Role):
    • 角色是 RBAC 模型的核心概念之一。它是一组权限的集合,代表了特定的工作职责或功能。例如,可以有 “管理员”“普通用户”“财务人员” 等不同的角色。
    • 角色将用户与权限进行解耦,使得权限管理更加清晰和易于维护。
  • 用户(User)
    • 用户是系统的使用者。在 RBAC 模型中,用户通过被分配到不同的角色来获得相应的权限。
    • 一个用户可以被分配多个角色,从而拥有多个角色所对应的权限。
  • 权限(Permission)
    • 权限定义了对系统资源的具体操作许可。例如,可以是对某个文件的读取权限、对数据库表的修改权限等。
    • 权限通常与系统中的具体资源和操作相关联。

1.2 基本工作原理

  • 用户分配角色:

    • 系统管理员将不同的角色分配给用户。用户在登录系统后,根据其被分配的角色来确定拥有的权限。
    • 例如,新员工入职时,管理员可以根据其工作职责为其分配 “普通员工” 角色。
  • 角色关联权限:

    • 管理员将各种权限分配给不同的角色。每个角色拥有一组特定的权限集合。
    • 比如,“管理员” 角色可能拥有对系统所有资源的读写和管理权限,而 “普通用户” 角色可能只有对部分资源的读取权限。
  • 权限控制:

    • 当用户在系统中进行操作时,系统会根据用户的角色来判断其是否具有执行该操作的权限。
    • 如果用户具有相应的权限,则操作被允许;否则,操作被拒绝。

1.3 数据库表

1

create database if not exists `rj-security-db`;
use `rj-security-db`;

-- 用户表
DROP TABLE IF EXISTS `tb_sys_user`;
CREATE TABLE `tb_sys_user`  (
                                `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
                                `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
                                `nick_name` varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
                                `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
                                `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
                                PRIMARY KEY (`id`) USING BTREE,
                                UNIQUE INDEX `name`(`name` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户管理' ROW_FORMAT = DYNAMIC;
INSERT INTO `tb_sys_user` VALUES (4, 'zhangsan', '张三', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);
INSERT INTO `tb_sys_user` VALUES (5, 'jack', '杰克', '$2a$10$dCr8Skk7kLa2kNCms.23aeiYI2RS2vrdoSae6Jz3.0w.YCiu9lmT2', NULL);

-- 角色表
DROP TABLE IF EXISTS `tb_sys_role`;
CREATE TABLE `tb_sys_role`  (
                         `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
                         `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
                         `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
                         PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色管理' ROW_FORMAT = DYNAMIC;

INSERT INTO `tb_sys_role` VALUES (10, '超级管理员', NULL);
INSERT INTO `tb_sys_role` VALUES (11, '技术经理', NULL);
INSERT INTO `tb_sys_role` VALUES (12, '财务总监', NULL);
INSERT INTO `tb_sys_role` VALUES (13, '研发工程师', NULL);
INSERT INTO `tb_sys_role` VALUES (14, '人事专员', NULL);
INSERT INTO `tb_sys_role` VALUES (15, '产品经理', NULL);

-- 权限表【菜单表】
DROP TABLE IF EXISTS `tb_sys_menu`;
CREATE TABLE `tb_sys_menu`  (
                                `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
                                `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
                                `parent_id` bigint NULL DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
                                `url` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单URL,类型:1.普通页面(如用户管理, /sys/user)2.嵌套完整外部页面,以http(s)开头的链接 ',
                                `perms` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:sys:user:add,sys:user:edit)',
                                `type` int NULL DEFAULT NULL COMMENT '类型   0:目录   1:菜单   2:按钮',
                                `icon` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单图标',
                                `order_num` int NULL DEFAULT NULL COMMENT '排序',
                                `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
                                PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单管理' ROW_FORMAT = DYNAMIC;

INSERT INTO `tb_sys_menu` VALUES (64, '用户管理', 0, '/user/list', 'sys:user:list', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (65, '用户添加', 64, '/user/add', 'sys:user:add', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (66, '用户删除', 64, '/user/delete', 'sys:user:delete', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (67, '用户更新', 64, '/user/update', 'sys:user:update', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (68, '商品管理', 0, '/goods/list', 'sys:goods:list', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (69, '商品添加', 68, '/goods/add', 'sys:goods:add', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (70, '商品删除', 68, '/goods/delete', 'sys:goods:delete', NULL, NULL, NULL, NULL);
INSERT INTO `tb_sys_menu` VALUES (71, '商品更新', 68, '/goods/update', 'sys:goods:update', NULL, NULL, NULL, NULL);

-- 用户-角色-中间表
DROP TABLE IF EXISTS `tb_sys_user_role`;
CREATE TABLE `tb_sys_user_role`  (
                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
                                     `user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
                                     `role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
                                     `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
                                     PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色' ROW_FORMAT = DYNAMIC;

INSERT INTO `tb_sys_user_role` VALUES (5, 4, 10, '2025-05-18 11:16:27');
INSERT INTO `tb_sys_user_role` VALUES (6, 4, 11, '2025-05-18 11:16:35');
INSERT INTO `tb_sys_user_role` VALUES (7, 5, 15, '2025-05-18 11:16:37');
INSERT INTO `tb_sys_user_role` VALUES (8, 5, 13, '2024-05-18 11:16:39');
INSERT INTO `tb_sys_user_role` VALUES (9, 5, 14, '2024-05-18 11:16:41');

-- 角色-权限【菜单】中间表
DROP TABLE IF EXISTS `tb_sys_role_menu`;
CREATE TABLE `tb_sys_role_menu`  (
                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
                                     `role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
                                     `menu_id` bigint NULL DEFAULT NULL COMMENT '菜单ID',
                                     `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
                                     PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 632 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单' ROW_FORMAT = DYNAMIC;

INSERT INTO `tb_sys_role_menu` VALUES (632, 10, 64, '2023-05-18 11:17:50');
INSERT INTO `tb_sys_role_menu` VALUES (633, 10, 65, '2023-10-18 11:18:02');
INSERT INTO `tb_sys_role_menu` VALUES (634, 10, 66, '2024-09-18 11:18:18');
INSERT INTO `tb_sys_role_menu` VALUES (635, 10, 67, '2029-05-18 11:18:27');
INSERT INTO `tb_sys_role_menu` VALUES (636, 10, 68, '2024-05-23 11:18:37');
INSERT INTO `tb_sys_role_menu` VALUES (637, 10, 69, '2025-06-18 11:18:49');
INSERT INTO `tb_sys_role_menu` VALUES (638, 10, 70, '2033-09-18 11:19:01');
INSERT INTO `tb_sys_role_menu` VALUES (639, 11, 64, '2027-05-18 11:19:23');
INSERT INTO `tb_sys_role_menu` VALUES (640, 11, 65, '2036-06-18 11:19:39');
INSERT INTO `tb_sys_role_menu` VALUES (641, 15, 68, '2024-12-18 11:20:00');
INSERT INTO `tb_sys_role_menu` VALUES (642, 15, 71, '2022-05-18 11:20:14');
INSERT INTO `tb_sys_role_menu` VALUES (643, 14, 64, '2024-06-21 11:20:50');
INSERT INTO `tb_sys_role_menu` VALUES (644, 14, 65, '2028-05-18 11:21:10');

RBAC跟语言和框架没有啥关系,纯纯一种技术解决方案而已, 不同的安全框架都有不同的实现. 上述五张表,结合自己的实际需求,自行修改,这里我们只是为了做测试而已.

二、spring security 授权处理

2.1 授权的通俗解释

  • 张三是一个普通的个人, 这相当于普通用户
  • 村长、县长、市长这些相当于角色
  • 简单理解, 村长只能管理一个村子, 县长能管理一个县, 市长管理一个市…, 这是权限

张三可以成为一个【村长】, 可以【管理一个村子】, 从大的粒度考量, 因为张三是村长,所以能管理一个村子, 并不是它叫张三, 所以这里可以抽象出来【基于角色信息的权限控制】.

现在张三是一个村长了, 村长可以管理村里的纠纷, 管理村里的财政, 不管是管理纠纷还是村里的财政这些都是村长这个角色对应的一条一条的权限.

以上就是用户、角色、权限的通俗解释,当然它们用户-角色-权限【菜单】之间的关系都是多对多的.

2.2 授权处理

在接口UserDetails 当中

public interface UserDetails extends Serializable {
	// 权限集合
	Collection<? extends GrantedAuthority> getAuthorities();
}
public interface GrantedAuthority extends Serializable {
	String getAuthority();
}

通过以上的接口定义,可以看出来,所谓的权限就是一个字符串而已. 我们常用的一个实现类就是: SimpleGrantedAuthority, 可以查看一下它的类定义

public final class SimpleGrantedAuthority implements GrantedAuthority {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final String role;

	public SimpleGrantedAuthority(String role) {
		Assert.hasText(role, "A granted authority textual representation is required");
		this.role = role;
	}

	@Override
	public String getAuthority() {
		return this.role;
	}
 	// 略...
}

通过调用SimpleGrantedAuthority构建方法,可以将一个普通的字符串转换为SimpleGrantedAuthority对象被spring security所识别.
另外一个就是SimpleGrantedAuthority是没有无参构造的,这一点再执行它的对象的序列化、反序列化打时候要特别注意.

2.3 权限字符串

基于角色的授权规则包括 ROLE_ 作为前缀, 也可以通过如下的方式进行修改.

public final class GrantedAuthorityDefaults {

	private final String rolePrefix;

	public GrantedAuthorityDefaults(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}

	/**
	 * The default prefix used with role based authorization. Default is "ROLE_".
	 * @return the default role prefix
	 */
	public String getRolePrefix() {
		return this.rolePrefix;
	}
}
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("RJ_");
}

使用静态方法公开GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全性@Configuration类之前发布它

权限字符串分隔符一般以冒号者分隔, 譬如说: sys:user:create, 表示系统模块下的用户服务的创建权限.当然这并不是必须的,一切结合公司规则为准则.

2.4 添加权限字符串

在UserDetailsServiceImpl中,也就是UserDetailsService的实现类,此处我们之前传递的是个空集合,现在传递一些权限字符串.
修改代码如下所示:
2
修改TokenAuthenticationFilter即token的校验过滤器
3

2.5 开启授权处理

前置条件: 授权的前端是当前用户必须经过认证

spring security当中,会使用默认的FilterSecrityInterceptor来进行权限的校验。

  • 从FitlerSecurityInterceptor当中会从SecurityContextHolder获取其中的Authentication, 然后获取权限信息,当前用户是否人的了该资源的访问的权限.
  • 我们需要把当前用户的具体权限也存储到Authentication当点,然后设置我们的资源所需要的权限即可.
  • 使用时候,配置类配置开启注解:@EnableMethodSecurity
  • @EnableMethodSecurity, 开启基于方法的授权
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MethodSecuritySelector.class)
public @interface EnableMethodSecurity {
}

这里有两个特别重要的属性:

  • boolean prePostEnabled() default true;表示注解@PreAuthorize、 @PostAuthorize、 @PreFilter和 @PostFilter是否开启,默认就是【开启】状态
  • boolean securedEnabled() default false; 表示注解@secured是否开启, true,表示开启, false,关闭

2.6 编写测试接口

在HomeController当中, 添加如下测试代码

 // 测试权限字符串
 // 表示只有当前用户有: ROLE_user这个角色才测出访问这个接口
 @Secured("ROLE_user")
 @GetMapping("/api/pub/v1/list")
 public Result menu(){
     List<String> menuList = new ArrayList<>();
     menuList.add("商品管理");
     menuList.add("用户管理");
     menuList.add("查看商品");
     menuList.add("查看客户信息");
     return Result.success(0, "获取列表", menuList);
 }

@Secured(“ROLE_user”), 于角色的权限控制注解。可以指定一个或多个角色,只有具有这些角色的用户才能执行被注解的方法。可以指定一个或者多个角色的字符串,写法简单,但是功能也十分简单,以上表示,当前认证的用户如果拥有ROLE_user这个角色, 那么接口可以访问,如果当前认证的用户没有ROLE_user角色,那么无法访问接口,会抛出异常.

2.7 前端测试工程添加代码并验证

// 测试接口,主要验证权限字符串
export const getMneuListTest = () => $http({url: '/list', method: 'get'})
const m1 = async () => {
    const {code, msg, data} = await getMneuListTest()
    if(code === 0){
        content.value = data
    }else {
        ElMessage.error({
            type: 'error',
            message: msg || '操作异常',
            showClose: true
        })
    }
}

3
启动服务器测试
5
在这里插入图片描述
再添加一个测试接口

// 测试只有admin能看的数据
@Secured("ROLE_admin")
@GetMapping("/api/pub/v1/admin")
public Result admin(){
    return Result.success(0, "操作成功", "数据只有admin角色能看");
}

前端写接口,访问即可,可以看到,由于当前认证的用户并没有User_admin这个角色,返回访问接口会返回403.
6

2.8 授权异常自定义处理

如果用户没有相关访问去访问资源,默认会抛出AccessDeniedException异常, 当该异常发生的时候,我们可以通过AccessDeniedHandler接口,进行自定义处理.在spring security配置文件当中进行配置, 如下所示:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity.authorizeHttpRequests(authorize -> {
                try {
                    authorize.requestMatchers("/api/pub/v1/login").permitAll()
                            .requestMatchers("/static/**", "/resources/**").permitAll()
                            .anyRequest().authenticated();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }).csrf(AbstractHttpConfigurer::disable)
            // 将我们自己定义的过滤器添加到 UsernamePasswordAuthenticationFilter过滤器之前
            .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .cors(c -> {
            	// 处理spring security跨域配置
                c.configurationSource(apiConfigurationSource());
            }).exceptionHandling(exception -> exception.accessDeniedHandler(new AccessDeniedHandler() { // 处理未授权的情况, 访问资源的情况.
                @Override
                public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException {
                    response.setContentType("application/json;charset=utf-8");
                    Result result = Result.error(403, "您没有权限操作", "权限不足,请联系管理员!");
                    PrintWriter writer = response.getWriter();
                    writer.write(JSON.toJSONString(result));
                }
            }))
            .build();
}

再次测试
5
异常信息
90

2.9 验证权限字符串

编写测试接口

// 只有拥有sys:user:list权限才能访问
@PreAuthorize(value = "hasAuthority('sys:user:list')")
@GetMapping("/api/pub/v1/user")
public Result userList(){
  return Result.success(0, "操作成功", "数据只有sys:user:list角色能看");
}

// 只有拥有sys:user:create权限才能访问
@PreAuthorize(value = "hasAuthority('sys:user:create')")
@GetMapping("/api/pub/v1/create")
public Result userCreate(){
  return Result.success(0, "操作成功", "数据只有sys:user:create角色能看");
}

@PreAuthorize(value = “hasAuthority(‘sys:user:create’)”), 该注解是我们最最常用的注解,没有之一.

  • @PreAuthorize是 Spring Security 中用于方法级别的访问控制的注解。
    • 权限检查:
      • 在方法执行之前,@PreAuthorize会根据指定的表达式来判断当前用户是否具有执行该方法的权限。如果表达式的结果为false,则会抛出一个访问拒绝异常,阻止方法的执行。
      • 这个注解可以用于控制对特定业务方法的访问,确保只有具有相应权限的用户才能执行这些方法。
    • 表达式语言支持
      • @PreAuthorize支持使用 SpEL(Spring Expression Language)表达式来定义权限检查逻辑。SpEL 提供了丰富的语法和功能,可以进行各种复杂的权限判断。
      • 例如,可以使用表达式检查用户的角色、权限、属性等信息,以确定是否允许执行方法。 ’

基于角色的权限检查使用示例:

  • @PreAuthorize(“hasRole(‘ADMIN’)”):表示只有具有 “ADMIN” 角色的用户才能执行该方法。
  • @PreAuthorize(“hasAnyRole(‘ADMIN’, ‘MANAGER’)”):表示具有 “ADMIN” 或 “MANAGER” 角色的用户可以执行该方法。

基于权限的权限检查

  • @PreAuthorize(“hasPermission(#order, ‘WRITE’)”):假设存在一个自定义的权限检查方法hasPermission,这个注解表示只有当当前用户对给定的订单对象order具有 “WRITE” 权限时,才能执行该方法

基于用户属性的权限检查

  • @PreAuthorize(“authentication.principal.username.equals(‘admin’)”):表示只有用户名是 “admin” 的用户才能执行该方法。

56
123

2.10 从表中查询出权限

根据用户id,查询出对应的角色信息

@Mapper
public interface TbSysUserMapper extends BaseMapper<TbSysUser> {
  @Select("select * from tb_sys_role where id in (select role_id from tb_sys_user_role where user_id = #{id})")
  List<TbSysRole> findSysRoleByUserId(Long id);

  @Select("select * from tb_sys_menu where id in (select menu_id from tb_sys_role_menu where role_id in (select role_id from tb_sys_user_role where user_id = #{id}))")
  List<TbSysMenu> findSysMenuByUserId(Long id);
}

单元测试一下
zhangsan账号id = 4, 角色信息如下所示
123
zhangsan账号id = 4的权限列表
345
jack的账号id=5, 角色信息如下所示
78
jack的账号id=5, 权限信息
222
修改UserDetailsServiceImp获取权限列表
1234
TokenAuthenticationFilter获取权限列表
21
编写一个测试接口

// 只有拥有sys:goods:delete权限才能访问
@PreAuthorize(value = "hasAuthority('sys:goods:delete')")
@GetMapping("/api/pub/v1/delete")
public Result userDelete(){
   return Result.success(0, "操作成功", "商品删除成功: 数据只有sys:goods:delete权限能看");
}

登录zhangsan账号
121
99
登录jack账号
543
1234
验证完成

三、总结

  • RBAC模型五张表
  • 权限校验的三个注解
    • @EnableMethodSecurity(securedEnabled = true)
    • @PreAuthorize
    • @Secured
  • MySQL多表查询

注意事项:

  • 在token校验的过滤器当中,频繁的操作数据库,可能会带来性能瓶颈, 我们可以引入Redis来存储认证对象,避免频繁的操作数据库

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity实战案例全解析:RTS游戏的框选和阵型功能(3)生成范围检测框 +重置框选操作

前篇&#xff1a;Unity实战案例全解析&#xff1a;RTS游戏的框选和阵型功能&#xff08;2&#xff09; 生成选择框-CSDN博客 本案例来源于unity唐老狮&#xff0c;有兴趣的小伙伴可以去泰克在线观看该课程 我只是对重要功能进行分析和做出笔记分享&#xff0c;并未无师自通&…

UR学习记录

实践 示教器使用 外设使用 抓手&#xff0c;力传感器 开关、气动元件、电磁阀 示教器编写一个完整的抓取放置应用代码 使用示教器变量编写一个相对运动的应用代码 UR坐标表示计算 UR TCP/IP通讯 理论基础 齐次变换 python 矩阵计算&#xff0c;代码示例 import numpy as np…

Gromacs位置限制问题

Atom index n in position_restraints out of bounds A common problem is placing position restraint files for multiple molecules out of order.(一个常见的问题是无序放置多个分子的位置约束文件。)Recall that a position restraint itp (page 449) file containing a …

TDengine 签约国家电投旗下四大火力发电厂,助力汽轮机振动数据的有效管理

在火力发电厂中&#xff0c;汽轮机作为能量转换的核心设备&#xff0c;其稳定性直接关系到电力供应的可靠性和经济效益。因此&#xff0c;对汽轮机状态的监测与维护成为了发电厂日常经营中的重要工作。然而&#xff0c;传统的监测方式受到复杂运行环境和数据处理能力的限制&…

KA客户关系管理策略全解析

在当今商业竞争日益激烈的环境中&#xff0c;如何有效管理和维护关键客户关系成为企业制胜的关键。无论是初创企业还是跨国公司&#xff0c;都面临着同样的挑战&#xff0c;那就是如何通过精准的客户关系管理策略&#xff0c;不仅保留现有客户&#xff0c;还能不断拓展新的商业…

【Git原理与使用】Git初识基本操作

Git初识&&基本操作 1.初识Git2.Git安装3.Git基本操作3.1创建Git本地仓库3.2配置Git3.3认识工作区、暂存区、版本库3.4添加文件3.5修改文件3.6版本回退3.7撤销修改3.8删除文件 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f…

大数据-156 Apache Druid 案例实战 Scala Kafka 订单统计

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Linux 学习笔记(二):深入理解用户管理、运行级别与命令行操作

Linux 学习笔记&#xff08;二&#xff09;&#xff1a;深入理解用户管理、运行级别与命令行操作 前置学习内容&#xff1a;Linux学习&#xff08;一&#xff09; 1. 用户管理 1.1 用户密码管理 创建用户密码 使用 passwd 命令可以为指定用户设置密码&#xff1a; sudo pas…

AWS Network Firewall - IGW方式配置只应许白名单域名出入站

参考链接 https://repost.aws/zh-Hans/knowledge-center/network-firewall-configure-domain-ruleshttps://aws.amazon.com/cn/blogs/networking-and-content-delivery/deployment-models-for-aws-network-firewall/ 1. 创建防火墙 选择防火墙的归属子网&#xff08;选择公有…

Unity给物体添加网格(Wire)绘制的方法参考

先看效果&#xff1a; 再看代码&#xff1a; using System.Collections.Generic; using UnityEngine;public class WireMesh : MonoBehaviour {[SerializeField]Material material;void Start(){Mesh mesh OptimizeMesh(GetComponent<MeshFilter>().mesh);GameO…

这 5 个自动化运维场景,可能用 Python 更香?

许多运维工程师会使用 Python 脚本来自动化运维任务。Python 是一种流行的编程语言&#xff0c;具有丰富的第三方库和强大的自动化能力&#xff0c;适用于许多不同的领域。 这里插播一条粉丝福利&#xff0c;如果你正在学习Python或者有计划学习Python&#xff0c;想要突破自我…

需求6:如何写一个后端接口?

这两天一直在对之前做的工作做梳理总结&#xff0c;不过前两天我都是在总结一些bug的问题。尽管有些bug问题我还没写文章&#xff0c;但是&#xff0c;我今天不得不先停下对bug的总结了。因为在国庆之后&#xff0c;我需要自己开发一个IT资产管理的功能&#xff0c;这个功能需要…

IDEA:Properties in parent definition are prohibited

问题背景 如果你在POM.xml中使用了自定义版本&#xff0c;那么IDEA就没办法很动态检测&#xff08;其实可以做到的&#xff0c;不是吗&#xff09;&#xff0c;就会有一个Properties in parent definition are prohibited 的错误信息&#xff08;禁止使用父级定义中的属性&…

2024 八九月份国内外CTF 散装re 部分wp

CTFZone silentDRM 附件拖入ida 最后部分很明显是比较。mmap和munmap函数的块大小为0x23280&#xff0c;比较大&#xff0c;暂时不管它。下断点动调&#xff0c;跳过v6和v7的分析部分&#xff0c;因为它是根据每五个字节的第一个字节生成的。直接看call v7 做运算后分为…

【博弈强化学习】——UAV-BS 的联合功率分配和 3D 部署:基于博弈论的深度强化学习方法

【论文】&#xff1a;Joint Power Allocation and 3D Deployment for UAV-BSs: A Game Theory Based Deep Reinforcement Learning Approach 【引用】&#xff1a;Fu S, Feng X, Sultana A, et al. Joint power allocation and 3D deployment for UAV-BSs: A game theory based…

基于Node.js+Express+MySQL+VUE科研成果网站发布查看科研信息科研成果论文下载免费安装部署

目录 1.技术选型‌ ‌2.功能设计‌ ‌3.系统架构‌ ‌4.开发流程‌ 5.开发背景 6.开发目标 7.技术可行性 8.功能可行性 8.1功能图 8.2 界面设计 8.3 部分代码 构建一个基于Spring Boot、Java Web、J2EE、MySQL数据库以及Vue前后端分离的科研成果网站&#xff0c;可…

PACS系统的延伸:三维重建后处理

影像中心PACS系统源代码&#xff0c;C#语言三发的PACS源码&#xff0c;三甲以下医院都能满足。 PACS系统即医学影像存档与通信系统&#xff0c;是医疗领域中不可或缺的信息技术系统。它主要负责医院内医学影像的数字化存储、管理、传输和显示&#xff0c;极大地促进了医疗影像信…

在线PDF怎么转换成JPG图片?分享14种转换操作!

作为一名社畜&#xff0c;俺也经常要将PDF转换为图片格式&#xff01; 如何进行快速转换&#xff0c;包括电脑端、在线端和手机端&#xff0c;今天俺就测评了50款工具&#xff0c;给你得出了下面这些渠道&#xff0c;不少也是免费的&#xff0c;相信对你有帮助哦&#xff01; …

springboot基于Vue的电影在线预定与管理系统

目录 毕设制作流程功能和技术介绍系统实现截图开发核心技术介绍&#xff1a;使用说明开发步骤编译运行代码执行流程核心代码部分展示可行性分析软件测试详细视频演示源码获取 毕设制作流程 &#xff08;1&#xff09;与指导老师确定系统主要功能&#xff1b; &#xff08;2&am…

VS Code调整字体大小

##在工程目录底下.vscode/settings.json添加设置参数 {"editor.fontSize": 15,"window.zoomLevel": 1.5 }