2024年7月

Linux系统中如何管理用户和组?

Linux系统中用户和组的管理通常包括以下几个方面:

1、
创建用户和组
: 使用
useradd

groupadd
命令创建新用户和新组。

2、
修改用户和组信息
: 使用
usermod

groupmod
命令来修改用户和组的信息。

3、
删除用户和组
: 使用
userdel

groupdel
命令来删除用户和组。

4、
管理密码
: 使用
passwd
命令来管理用户密码。

这些基本命令和操作使得Linux系统管理员能够有效地管理系统的用户和权限。


Linux系统中常见的文件权限有哪些?如何修改它们?

1、Linux系统中的文件权限主要包括读(r)、写(w)和执行(x)权限。权限可以针对文件的所有者、所属组以及其他用户设置。

2、修改文件权限的常用命令是
chmod
。例如,
chmod u+x filename
命令将给文件所有者增加执行权限。

3、另外,
chown

chgrp
命令可用于更改文件的所有者和所属组。


Linux系统中的进程管理包括哪些基本操作?

Linux系统中的进程管理主要包括以下操作:

1、
查看进程
: 使用
ps

top
命令查看当前系统中的进程。

2、
控制进程
: 使用
kill

pkill
、和
killall
命令来终止进程。

3、
后台和前台控制
: 使用
bg
将进程移到后台,使用
fg
将进程带回前台。

4、
调整优先级
: 使用
nice

renice
命令调整进程的优先级。


Linux系统中的网络配置包括哪些基本操作?

Linux系统中的网络配置主要涉及以下操作:

1、
查看和配置网络接口
: 使用
ifconfig

ip
命令查看和配置网络接口。

2、
管理路由表
: 使用
route

ip route
命令查看和修改路由表。

3、
配置DNS
: 编辑
/etc/resolv.conf
文件来配置DNS服务器。

4、
测试网络连接
: 使用
ping

traceroute
命令测试网络连通性。


Linux中如何使用cron和crontab进行任务调度?

在Linux中,cron是一个基于时间的任务调度器,用于定期执行任务。

1、
crontab
命令用来创建、修改、删除或列出cron作业。

2、编辑
crontab文件
时,需要指定执行时间和相应的命令。

3、格式通常为
“分钟 小时 日期 月份 星期 命令”


Linux系统中的日志管理主要涉及哪些方面?

Linux系统的日志管理主要包括以下几个方面:

1、
日志文件存储
: 系统和应用日志通常存储在
/var/log
目录。

2、
日志级别
: 日志有不同的级别,如
INFO

ERROR

DEBUG
等。

3、
查看日志
: 使用如
cat

less

grep
等命令查看日志文件。

4、
日志轮换
: 使用
logrotate
等工具进行日志文件的轮换和管理。

5、
系统日志服务
: 如
syslogd

rsyslog
服务,负责日志的收集和处理。


Linux系统中的inode是什么,它有什么作用?

在Linux系统中,inode(索引节点)是文件系统的一个重要概念。每个文件或目录都有一个与之对应的inode,它包含了关于文件的元数据,但不包含文件名或文件数据。inode的作用包括:

1、
存储属性

inode
存储了文件的属性,如大小、权限、所有者、创建时间、最后访问和修改时间等。

2、
链接信息

inode
存储了文件数据块的位置信息,这是文件系统查找文件内容的关键。

3、
管理硬链接
: 通过
inode
,多个文件名(硬链接)可以指向同一个文件。

inode是文件系统中非常关键的部分,对于文件的管理和访问至关重要。


Linux中的Swap空间是什么,它是如何工作的?

1、
Swap空间
是Linux系统中的一种虚拟内存管理机制。它的主要作用是:
2、当物理内存不足时,系统可以将内存中不常用的数据页交换到
Swap空间
,从而为需要更多内存的进程释放空间。
3、
Swap空间
可以是一个专用的分区(
Swap分区
)或一个文件(
Swap文件
)。
4、
Swap
的工作原理是基于页面置换算法,当系统物理内存紧张时,它会将内存中的数据页移动到
Swap空间
,从而为新的数据页腾出空间。


Linux系统中如何查看和监控系统性能?

在Linux系统中,查看和监控系统性能的常用方法包括:

1、
top
命令: 实时显示系统中各个进程的资源占用情况。

2、
vmstat
命令: 报告虚拟内存统计信息。

3、
iostat
命令: 监控系统输入输出设备和CPU使用情况。

4、
netstat
命令: 显示网络连接、路由表、接口统计等网络信息。

5、
free
命令: 显示系统内存使用情况。
这些工具和命令能帮助系统管理员有效监控和诊断系统性能问题。


Linux中的权限控制列表(ACL)是什么,如何使用它们?

Linux中的权限控制列表(ACL)提供了比传统权限设置更详细的权限控制。使用ACL可以对单个用户或组设置特定的权限。基本的ACL操作包括:

1、使用
getfacl
命令查看文件或目录的ACL。

2、使用
setfacl
命令设置或修改文件或目录的ACL。

例如,
setfacl -m u:username:rw file
命令会给特定用户对文件的读写权限。ACL是高级文件权限设置的有效工具。


Linux系统中的“僵尸进程”是什么,如何处理它们?

在Linux系统中,僵尸进程(Zombie Process)是指已经完成执行但其父进程尚未读取其退出状态的进程。它们的特点和处理方法如下:

