3.SpringSecurity基于数据库的认证与授权
文章目录
- SpringSecurity基于数据库的认证与授权
- 一、自定义用户信息UserDetails
- 1.1 新建用户信息类UserDetails
- 1.2 UserDetailsService
- 二、基于数据库的认证
- 2.1 连接数据库
- 2.2 获取用户信息
- 2.2.1 获取用户实体类
- 2.2.2 Mapper
- 2.2.3 Service
- 2.3 认证
- 2.3.1 实现UserDetails接口
- 2.3.2 实现UserDetailsService接口
- 2.3.3 安全配置类
- 2.3.4 测试程序
- 2.3.4.1 控制器
- 3.3.4.1 访问
- 三、基于数据库的授权
- 3.1 数据库表
- 3.2 获取权限
- 3.2.1 权限类 SysMenu
- 3.2.2 SysMenuDao
- 3.2.3 SysMenuServiceImpl
- 3.2.4 修改 UserDetailsService
- 3.2.5 修改 UserDetails
- 3.3 测试
- 四、总结
SpringSecurity基于数据库的认证与授权
承接:2.SpringSecurity - 处理器简单说明-CSDN博客
我们之前学习的用户的信息都是配置在代码中,如下段代码所示
/*** 定义一个Bean,用户详情服务接口* <p>* 系统中默认是有这个UserDetailsService的,也就是默认的用户名(user)和默认密码(控制台生成的)* 如果在yaml文件中配置了用户名和密码,那在系统中的就是yaml文件中的信息* <p>* 我们自定义了之后,就会把系统中的UserDetailsService覆盖掉*/
@Configuration
public class MySecurityUserConfig {/*** 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetails(包括用户名、密码、用户所拥有的权限)* <p>* UserDetails存储的是用户的用户名、密码、去权限信息*/@Beanpublic UserDetailsService userDetailsService() {
// 用户细节信息,创建两个用户
// 此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456"))
// 配置用户角色.roles("student") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager.build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456"))
// 配置权限.authorities("teacher:query").build();UserDetails user3 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
// 配置权限.authorities("teacher:query","teacher:add","teacher:update","teacher:delete").build();// InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);userDetailsManager.createUser(user3);return userDetailsManager;}/*** 配置密码加密器* NoOpPasswordEncoder.getInstance() 此实例表示不加密* BCryptPasswordEncoder() 会加密*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
但是我们并不希望上面这样写,我们希望把用户的信息存入到数据库
一、自定义用户信息UserDetails
用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图
1.1 新建用户信息类UserDetails
//只有当"accountNonExpired"、“accountNonLocked”、“credentialsNonExpired”、"enabled"都为true时,账户才能使用
//之前我们创建的时候,直接User.builder()创建,之后InMemoryUserDetailsManager对象createUser
public class SecurityUser implements UserDetails {/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {//明文为123456return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";}/*** @return 用户名*/@Overridepublic String getUsername() {return "thomas";}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return true;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return true;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return true;}
}
1.2 UserDetailsService
/*** 当我们定义了此类后,系统默认的UserDetailsService不会起作用,下面UserServiceImpl会起作用*/
@Service
public class UserServiceImpl implements UserDetailsService {/*** 根据用户名获取用户详情UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SecurityUser securityUser= new SecurityUser();//我们自己定义的if(username==null || !username.equals(securityUser.getUsername())){
// SpringSecurity框架中自带的异常throw new UsernameNotFoundException("该用户不存在或用户名不正确");}
// 执行到这里,说明username是没有问题的
// 用户密码对不对,框架会帮我们进行判断return securityUser;}}
二、基于数据库的认证
我们观察到在1.1UserDetails中,我们把用户名和密码是写死的,但是这种情况下是不合理的,包括权限在内,我们都需要从数据库中取出来
将信息从数据库取出来后,可以将信息封装成一个UserDetails类
2.1 连接数据库
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.15</version>
</dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version>
</dependency>
spring:#数据源datasource:#德鲁伊连接池druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springsecurity?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis:#SQL映射文件的位置mapper-locations: classpath:mapper/**/*.xml# 指定实体类起别名,(实体类所在的包的包路径,那么包中的所有实体类别名就默认是类名首字母小写)type-aliases-package: com.zhangjingqi.entityconfiguration:#开启驼峰命名法map-underscore-to-camel-case: true#日志功能log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.2 获取用户信息
判断用户是否正确,我们可以从sys_user中取出用户相关信息,将用户的相关信息取出来后封装成一个UserDetails类
2.2.1 获取用户实体类
获取用户信息的实体类,这里不建议SysUser实体类实现UserDetails接口,因为会显得很乱
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysUser implements Serializable {private static final long serialVersionUID = -5352627792860514242L;private Integer userId;private String username;private String password;private String sex;private String address;private Integer enabled;private Integer accountNoExpired;private Integer credentialsNoExpired;private Integer accountNoLocked;}
2.2.2 Mapper
封装dao层,也就是Mapper层,从数据库中获取用户的信息
@Mapper
public interface SysUserDao {/*** 根据用户名访问用户信息* @param userName 用户名* @return 用户信息*/SysUser getByUserName(@Param("userName") String userName);}
<mapper namespace="com.zhangjingqi.dto.SysUserDao"><select id="getByUserName" resultType="com.zhangjingqi.entity.SysUser">select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_lockedfrom sys_user where username = #{userName};</select></mapper>
2.2.3 Service
@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {@Autowiredprivate SysUserDao sysUserDao;@Overridepublic SysUser getByUserName(String userName) {return sysUserDao.getByUserName(userName);}
}
2.3 认证
2.3.1 实现UserDetails接口
我们之前是写死的用户信息,但是现在是从数据库中进行获取的
@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}
这个地方我们之前是这么写的
2.3.2 实现UserDetailsService接口
@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
// 这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}// 2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);return securityUser;}
}
我们之前是这么写的
这篇文章会有介绍:1.SpringSecurity -快速入门、加密、基础授权-CSDN博客
2.3.3 安全配置类
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.authenticated();//需要验证http.formLogin().permitAll(); //SpringSecurity的表单认证}/*** @return 密码加密器*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}
2.3.4 测试程序
2.3.4.1 控制器
@RestController
@RequestMapping("/student")
public class StudentController {@GetMapping("/query")private String query(){return "query student";}@GetMapping("/add")private String add(){return "add student";}@GetMapping("/delete")private String delete(){return "delete student";}@GetMapping("/update")private String update(){return "export student";}
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {@GetMapping("/query")@PreAuthorize("hasAuthority('teacher:query')")//预授权public String queryInfo() {return "teacher query";}}
3.3.4.1 访问
用下面这个人的信息进行登录
访问一下localhost:8080/student/query,发现可以访问
再访问一下localhost:8080/teacher/query,发现是不能访问的,原因是thomas用户没有/teacher/query路径的权限
三、基于数据库的授权
3.1 数据库表
首先我们很明确的就是用户和角色的关系是多对多的
用户表
角色表
用户与角色关联表
权限表
权限与角色关联表
3.2 获取权限
3.2.1 权限类 SysMenu
@Data
public class SysMenu implements Serializable {private static final long serialVersionUID = 597868207552115176L;private Integer id;private Integer pid;private Integer type;private String name;private String code;
}
3.2.2 SysMenuDao
@Mapper
public interface SysMenuDao {List<String> queryPermissionByUserId(@Param("userId") Integer userId);
}
这个地方涉及到三张表,角色用户关联表sys_role_user、角色权限关联表sys_role_menu、权限表sys_menu
我们要实现通过用户获取对应的权限
<mapper namespace="com.zhangjingqi.dto.SysMenuDao"><select id="queryPermissionByUserId" resultType="java.lang.String">SELECT distinct sm.codeFROM sys_role_user sruinner join sys_role_menu srmon sru.rid = srm.ridinner join sys_menu sm on srm.mid = sm.idwhere sru.uid = #{userId}and sm.delete_flag = 0</select>
</mapper>
3.2.3 SysMenuServiceImpl
@Service
public class SysMenuServiceImpl implements SysMenuService {@Autowiredprivate SysMenuDao sysMenuDao;@Overridepublic List<String> queryPermissionByUserId(Integer userId) {return sysMenuDao.queryPermissionByUserId(userId);}
}
3.2.4 修改 UserDetailsService
@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysMenuService sysMenuService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
// 这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}// 2.获取该用户的权限List<String> permissionList = sysMenuService.queryPermissionByUserId(sysUser.getUserId());// List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(permission -> new SimpleGrantedAuthority(permission) ).collect(Collectors.toList());
// 将集合的泛型转换成SimpleGrantedAuthority (GrantedAuthority类的子类即可)List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());// 2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);securityUser.setSimpleGrantedAuthorities(simpleGrantedAuthorities);return securityUser;}
}
3.2.5 修改 UserDetails
@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;// 用户权限private List<SimpleGrantedAuthority> simpleGrantedAuthorities;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/*** 这个集合中对象的类型必须是GrantedAuthority类或其子类* @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return simpleGrantedAuthorities;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}
3.3 测试
使用Obama登录
查看obama权限
四、总结
认证与授权,需要我们实现UserDetails用户详情类和UserDetailsService类
相关文章:

3.SpringSecurity基于数据库的认证与授权
文章目录 SpringSecurity基于数据库的认证与授权一、自定义用户信息UserDetails1.1 新建用户信息类UserDetails1.2 UserDetailsService 二、基于数据库的认证2.1 连接数据库2.2 获取用户信息2.2.1 获取用户实体类2.2.2 Mapper2.2.3 Service 2.3 认证2.3.1 实现UserDetails接口2…...

【软件测试】自动化测试selenium
目录 一、什么是自动化测试 二、Selenium介绍 1、Selenium是什么 2、Selenium的原理 三、了解Selenium的常用API 1、webDriver API 1.1、元素定位 1.1.1、CSS选择器 1.1.2、Xpath元素定位 1.1.3、面试题 1.2、操作测试对象 1.3、添加等待 1.4、打印信息 1.5、浏…...

如何解决Google play开发者新注册账号,身份验证的地址证明问题?
我们知道,Google Play应用市场的发展速度惊人,但这两年,为了防止恶意软件的传播,谷歌要求开发者账号需要进行身份验证才能发布应用。 而今年越来越严格,不仅在提审时需要进行电话验证(链接)&am…...

Gin vs Beego: Golang的Web框架之争
前言 Golang作为一门高效且简洁的语言,已经在Web开发领域得到了广泛的应用。Gin和Beego是Golang中两个著名的Web框架,它们都提供了一系列强大的功能,帮助开发者构建高性能的Web应用。本文将对Gin和Beego进行全面的对比,帮助开发者…...

javascript IP地址正则表达式
/^(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2})\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)$/g.test(10.2.35.8) 注: 一定不要把表达式赋值给变量,直接…...

【Bash】记录一个长命令换行的BUG
假设现在我要在terminal执行如下命令跑模型: CUDA_VISIBLE_DEVICES6 python finetune.py -c configs/quantized/resnet32_cifar100_finetune.yml --model resnet32 --data-dir ~/datasets --apex-amp --initial-checkpoint /home/zwx/projects/hawq/resnet32.pth.t…...

【.net core】yisha框架imageupload组件多图上传修改
框架\wwwroot\lib\imageupload\1.0\js路径下imgup.js文件,参照旧版本代码和修改代码修改 (function ($) {"use strict";var deleteParent;var deleteDisplay none;var defaults {fileType: ["jpg", "png", "bmp", "…...

vscode markdown 使用技巧 -- 如何快速打出一个Tab 或多个空格
背景描述: 我在使用VSCode,这玩意很好用,但是,有一个缺点是,我想使用Tab来做一些对齐,但是我发现在VSCode中,无论是Tab还是多个空格,最终显示出来的都是一个空格 使用代码可以实现打…...

I/O 模型学习笔记【全面理解BIO/NIO/AIO】
文章目录 I/O 模型什么是 I/O 模型Java支持3种I/O模型BIO(Blocking I/O)NIO(Non-blocking I/O)AIO(Asynchronous I/O) BIO、NIO、AIO适用场景分析 java BIOJava BIO 基本介绍Java BIO 编程流程一个栗子实现…...

【Python学习笔记】字符编码
1. 字符串编码 Python3语言里面的字符串对象是unicode字符串,在内存中实际存储时,使用的是 UTF16 编码。但通常不会将UTF16编码的内容写到磁盘或者在网络进行传输, 因为utf16编码比较浪费空间。特别是如果文字信息基本都是英文符号的情况下&…...

华为昇腾NPU卡 大模型LLM ChatGLM2模型推理使用
参考:https://gitee.com/mindspore/mindformers/blob/dev/docs/model_cards/glm2.md#chatglm2-6b 1、安装环境: 昇腾NPU卡对应英伟达GPU卡,CANN对应CUDA底层; mindspore对应pytorch;mindformers对应transformers 本…...

Git 拉取远程更新报错
报错内容如下: cannot lock ref refs/remotes/origin/bugfix/bug: refs/remotes/origin/bugfix 已存在,无法创建 refs/remotes/origin/bugfix/bug 来自 gitlab.zhangyue-inc.com:dejian_ios/iReaderDejian! [新分支] bugfix/bug -> ori…...

腾讯云国际站服务器端口开放失败怎么办?
腾讯云服务器是腾讯公司推出的一种云服务,用户能够经过这种方式在互联网上进行数据存储和计算。然而,用户在运用腾讯云服务器时或许会遇到各种问题,其间端口敞开失利是一个常见问题。本文将具体介绍如何解决腾讯云服务器端口敞开失利的问题。…...

一句话解释什么是出口IP
出口 IP 是指从本地网络连接到公共互联网时所使用的 IP 地址。这个 IP 地址是由 Internet 服务提供商(ISP)分配给你的,它可以用来标识你的网络流量的来源。如果你使用的是 NAT(网络地址转换)技术,则在 NAT 设备内部会进行地址转换,使得多个设备可以共享同一个公共 IP 地…...

深入理解强化学习——强化学习的历史:试错学习
分类目录:《深入理解强化学习》总目录 让我们现在回到另一条通向现代强化学习领域的主线上,它的核心则是试错学习思想。我们在这里只对要点做概述,《深入理解强化学习》系列后面的文章会更详细地讨论这个主题。根据美国心理学家R.S.woodworth…...

分享一个用HTML、CSS和jQuery构建的漂亮的登录注册界面
作为一个前端开发人员,我们经常需要构建用户的登录和注册界面。一个漂亮、用户友好的登录注册界面对于提升用户体验和网站形象至关重要。以下我们使用HTML、CSS和jQuery来做一个漂亮的登录注册界面。 首先,我们需要创建一个html文档,定义登录…...

Java学习 习题 1.
一、 1.2. 3. 4. 5. 二、 1. 2. 3. 4. 5. 6. 7. 8....

第六节——Vue中的事件
一、定义事件 Vue 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同 使用修饰符(v-on:的缩写)事件名的方式 给dom添加事件后面跟方法名,方法名可以直接加括号如click"add()"里面进行传参。对应的事件处理函…...

设置GridView单选
/// <summary> /// 设置GridView单选 /// </summary> /// <param name"view"></param> /// <param name"selectCaption"></param> public static void SetGridViewSingleSel…...

[Python从零到壹] 七十二.图像识别及经典案例篇之OpenGL入门及绘制基本图形和3D图
十月太忙,还是写一篇吧!祝大家1024节日快乐O(∩_∩)O 欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希…...

论文-分布式-并发控制-Lamport逻辑时钟
目录 前言 逻辑时钟讲解 算法类比为面包店内取号 Lamport算法的时间戳原理 Lamport算法的5个原则 举例说明 算法实现 参考文献 前言 在并发系统中,同步与互斥是实现资源共享的关键Lamport面包店算法作为一种经典的解决并发问题的算法,它的实现原…...

长三角实现区块链电子医疗票据互联互通,蚂蚁链提供技术支持
10月25日,记者从浙江省财政厅发布的消息获悉,上海、浙江、江苏和安徽三省一市基于蚂蚁链实现区块链电子医疗票据互联互通,商业保险理赔作为首个规模化应用场景正式落地,蚂蚁保“安心赔”理赔服务率先接入。 今后,老百…...

Redis快速上手篇(三)(事务+Idea的连接和使用)
Redis事务 可以一次执行多个命令,本质是一组命令的集合。一个事务中的 所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。 单独的隔离的操作 官网说明 https://redis.io/docs/interact/transactions/ MULTI、EXEC、…...

Spring三级缓存解决循环依赖问题
文章目录 1. 三级缓存解决的问题场景2. 三级缓存的差异性3. 循环依赖时的处理流程4. 源码验证 1. 三级缓存解决的问题场景 循环依赖指的是在对象之间存在相互依赖关系,形成一个闭环,导致无法准确地完成对象的创建和初始化;当两个或多个对象彼…...

Unity 中使用波浪动画创建 UI 图像
如何使用 只需将此组件添加到画布中的空对象即可。强烈建议您将此对象放入其自己的画布/嵌套画布中,因为它会弄脏每一帧的画布并导致重新生成整个网格。 注意:不支持切片图像。 using System.Collections.Generic; using UnityEngine; using UnityEng…...

支付功能测试用例测试点?
支付功能测试用例测试点是指在测试支付功能时,需要关注和验证的各个方面。根据不同的支付场景和需求,支付功能测试用例测试点可能有所不同,但一般可以分为以下几类: 功能测试:主要检查支付功能是否符合设计和业务需求…...

HFS 快速搭建 http 服务器
HFS 是一个轻量级的HTTP 服务工具,3.0版本前进提供Windows平台安装包,3.0版本开提供Linux和macOS平台的安装包。 HFS更适合在局域网环境中搭建文件共享服务或者安装配置源服务器。 甲 非守护进程的方式运行 HFS (Ubuntu 22.04) 一…...

学生专用台灯怎么选?双十一专业学生护眼台灯推荐
台灯应该是很多家庭都会备上一盏的家用灯具,很多大人平时间看书、用电脑都会用上它,不过更多的可能还是给家中的小孩学习、阅读使用的。而且现在的孩子近视率如此之高,这让家长们不得不重视孩子的视力健康问题。那么孩子学习使用的台灯应该怎…...

Go 常用标准库之 fmt 介绍与基本使用
Go 常用标准库之 fmt 介绍与基本使用 文章目录 Go 常用标准库之 fmt 介绍与基本使用一、介绍二、向外输出2.1 Print 系列2.2 Fprint 系列2.3 Sprint 系列2.4 Errorf 系列 三、格式化占位符3.1 通用占位符3.2 布尔型3.3 整型3.4 浮点数与复数3.5 字符串和[]byte3.6 指针3.7 宽度…...

antv/x6 导出图片方法exportPNG
antv/x6 导出图片方法exportPNG antv/x6 版本如下: "antv/x6": "2.14.1","antv/x6-plugin-export": "2.1.6",在文件中导入 import { Graph, Shape, StringExt } from antv/x6 import { Export } from antv/x6-plugin-exp…...