1、
特点
: 僵尸进程已经释放了大部分资源,但在进程表中保留一个条目,包含退出码等信息。

2、
处理
: 通常,父进程会通过调用
wait()

waitpid()
函数来读取子进程的退出状态,从而移除僵尸进程。如果父进程未正确处理,僵尸进程将保持在系统中。

3、
解决方法
: 可以尝试手动终止父进程,这通常会导致它的所有子进程被init进程接管并清理。如果不想终止父进程,可能需要修改并重启有问题的应用以防止产生更多僵尸进程。

僵尸进程通常不会导致严重问题,但如果数量过多,可能会消耗系统资源。


Linux中的I/O调度器有哪些类型,它们各自的特点是什么?

Linux中的I/O调度器负责管理对磁盘的读写请求。主要类型及其特点包括:

1、
CFQ(完全公平队列)
调度器: 为每个进程提供公平的磁盘时间。适用于多任务和通用系统。

2、
Deadline
调度器: 确保请求在一定时间内完成,优先处理即将到期的请求。适用于需要快速响应的系统。

3、
NOOP(无操作)
调度器: 是一个简单的FIFO队列,适用于高级存储系统(如SSD)。

4、
Anticipatory
调度器: 基于启发式方法,预测下一个即将到来的I/O请求,减少磁盘寻道时间。

根据系统的具体需求和硬件配置,选择合适的I/O调度器可以显著提高系统性能。


Linux系统中的RAID是什么,它有哪些常见类型?

RAID(冗余磁盘阵列的独立磁盘)是一种存储技术,它将多个磁盘组合成一个单一的逻辑单元,以提高性能和/或数据冗余。常见的RAID类型包括:

1、
RAID 0(条带化)
: 将数据分散存储在两个或更多的硬盘上,提高读写速度,但不提供冗余。

2、
RAID 1(镜像)
: 数据完全复制到两个或更多硬盘上,提供冗余,但容量只有单个硬盘的大小。

3、
RAID 5(带奇偶校验的条带化)
: 分布式奇偶校验,提供数据冗余和改善性能。

4、
RAID 10(镜像和条带化的组合)
: 结合了RAID 0和RAID 1的特点,提供冗余和性能的平衡。

选择合适的RAID级别取决于具体的性能和数据冗余需求。


Linux系统中的NFS是什么,它的主要用途是什么?

NFS(Network File System)是一种分布式文件系统协议,允许一个系统上的用户访问另一个系统上的文件,就像访问本地文件一样。其主要用途包括:
1、
共享存储
: 在网络中的多个机器间共享文件和目录。

2、
简化数据管理
: 中央管理数据,而非在每个客户端单独存储。

3、
提高可用性和灵活性
: 数据可以从网络的任何地方访问。

NFS广泛应用于企业和学术环境,提供了简单而有效的跨平台文件共享解决方案。


Linux中的SSH是什么,如何安全地使用SSH?

SSH(Secure Shell)是一种网络协议,用于安全地访问远程计算机。安全使用SSH的方法包括:

1、
使用密钥认证
: 相对于密码认证,使用SSH密钥对提供更高的安全性。

2、
禁用根登录
: 修改SSH配置,禁止远程根用户登录。

3、
更改默认端口
: 将SSH从默认的端口22更改为其他端口,减少自动化攻击的可能性。

4、
使用防火墙限制访问
: 仅允许可信的IP地址访问SSH端口。

5、
使用Fail2ban
: 自动阻止频繁尝试登录失败的IP地址。

遵循这些最佳实践可以显著提高通过SSH访问系统的安全性。

JWT(英文全名:JSON Web Token)是目前最流行的跨域身份验证解决方案之一,今天我们一起来揭开它神秘的面纱!

一、故事起源

说起 JWT,我们先来谈一谈基于传统
session
认证的方案以及瓶颈。

传统
session
交互流程,如下图:

当浏览器向服务器发送登录请求时,验证通过之后,会将用户信息存入
seesion
中,然后服务器会生成一个
sessionId
放入
cookie
中,随后返回给浏览器。

当浏览器再次发送请求时,会在请求头部的
cookie
中放入
sessionId
,将请求数据一并发送给服务器。

服务器就可以再次从
seesion
获取用户信息,整个流程完毕!

通常在服务端会设置
seesion
的时长,例如 30 分钟没有活动,会将已经存放的用户信息从
seesion
中移除。

session.setMaxInactiveInterval(30 * 60);//30分钟没活动,自动移除

同时,在服务端也可以通过
seesion
来判断当前用户是否已经登录,如果为空表示没有登录,直接跳转到登录页面;如果不为空,可以从
session
中获取用户信息即可进行后续操作。

在单体应用中,这样的交互方式,是没啥问题的。

但是,假如应用服务器的请求量变得很大,而单台服务器能支撑的请求量是有限的,这个时候就容易出现请求变慢或者
OOM

解决的办法,要么给单台服务器增加配置,要么增加新的服务器,通过负载均衡来满足业务的需求。

如果是给单台服务器增加配置,请求量继续变大,依然无法支撑业务处理。

显而易见,增加新的服务器,可以实现无限的水平扩展。

但是增加新的服务器之后,不同的服务器之间的
sessionId
是不一样的,可能在
A
服务器上已经登录成功了,能从服务器的
session
中获取用户信息,但是在
B
服务器上却查不到
session
信息,此时肯定无比的尴尬,只好退出来继续登录,结果
A
服务器中的
session
因为超时失效,登录之后又被强制退出来要求重新登录,想想都挺尴尬~~

面对这种情况,几位大佬于是合起来商议,想出了一个
token
方案。

将各个应用程序与内存数据库
redis
相连,对登录成功的用户信息进行一定的算法加密,生成的
ID
被称为
token
,将
token
还有用户的信息存入
redis
;等用户再次发起请求的时候,将
token
还有请求数据一并发送给服务器,服务端验证
token
是否存在
redis
中,如果存在,表示验证通过,如果不存在,告诉浏览器跳转到登录页面,流程结束。

token
方案保证了服务的无状态,所有的信息都是存在分布式缓存中。基于分布式存储,这样可以水平扩展来支持高并发。

当然,现在
springboot
还提供了
session
共享方案,类似
token
方案将
session
存入到
redis
中,在集群环境下实现一次登录之后,每个服务器都可以获取到用户信息。

二、JWT是什么

上文中,我们谈到的
session
还有
token
的方案,在集群环境下,他们都是靠第三方缓存数据库
redis
来实现数据的共享。

那有没有一种方案,不用缓存数据库
redis
来实现用户信息的共享,以达到一次登录,处处可见的效果呢?

答案肯定是有的,就是我们今天要介绍的
JWT

JWT
全称
JSON Web Token
,实现过程简单的说就是用户登录成功之后,将用户的信息进行加密,然后生成一个
token
返回给客户端,与传统的
session
交互没太大区别。

交互流程如下:

唯一的不同点就是

token
存放了用户的基本信息,更直观一点就是将原本放入
redis
中的用户数据,放入到
token
中去了!

这样一来,客户端、服务端都可以从
token
中获取用户的基本信息,既然客户端可以获取,
肯定是不能存放敏感信息的
,因为浏览器可以直接从
token
获取用户信息。

JWT具体长什么样呢?

JWT是由三段信息构成的,将这三段信息文本用
.
链接一起就构成了
JWT
字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 第一部分:我们称它为头部(header),用于存放token类型和加密协议,一般都是固定的;
  • 第二部分:我们称其为载荷(payload),用户数据就存放在里面;
  • 第三部分:是签证(signature),主要用于服务端的验证;
1、header

JWT的头部承载两部分信息:

  • 声明类型,这里是JWT;
  • 声明加密的算法,通常直接使用 HMAC SHA256;

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

使用
base64
加密,构成了第一部分。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2、playload

载荷就是存放有效信息的地方,这些有效信息包含三个部分:

  • 标准中注册的声明;
  • 公共的声明;
  • 私有的声明;

其中,标准中注册的声明 (建议但不强制使用)包括如下几个部分

  • iss: jwt签发者;
  • sub: jwt所面向的用户;
  • aud: 接收jwt的一方;
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间;
  • nbf: 定义在什么时间之前,该jwt都是不可用的;
  • iat: jwt的签发时间;
  • jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击;

公共的声明部分

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密。

私有的声明部分

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为
base64
是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行
base64
加密,得到
Jwt
的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的);
  • payload (base64后的);
  • secret (密钥);

这个部分需要
base64
加密后的
header

base64
加密后的
payload
使用
.
连接组成的字符串,然后通过
header
中声明的加密方式进行加盐
secret
组合加密,然后就构成了
jwt
的第三部分。

//javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, '密钥');

加密之后,得到
signature
签名信息。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用
.
连接成一个完整的字符串,就构成了最终的jwt:

//jwt最终格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

这个只是通过
javascript
实现的一个演示,
JWT
的签发和密钥的保存都是在服务端来完成。

secret
用来进行
jwt
的签发和
jwt
的验证,所以,在任何场景都不应该流露出去

三、实战

介绍了这么多,怎么实现呢?废话不多说,下面我们直接开撸!

  • 创建一个
    springboot
    项目,添加
    JWT
    依赖库
<!-- jwt支持 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
  • 然后,创建一个用户信息类,将会通过加密存放在
    token
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserToken implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private String userId;

    /**
     * 用户登录账户
     */
    private String userNo;

    /**
     * 用户中文名
     */
    private String userName;
}
  • 接着,创建一个
    JwtTokenUtil
    工具类,用于创建
    token
    、验证
    token
public class JwtTokenUtil {

    //定义token返回头部
    public static final String AUTH_HEADER_KEY = "Authorization";

    //token前缀
    public static final String TOKEN_PREFIX = "Bearer ";

    //签名密钥
    public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
    
    //有效期默认为 2hour
    public static final Long EXPIRATION_TIME = 1000L*60*60*2;


    /**
     * 创建TOKEN
     * @param content
     * @return
     */
    public static String createToken(String content){
        return TOKEN_PREFIX + JWT.create()
                .withSubject(content)
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .sign(Algorithm.HMAC512(KEY));
    }

    /**
     * 验证token
     * @param token
     */
    public static String verifyToken(String token) throws Exception {
        try {
            return JWT.require(Algorithm.HMAC512(KEY))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();
        } catch (TokenExpiredException e){
            throw new Exception("token已失效,请重新登录",e);
        } catch (JWTVerificationException e) {
            throw new Exception("token验证失败!",e);
        }
    }
}
  • 编写配置类,允许跨域,并且创建一个权限拦截器
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
       /**
     * 重写父类提供的跨域请求处理的接口
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 添加映射路径
        registry.addMapping("/**")
                // 放行哪些原始域
                .allowedOrigins("*")
                // 是否发送Cookie信息
                .allowCredentials(true)
                // 放行哪些原始域(请求方式)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
                // 放行哪些原始域(头部信息)
                .allowedHeaders("*")
                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                .exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin","Access-Control-Allow-Credentials");
    }

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加权限拦截器
        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");
    }
}
  • 使用
    AuthenticationInterceptor
    拦截器对接口参数进行验证
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从http请求头中取出token
        final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
        //如果不是映射到方法,直接通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }
        //如果是方法探测,直接通过
        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        //如果方法有JwtIgnore注解,直接通过
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method=handlerMethod.getMethod();
        if (method.isAnnotationPresent(JwtIgnore.class)) {
            JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
            if(jwtIgnore.value()){
                return true;
            }
        }
        LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");
        //验证,并获取token内部信息
        String userToken = JwtTokenUtil.verifyToken(token);
        
        //将token放入本地缓存
        WebContextUtil.setUserToken(userToken);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //方法结束后,移除缓存的token
        WebContextUtil.removeUserToken();
    }
}
  • 最后,在
    controller
    层用户登录之后,创建一个
    token
    ,存放在头部即可
/**
 * 登录
 * @param userDto
 * @return
 */
@JwtIgnore
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
    //...参数合法性验证

    //从数据库获取用户信息
    User dbUser = userService.selectByUserNo(userDto.getUserNo);

    //....用户、密码验证

    //创建token,并将token放在响应头
    UserToken userToken = new UserToken();
    BeanUtils.copyProperties(dbUser,userToken);

    String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));
    response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);


    //定义返回结果
    UserVo result = new UserVo();
    BeanUtils.copyProperties(dbUser,result);
    return result;
}

到这里基本就完成了!

其中
AuthenticationInterceptor
中用到的
JwtIgnore
是一个注解,用于不需要验证
token
的方法上,例如验证码的获取等等。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtIgnore {

    boolean value() default true;
}


WebContextUtil
是一个线程缓存工具类,其他接口通过这个方法即可从
token
中获取用户信息。

public class WebContextUtil {

    //本地线程缓存token
    private static ThreadLocal<String> local = new ThreadLocal<>();

    /**
     * 设置token信息
     * @param content
     */
    public static void setUserToken(String content){
        removeUserToken();
        local.set(content);
    }

    /**
     * 获取token信息
     * @return
     */
    public static UserToken getUserToken(){
        if(local.get() != null){
            UserToken userToken = JSONObject.parseObject(local.get() , UserToken.class);
            return userToken;
        }
        return null;
    }

    /**
     * 移除token信息
     * @return
     */
    public static void removeUserToken(){
        if(local.get() != null){
            local.remove();
        }
    }
}

最后,启动项目,我们来用
postman
测试一下,看看头部返回结果。

我们把返回的信息提取处理,使用浏览器的
base64
对前两个部分进行解密。

  • 第一部分,也就是header,结果如下:

  • 第二部分,也就是playload,结果如下:

可以很清晰的看到,头部、载荷的信息都可以通过
base64
解密出来。

所以,一定别在
token
中存放敏感信息

当我们需要请求其它服务接口时,只需要在请求头部
headers
中加入
Authorization
参数即可。

当权限拦截器验证通过之后,在接口方法中只需要通过
WebContextUtil
工具类就可以获取用户信息。

//获取用户token信息
UserToken userToken = WebContextUtil.getUserToken();

四、总结

JWT
相比
session
方案,因为
json
的通用性,所以
JWT
是可以进行跨语言支持的,像
JAVA

JavaScript

PHP
等很多语言都可以使用,而
session
方案只针对
JAVA

因为有了
payload
部分,所以
JWT
可以存储一些其他业务逻辑所必要的非敏感信息。

同时,保护好服务端
secret
私钥非常重要,因为私钥可以对数据进行验证、解密。如果可以,请使用
https
协议!

项目源代码地址如下!

https://gitee.com/pzblogs/spring-boot-example-demo

五、参考

1、
简书 - 什么是 JWT -- JSON WEB TOKEN

2、
博客园 - 基于session和token的身份认证方案

随着人工智能技术的日新月异,2024年的测试管理工具将迎来全新的发展机遇。AI赋能将助力测试管理工具实现前所未有的智能化升级,为软件研发团队带来革命性的变革。

一、什么是AI?

人工智能(AI)是一种能够模仿人类智能行为的技术。它通过模拟人类大脑的功能来解决复杂问题,具有学习、推理、感知、预测等能力。AI 技术主要包括以下几个方面:

  1. 机器学习(Machine Learning):通过算法和统计模型,让计算机系统从数据中学习并做出预测。包括监督学习、无监督学习、强化学习等技术。
  2. 深度学习(Deep Learning):是机器学习的一种应用,通过多层神经网络模拟人脑的工作方式来识别模式、分类数据。在计算机视觉、自然语言处理等领域应用广泛。
  3. 自然语言处理(Natural Language Processing):使计算机能够理解和生成人类语言,如语音识别、对话系统、机器翻译等。
  4. 计算机视觉(Computer Vision):使计算机能够识别和处理数字图像和视频,如图像分类、目标检测、图像生成等。
  5. 规划和决策(Planning and Decision Making):通过分析大量数据,为复杂决策问题提供建议和支持。

二、AI如何与测试管理工具相结合?

首当其冲,借助先进的AI算法,如机器学习与深度学习,可显著提升测试管理工具的自动化程度。未来,该工具将能自动生成定制化的测试用例,并依据历史数据精确预测测试结果,从而大大减轻研发团队繁琐的测试负担。AI驱动的智能分析功能,亦有助于测试管理工具深入挖掘潜在的缺陷与风险,实现全面智能化的质量控制。

AI赋能的测试管理工具将具备更强的协同交互能力。借助自然语言处理与知识图谱技术,该工具将拥有更为人性化的对话交互界面,研发人员可通过自然语言进行交流,从而提高使用效率。同时,AI驱动的测试数据管理与知识库建设,也将有助于加强研发团队的协作,构建更为高效流畅的软件交付体系。

AI驱动的测试管理工具将实现更为精准的预测分析与决策支持。借助大量历史数据的深度分析,该工具将能为研发团队提供更精确的项目进度预测、风险预警,乃至智能的决策建议,助力企业实现软件交付全流程的可控与高效。

总体而言,在2024年,以人工智能驱动的测试管理工具将全面升级至自动化、协同交互以及预测分析等领域,从而成为企业数字化转型的重要保障。测试管理工具的智能化进程不仅能够显著提高软件研发效率,还将促进整个软件交付生态系统的全面改革,引领软件质量管理步入崭新的智能时代。

三、目前有哪些测试管理工具已经运用上了AI技术?

1.禅道测试项目管理软件
禅道近年来推出了基于 AI 技术的一些功能,例如:

  • 缺陷预测分析:利用机器学习模型,根据历史缺陷数据预测未来可能出现的缺陷,帮助测试人员提前识别风险。

  • 智能分配任务:结合项目人员的能力和工作负载,自动为缺陷分配合适的处理人员。

  • 自动生成测试用例:结合产品需求和历史测试用例,利用自然语言处理技术自动生成新的测试用例。

    2.TestRail
    这款广受欢迎的测试管理工具也开始集成 AI 技术,主要体现在:

  • 测试用例优化:根据执行记录自动优化测试用例,删除冗余或低价值的用例。

  • 缺陷分类:利用机器学习技术自动对新发现的缺陷进行分类,提高缺陷管理效率。

  • 智能报告生成:通过分析测试数据,自动生成定制化的测试报告。

3.qTest
这款企业级测试管理工具也集成了一些 AI 功能,如:

  • 智能缺陷分析:根据缺陷描述、严重程度等特征,自动识别缺陷类型并预测修复难度。
  • 测试用例推荐:基于产品需求和历史用例,为新项目自动生成相关的测试用例。
  • 测试执行优化:利用机器学习预测哪些测试用例最有可能发现缺陷,优先执行。

4.Appium AI
这是一款基于Appium自动化框架的移动测试平台,集成了机器学习算法,可以自动生成测试用例、智能识别UI元素,并根据历史数据预测测试结果。

5.Testim.io
这款测试自动化工具使用计算机视觉和自然语言处理技术,能够自动生成测试脚本并进行持续维护,大幅降低了测试用例编写和维护的工作量。

6.Azure Test Plans
微软的这款测试管理服务集成了机器学习功能,能够根据项目历史数据自动预测测试进度和缺陷趋势,为测试计划提供智能建议。

7.Tricentis Neosys
这是一款AI驱动的企业级测试管理平台,整合了风险分析、测试自动化、测试数据管理等功能,利用AI算法优化测试过程。

8.Mabl
这是一款"低代码"的测试自动化工具,利用机器学习技术自动生成测试脚本并持续维护更新,大幅提高了测试效率。

总结:
可以看到,AI技术正在逐步渗透到各类测试管理工具中,通过自动化、智能分析等功能,显著提升了测试过程的效率和质量。未来随着AI技术的不断成熟,测试管理工具的智能化水平必将进一步提升。

前言

近年来,中国在信息技术领域持续追求自主创新和供应链安全,伴随信创上升为国家战略,一些行业也开始明确要求文件导出的格式必须为 OFD 格式。OFD 格式目前在政府、金融、税务、教育、医疗等需要文件开放、共享和长期保存的行业中广泛应用。这种趋势在未来几年内将进一步增强。

相较于 PDF,OFD 在以下方面展现了明显的优势,具体体现在:

  • 开放性

PDF 是 Adobe 公司开发的专有格式,虽然也被广泛应用,但受制于 Adobe 公司的软件和许可。OFD 则是基于国际开放标准制定的开放式文档格式,任何人或组织都可以自由使用和开发相关软件。

  • 功能特性

PDF 主要用于文档展示和打印,功能较为单一。OFD 在文档展示、打印、编辑等方面都有更强大的功能支持。

  • 文件大小

PDF 文件通常会略大于 OFD 文件,因为 PDF 包含更多的元数据和功能,OFD 文件在保持良好的视觉效果的前提下,通常体积更小。

  • 兼容性

PDF 虽然跨平台性强,但在不同软件和系统中的表现可能会有差异,OFD 则具有更好的跨平台一致性。

  • 安全性

PDF 文件可能包含隐藏的功能和潜在的安全隐患,OFD 则更加透明,安全性更高。

如何将 PDF 转化为 OFD?

既然导出 OFD 格式如此重要,然而目前市面上的报表工具,前端导出时通常只支持 PDF 格式。那么在这种情况下,如何实现一键在前端将报表导出为 OFD 格式呢?今天,小编将以葡萄城的嵌入式 BI 工具——
Wyn 商业智能
作为例子,向大家介绍如何将 PDF 转换为 OFD 格式。

首先小编先带大家一起了解下
OFD文件解析的底层原理:

OFD 文件底层结构:

OFD 文件采用XML作为其基本结构,这意味着文件内容是以文本形式存储的,便于编辑和搜索。OFD 文件主要由以下几个部分组成:

  1. 文档头(Document Header):包含文档的基本信息,如标题、作者、创建日期等。
  2. 文档体(Document Body):包含文档的实际内容,如文字、图片、表格等。
  3. 资源文件(Resource Files):包括文档中使用到的图片、字体、样式等资源。
  4. 元数据(Metadata):提供有关文档内容的额外信息,如关键词、摘要等。

PDF 转换为 OFD 的流程:

首先,通过使用 Wyn 报表工具,可以轻松设计出符合需求的报表样式。这些报表样式可以包含各种元素,例如表格、图表、图片、文本、超链接等等。设计完成后,可以直接在 Web 端进行预览,同时还支持将报表导出为PDF 格式。这样的设计流程和功能使得报表的创建和预览变得更加便捷和直观。

前端支持 PDF 导出只是第一步,为了实现从 PDF 转换为 OFD,还需要前端提供导出 PDF 的 API 接口,以便前端能够获取到 PDF 文件的流数据。幸运的是,Wyn 提供了丰富的 API 接口,使得前端可以通过接口直接实现PDF 的导出功能。这样的设计使得 PDF 转换为 OFD 变得更加便捷和可行。

PDF 转 OFD 的实现步骤

前端导出PDF文件的API接口:

http://localhost:51980/api/v2/reporting/export-templates/{exportTemplateId}

后端进行PDF文件解析的方法

  1. 继承 PDFGraphicsStreamEngine 类,便于分析 PDF 数据图层和资源归类
public class OFDPageDrawer extends PDFGraphicsStreamEngine {

}
  1. 重写构造方法,分析 PDF 每页的资源,并初始化 OFD 生成器
/**
构造器,调用super(page),这个操作的目的是将page资源准备好,并且添加对应的操作符,
当下一次调用processPage或者processPageContentStream时执行对应的操作符对应的操作
@param idx
@param page
@param ofdCreator
@param scale
@throws IOException
*/
protected OFDPageDrawer(int idx, PDPage page, OFDCreator ofdCreator, float scale) 
throws IOException {
    super(page);
    this.page = page;
    this.ofdCreator = ofdCreator;
    ctLayer = this.ofdCreator.createLayer();
    this.scale = scale;
}
  1. 重写 drawImage 方法收集整理 PDF 中分析出来的图片资源
/**
作用:将 PDF 图像对象转换为 OFD 格式进行绘制。此方法包括:
*
将图像写入字节流并保存。
根据当前变换矩阵计算图像在页面上的位置和大小。
创建 OFD 图像对象并设置其相关属性,然后添加到当前层中。
*
@param pdImage
@throws IOException
*/
@Override
public void drawImage(PDImage pdImage) throws IOException {
    ByteArrayOutputStream bosImage = new ByteArrayOutputStream();
    String suffix = "png";
    ImageIO.write(pdImage.getImage(), suffix, bosImage);
    String name = String.format("%s.%s", bcMD5(bosImage.toByteArray()), suffix);
    ofdCreator.putImage(name, bosImage.toByteArray(), suffix);

    // 根据当前变换矩阵计算图像在页面上的位置和大小,实际上就是将PDF中该图像的属性信息转换成OFD中的形式
    Matrix ctmNew = this.getGraphicsState().getCurrentTransformationMatrix();
    float imageXScale = ctmNew.getScalingFactorX();
    float imageYScale = ctmNew.getScalingFactorY();
    double x = ctmNew.getTranslateX() * scale;
    double y = (page.getCropBox().getHeight() - ctmNew.getTranslateY() - imageYScale) * scale;
    double w = imageXScale * scale;
    double h = imageYScale * scale;
    ImageObject imageObject = new ImageObject(ofdCreator.getNextRid());
    imageObject.setBoundary(x, y, w, h);
    imageObject.setResourceID(new ST_RefID(ST_ID.getInstance(ofdCreator.getImageMap().get(name))));
    imageObject.setCTM(ST_Array.getInstance(String.format("%.0f 0 0 %.0f 0 0", w, h)));
    setImageClip(imageObject, x, y, w, h);
    ctLayer.add(imageObject);
}
  1. 通过继承 PDFGraphicsStreamEngine 类分析得到的文字内容重绘
public void addPageContent(int idx, CT_Layer ctLayer, float width, float height) {
    PageDir pageDirInv = new PageDir();// 资源归类
    pageDirInv.setIndex(idx);
    org.ofdrw.core.basicStructure.pageObj.Page pageInv = new org.ofdrw.core.basicStructure.pageObj.Page();
    CT_PageArea areaInv = new CT_PageArea();// ofd可视区域(PDF的裁剪区)
    areaInv.setPhysicalBox(0, 0, width, height);
    pageInv.setArea(areaInv);
    Content contentInv = new Content();// 内容
    contentInv.addLayer(ctLayer);
    pageInv.setContent(contentInv);
    pageDirInv.setContent(pageInv);
    docDir.getPages().add(pageDirInv);
}
  1. 将收集到的资源进行打包生成 OFD 文件
/**
打包OFD文件包二进制数据
*
@param virtualFileMap
@return
@throws IOException
*/
public static void zip(Map<String, byte[]> virtualFileMap,OutputStream output) throws IOException {
    ZipArchiveOutputStream zaos = new ZipArchiveOutputStream(output);

    for (Map.Entry<String, byte[]> entry : virtualFileMap.entrySet()) {
        zaos.putArchiveEntry(new ZipArchiveEntry(entry.getKey()));
        zaos.write(entry.getValue());
        zaos.closeArchiveEntry();
    }

    zaos.finish();
}

最终效果展示:

完整代码的链接:

GcExcelTestArea.rar

总结

在当今时代,对于国产化的支持,OFD(Office Open XML for Developers)变得越来越重要。本文首先介绍了OFD 文件的底层结构,并阐述了 OFD 相对于 PDF 的优势。接着,介绍如何通过葡萄城的嵌入式 BI 工具——Wyn 商业智能,进行报表设计和导出 PDF 。同时,还展示了如何使用 Wyn 商业智能的 API 接口将 PDF 转换为 OFD,除此之外,在企业级复杂系统中,除了 OFD 之外,Wyn还同时支持Word、Excel、图片、Text、JSON等多种格式的导出。

通过本文的介绍,我们可以清楚地看到,将 PDF 转换为 OFD 不再是一个困扰。借助 Wyn 强大的功能和丰富的 API 接口支持,能够轻松高效地实现文档格式转换。这一解决方案为用户提供了便捷、灵活的操作方式,满足了行业对 OFD 格式的要求。

扩展链接:

创意展示:打造数据大屏的炫酷天气预报插件

聊一聊数字孪生与3D可视化

探秘移动端BI:发展历程与应用前景解析

〇、写在前面

本应用基于开源UI框架PyDracula进行开发,除去最基本的UI框架外,所有功能的前后端实现都由我个人开发完成,但也有部分UI(如开关控件和进度条)是参考其他大佬的分享。

这个应用是我的本科毕业设计,但因为个人能力不足,姑且只能使用Python+PySide6开发。

开发这个应用的启发是,曾经我作为深度学习的萌新,在初出训练模型时费了不少力气去学习,一个人摸索了很长时间,直到现在我可以熟练掌握模型的训练。回过头来看我曾经踩过的坑,我把自己在模型训练过程中经常使用的图像处理脚本工具集成在了这个应用中,并实现了一站式的模型选择可视化训练。

但我也知道,真正训练一个好模型远比这复杂的多,因此本应用可以当作新手朋友们的入门体验。

希望本应用能对大家有所帮助。

一、应用简介

本应用主要分为三大功能:

1. 单张图片预处理

本部分功能主要实现仅对一张图片的处理,该功能分为预处理工具和图像增强两部分。

1.1 预处理工具

(1) 更改后缀

在这部分功能中实现了对图片后缀的修改,可以支持jpg、jpeg、png和bmp四种常见数据集图像格式图片的后缀更改,用户通过点击相应的按钮,执行对应的功能。

  1. 修改为.jpg后缀功能:

    将图片统一转为 *.jpg 后缀。支持转换 *.JPG、*.png、*.PNG、*.jpeg、*.JPEG、*.bmp、*.BMP后缀格式。

  2. 修改为.jpeg后缀功能:

    将图片转为 *.jpeg 后缀。支持转换 *.jpg、*.JPG、*.png、*.PNG、*.JPEG、*.bmp、*.BMP后缀格式。

  3. 修改为.png后缀功能:

    将图片转为 *.png 后缀。支持转换 *.jpg、*.JPG、*.PNG、*.jpeg、*.JPEG、*.bmp、*.BMP后缀格式。

  4. 修改为.bmp后缀功能:

    将图片转为 *.bmp 后缀。支持转换 *.jpg、*.JPG、*.png、*.PNG、*.jpeg、*.JPEG、*.BMP后缀格式。

(2) 更改大小

该部分功能实现了直接将图片像素大小进行修改。插值方式说明如下:

插值方式 说明
INTER_LINEAR 双线性插值。
INTER_NEAREAST 最邻近插值。
INTER_CUBIC 三次样条插值。
INTER_AREA 区域插值。(使用像素面积关系进行重采样)
INTER_LANCZOS4 一种Lanczos插值方法(超过8×放大时效果最好)。
INTER_LINEAR_EXACT 位精确双线性插值。

(3) 图片方形化

该部分功能实现了将不规则矩形图片统一为方形图片。

该功能模块所支持的边框拓充方式有:

  1. BORDER_CONSTANT:用纯色背景(常数)填充,需指定拓充的RGB颜色。
  2. BORDER_REFLECT:用边界元素的镜面反射拓充。
  3. BORDER_REPLICATE:使用最边界的像素值代替。
  4. BORDER_WRAP:进行上下边缘调换的外包复制操作。

1.2 图像增强

注意:本部分所有功能可以
按顺序同时叠加使用

(1) 图像旋转

该功能可使图像进行任意角度旋转,并同时缩放处理。

(2) 水平翻转

(3) 垂直翻转

(4) 模糊

该功能可对图像进行模糊处理,支持四种滤波方式:

  1. 均值滤波
  2. 方框滤波
  3. 高斯滤波:滤波核必须为奇数。
  4. 中值滤波:滤波核必须为大于1的奇数。

(5) 噪声

该功能可对图像增加噪声,支持两种噪声方式:

  1. 高斯噪声
  2. 椒盐噪声

这两种方式可以单独使用,也可以同时使用。

(6) 亮度

(7) 对比度

(8) 所有功能同时启用演示

2. 数据集预处理

本部分功能主要实现对整个数据集图片的处理,该功能分为预处理工具和图像增强两部分。

2.1 预处理工具

(1) 统一后缀

该功能和单张图片预处理的
更改后缀
功能类似,故不再过多赘述。

(2) 统一命名

在该模块下,用户可以对数据集进行统一命名,支持的统一命名格式为:

  1. 分类_i
  2. 分类i
  3. 分类-i

(3) 统一大小

该功能和单张图片预处理的
更改大小
功能类似,故不再过多赘述。

(4) 图片方形化

该功能和单张图片预处理的
图片方形化
功能类似,故不再过多赘述。

(5) 数据集分割

该功能用于将整个数据集按照比例分割成训练集(Train)、验证集(Val)、测试集(Test)。

2.2 图像增强

该模块的图像增强功能实现了图像旋转、水平翻转、垂直翻转、模糊、噪声、亮度和对比度的功能,功能的实现同单张图像预处理模块的图像增强功能相似,故不再重复说明。

不同之处在于,由于该模块下的图像增强功能是对整个数据集进行处理,因此该功能引入了
概率随机处理
,以及
参数在一个区间内随机取值

同样:本部分所有功能可以
按顺序同时叠加使用

3. 可视化训练器

在该模块中,可以使用预集成在应用内的36神经网络模型进行训练,包含如下模型:

  1. AlexNet
  2. VGG11
  3. VGG13
  4. VGG16
  5. VGG19
  6. GoogLeNet
  7. ResNet18
  8. ResNet34
  9. RsNet50
  10. ResNet101
  11. ResNet152
  12. ResNeXt50(32×4d)
  13. ResNeXt101(32×8d)
  14. MobileNetV2
  15. MobileNetV3(large)
  16. MobileNetV3(small)
  17. ShuffleNetV2(×0.5)
  18. ShuffleNetV2(×1.0)
  19. ShuffleNetV2(×1.5)
  20. ShuffleNetV2(×2.0)
  21. EfficientNet(B0)
  22. EfficientNet(B1)
  23. EfficientNet(B2)
  24. EfficientNet(B3)
  25. EfficientNet(B4)
  26. EfficientNet(B5)
  27. EfficientNet(B6)
  28. EfficientNet(B7)
  29. EfficientNetV2(S)
  30. EfficientNetV2(M)
  31. EfficientNetV2(L)
  32. VisionTransformer(b16)
  33. VisionTransformer(b32)
  34. SwinTransformer(t)
  35. SwinTransformer(s)
  36. SwinTransformer(b)

可以直接使用以上模型进行训练,如果不开启迁移学习则是重新从头开始训练自己的数据集,如果需要使用迁移学习,则需要自行下载官方的.pth权重文件,具体操作事项在后续的
说明
中陈述。

本功能除了实现
训练过程可视化
,还可以在训练完成后查看
混淆矩阵
,以及
log输出

注意:
本功能暂未实现继续训练的功能,因此建议使用者在无法确定何时停止训练的情况下,将Epoch数值设置到一个较大的值,在通过可视化确认差不多收敛时点击终止按钮停止训练。

3.1 训练过程可视化

3.2 混淆矩阵

3.3 log输出

二、环境要求

本程序在使用时需要安装以下Python环境:

  1. pyside6
  2. pyqt5
  3. matplotlib
  4. opencv
  5. pytorch

三、使用说明

  1. 本应用的图像处理功能由OpenCV实现,相关参数请参考OpenCV的参数说明。

  2. 数据集预处理功能中,传入的数据集文件夹格式必须严格按照如下方式:
    root文件夹/分类1..n文件夹/图片1..n

  3. 可视化训练器部分中,模型的训练是基于PyTorch深度学习框架实现的,因此最终训练后保存的权重文件是以.pth后缀结尾的形式,如果需要其它形式,可以自行进行权重文件格式的转换。

  4. 如果需要使用迁移学习,请自行下载官方权重文件放在
    algorithms/trainer/imagenet
    目录下,并将下载后的权重文件重命名为“xxx.pth”,且命名必须为如下命名之一:

    alexnet.pth、efficientnet_b0.pth、efficientnet_b1.pth、efficientnet_b2.pth、efficientnet_b3.pth、efficientnet_b4.pth、efficientnet_b5.pth、efficientnet_b6.pth、efficientnet_b7.pth、efficientnet_v2_l.pth、efficientnet_v2_m.pth、efficientnet_v2_s.pth、googlenet.pth、mobilenet_v2.pth、mobilenet_v3_large.pth、mobilenet_v3_small.pth、resnet101.pth、resnet152.pth、resnet18.pth、resnet34.pth、resnet50.pth、resnext101_32x8d.pth、resnext50_32x4d.pth、shufflenetv2_x0_5.pth、shufflenetv2_x1_0.pth、shufflenetv2_x1_5.pth、shufflenetv2_x2_0.pth、swin_b.pth、swin_s.pth、swin_t.pth、vgg11.pth、vgg13.pth、vgg16.pth、vgg19.pth、vit_b_16.pth、vit_b_32.pth

四、代码获取

完整应用程序已免费开源在我的GitHub中:
https://github.com/CorianderSaint/TrainerGUI

请需要者在GitHub中点一颗免费的Star⭐,十分感谢!