首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >SpringBoot项目集成【用户身份认证】实战 【实战核心篇】基于JWT生成和校验Token

SpringBoot项目集成【用户身份认证】实战 【实战核心篇】基于JWT生成和校验Token

作者头像
天罡gg
修改于 2023-04-02 07:49:18
修改于 2023-04-02 07:49:18
1.3K00
代码可运行
举报
文章被收录于专栏:天罡gg天罡gg
运行总次数:0
代码可运行

前言

书接上文 技术选型篇,我们做了【用户身份认证】的技术选型说明,对基于Session、Token、JWT的方案进行了详细的对比分析,详细说明了它们都是什么和各自的优缺点!这些是实战的基础,还没看过的同学,建议先看上文。最终我和狗哥(博客主页) 采用的是目前流行的基于JWT的Token用户身份认证机制!

本文是实战核心篇,重点是把JWT的核心代码实现! 基于上文我们分析的【用户身份认证】的流程(如下图),我们可以确定使用JWT的核心是实现两点:生成Token、校验Token! 接下来我们就来实现它!

PS,完整的用户身份认证代码早已实现,和狗哥也已联调通过,正在赶工博文,预告一下我将分三篇来写,非常详细,料很足,准备好发车喽,Let’s go!

  • 【技术选型篇】基于Session、Token、JWT怎么选? 【上文-已发布】
  • 【实战核心篇】基于JWT生成和校验Token【本文】
  • 【实战全流程篇】基于JWT+双重检查的登录+登出+拦截器 --防XSS+CSRF漏洞【下文】

本文目录


专栏介绍

因为可能还有很多同学还不清楚上下文,所以简单介绍一下这个专栏要做的事:

天罡老哥和狗哥(博客主页)有意从0到1带大家搭建一个SpringBoot+SpringCloud+Vue的前后端分离项目! 打造一个短小精悍、技术主流、架构规范的前后端分离实战项目!我负责后端,狗哥负责前端! 目的就是让大家通过项目实战,学到一些真东西,将所学理论落地,助力有心强大的你更快的成长!开启你的工作之旅,让开发游刃有余!

详细的后端规划后端大纲思维导图在开篇已经给出,你可以到开篇查收:基于SpringBoot+SpringCloud+Vue前后端分离项目实战 --开篇


一、引入依赖

官方推荐Java的JWT开源库中,收藏数最高的是:java-jwtjjwt-root

我们选择使用java-jwt库,项目中将认证相关的通用实现会封装到common层!提前展示一下目录结构,方便大家对照实战:

pom中引入依赖,版本号依然定义在父pom定义!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
</dependency>

二、TokenProvider 接口

令牌的提供者接口。

从用户身份认证对Token的应用场景来看,核心实现就两个方法:创建Token校验Token

所以,这里提取一个接口TokenProvider,虽然目前只有JWT一种实现,但JWT实际上也只是Token中的一种,所以,以后想用其它Token,只要实现TokenProvider接口,就可以平滑的切过去。

符合开闭原则:对扩展开放,对修改关闭!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface TokenProvider {

    /**
     * 根据用户授权信息,创建token
     */
    String create(AuthContextInfo authContextInfo);

    /**
     * 校验token,解析出用户授权信息
     */
    AuthContextInfo verify(String token) ;
}

AuthContextInfo里保存的是认证信息,包含两个重要字段(也就是要存入Payload中的信息):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private String userId;
private String userName;

三、JwtTokenProvider 实现类

基于JWT实现的令牌提供者,快速预览说明如下:

从上图可以看出,除了两个核心方法,还定义了两个Payload相关的常量,不过这不是重点。

重点是红框处的【将依赖由构造函数传入】,说明一下为什么这么做!

JWT的签名算法(JwtAlgorithm)和 过期时间(expire)都是变化点,根据依赖倒置原则,要依赖抽象接口,不依赖具体实现,所以我们将它交给外部传入!

另外,在common层实现的类,对变化点应不做决定,而是交给上层决定将依赖注入。

1. 创建Token方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public String create(AuthContextInfo authContextInfo) {
    Date issuedAt = new Date();

    Calendar expiresAt  = Calendar.getInstance();
    expiresAt.add(Calendar.SECOND, expire);

    return JWT.create()
        // 签发者
        .withIssuer(authContextInfo.getUserId())
        // 主题
        .withSubject(SUBJECT)
        // 签发时间
        .withIssuedAt(issuedAt)
        // 过期时间
        .withExpiresAt(expiresAt.getTime())
        // 在签发时间之前不可用
        .withNotBefore(issuedAt)
        // 自定义 userName
        .withClaim(CLAIM_USERNAME, authContextInfo.getUserName())
        .sign(this.jwtAlgorithm.getAlgorithm());
}
  • with开头的方法都是构建payload字段信息,withClaim是构建自定义字段,可以构建多个自定义字段!
  • sign方法是指定签名算法 这里不依赖具体算法,而是依赖JwtAlgorithm接口!说完校验token再具体说 JwtAlgorithm。

2. 校验Token方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public AuthContextInfo verify(String token) {
    DecodedJWT decodedJWT;
    try {
        // 校验token,无效或过期会抛异常
        decodedJWT = this.jwtAlgorithm.getJwtVerifier().verify(token);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    // 主题不一致,被修改了
    if (!SUBJECT.equals(decodedJWT.getSubject())) {
        return null;
    }
    // 返回userId和userName
    AuthContextInfo authInfo = new AuthContextInfo();
    authInfo.setUserId(decodedJWT.getIssuer());
    authInfo.setUserName(decodedJWT.getClaim(CLAIM_USERNAME).asString());
    return authInfo;
}
  • verify方法不报错,说明token合法且未过期,解析的decodedJWT对象里面包含了我们创建时存储的payload载荷信息(也就是数据)。

3. JwtAlgorithm接口

JwtAlgorithm接口是 JwtTokenProvider 的重要依赖,主要包括获取【签名算法】和【验证方法】,定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface JwtAlgorithm {

    /**
     * 获取JWT使用的算法
     */
    Algorithm getAlgorithm();

    /**
     * 获取JWT使用的验证方法
     */
    JWTVerifier getJwtVerifier();
}

3.1 RSA算法实现

对应的是RSA算法的实现类JwtRsaAlgorithm,这里的公钥(publicKey)和私钥(privateKey)也是通过构造函数由外部指定,也是将依赖倒置!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class JwtRsaAlgorithm implements JwtAlgorithm {

    private final Algorithm algorithm;
    private final JWTVerifier jwtVerifier;
    @Override
    public Algorithm getAlgorithm() {
        return algorithm;
    }
    @Override
    public JWTVerifier getJwtVerifier() {
        return jwtVerifier;
    }

    /**
     * 有参构造函数,将依赖倒置
     */
    public JwtRsaAlgorithm(String publicKey, String privateKey) {
        // 获取公钥对象
        RSAPublicKey rsaPublicKey;
        try {
            rsaPublicKey = RsaKeyUtils.getPublicKey(publicKey);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Get RSA public key error!", e);
        }
        // 获取私钥对象
        RSAPrivateKey rsaPrivateKey;
        try {
            rsaPrivateKey = RsaKeyUtils.getPrivateKey(privateKey);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Get RSA public key error!", e);
        }
        // 创建RSA256签名算法
        this.algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey);
        // 构建JWTVerifier对象
        this.jwtVerifier = JWT.require(this.algorithm)
                .acceptLeeway(10)
                .acceptExpiresAt(5)
                .build();;
    }
}
  • 代码中的像Algorithm.RSA256JWT.require都是开源库提供的,用于生成最终的AlgorithmJWTVerifier;
  • RsaKeyUtils 是我们定义的通用的工具类,用于将base64编码的 公钥(publicKey)和私钥(privateKey)转为对应的Key对象,直接拷贝使用即可。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class RsaKeyUtils {
    public static RSAPublicKey getPublicKey(String base64String) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] b = Base64.getDecoder().decode(base64String);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(b);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key key = keyFactory.generatePublic(keySpec);
        return (RSAPublicKey) key;
    }

    public static RSAPrivateKey getPrivateKey(String base64String) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] b = Base64.getDecoder().decode(base64String);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key key = keyFactory.generatePrivate(keySpec);
        return (RSAPrivateKey)key;
    }
}

3.2 HMAC算法实现

支持的算法很多,我们再扩展JwtAlgorithm接口,实现一下HMAC算法,你可以实现你需要的算法!

对应的是HMAC算法的实现类JwtHmacAlgorithm,这里的秘钥(secret)也是通过构造函数由外部指定,也是将依赖倒置!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class JwtHmacAlgorithm  implements JwtAlgorithm {

    private final Algorithm algorithm;
    private final JWTVerifier jwtVerifier;

    @Override
    public Algorithm getAlgorithm() {
        return algorithm;
    }
    @Override
    public JWTVerifier getJwtVerifier() {
        return jwtVerifier;
    }

    /**
     * 有参构造函数,将依赖倒置
     */
    public JwtHmacAlgorithm(String secret) {
        // 创建HMAC256签名算法
        this.algorithm = Algorithm.HMAC256(secret);
        // 构建JWTVerifier对象
        this.jwtVerifier = JWT.require(this.algorithm)
                .acceptLeeway(10)
                .acceptExpiresAt(5)
                .build();
    }
}
  • 使用Algorithm.HMAC256生成算法,再根据Algorithm生成JWTVerifier;

四、测试

因为我们写的类不依赖Spring容器,所以直接在JwtTokenProvider里写个main方法就可以测试,这里使用RSA算法测试,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws InterruptedException {
    String publicKey = "太长,省略,可以到 http://www.metools.info/code/c80.html 自行生成。。。";
    String privateKey = "太长,省略,可以到 http://www.metools.info/code/c80.html 自行生成。。。";
    // 创建RSA算法
    JwtAlgorithm jwtRsaAlgorithm = new JwtRsaAlgorithm(publicKey, privateKey);
    // 创建JWT提供者:10秒过期 + RSA算法
    TokenProvider jwtTokenProvider = new JwtTokenProvider(10, jwtRsaAlgorithm);
    // 测试保存到Token中的授权信息
    AuthContextInfo authContextInfo = new AuthContextInfo();
    authContextInfo.setUserId("123456");
    authContextInfo.setUserName("admin");
    // 创建token
    String token = jwtTokenProvider.create(authContextInfo);
    System.out.println("token:" + token);
    // 循环校验token何时过期
    while (true) {
        Thread.sleep(2000);
        // 校验token
        AuthContextInfo authInfo = jwtTokenProvider.verify(token);
        if (authInfo == null) {
            break;
        }
        System.out.println("校验ok:" + authInfo.toString());
    }
}

上面的测试代码,既包括了【创建Token】方法,也包括了【校验Token】方法,主逻辑如下:

  • 创建jwtTokenProvider:基于RSA算法的jwtRsaAlgorithm
  • 创建1个只有10秒有效的Token:jwtTokenProvider.create
  • 循环校验Token是否过期:jwtTokenProvider.verify 返回null就过期了

测试结果,刚好10秒过期!


五、Web层配置注入

在SpringBoot中,我们通常将类交给Spring管理,首先复习一下之前讲过的常用的组件注解:

  • @Service: 通常放在service层的服务类上
  • @Repository: 通常放在dal层的数据访问类上
  • @Controller: 通常放在web层控制器的类上
  • @Component: 代表通用的组件,从它派生了上面3个注解,用于各个实际的场景.

打上这些注解的类,在Spring中称之为Bean。

本文,我们将学习一种新的IOC注入方式:通过JavaConfig的方式注入Bean,即在类上加@Configuration注解,代表这是一个配置类,里面通过添加@Bean注解注入Bean对象,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class AuthConfig {

    @Bean
    public JwtAlgorithm jwtRsaAlgorithm(@Value("${auth.jwt.rsa.publicKey}") String publicKey, @Value("${auth.jwt.rsa.privateKey}") String privateKey ) {
        return new JwtRsaAlgorithm(publicKey, privateKey);
    }

    @Bean
    public TokenProvider jwtTokenProvider(@Value("${auth.jwt.expire}") int expire, JwtAlgorithm jwtAlgorithm) {
        return new JwtTokenProvider(expire, jwtAlgorithm);
    }
}

通过以上方式,我们就向Spring的IOC容器注入了TokenProvider、JwtAlgorithm,这样我们就可以通过@Autowired注解直接使用了!以后如果想将切换算法,只需要修改这里的配置类,而不用去修改已实现的RSA算法类。

这里还有一个知识点:@Value注解,用于读取配置文件(application.properties或application.yml)中读取字段的值,格式:@Value(“$配置字段名”),这里是用在方法参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 @Value("${auth.jwt.expire}") int expire
 代表:expire = 配置文件中auth.jwt.expire的值

这里到了3个@Value,对应application.properties中的3个配置如下(以\换行):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auth.jwt.expire =300

auth.jwt.rsa.publicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArK+NOK/89rNAWeAguHti\
91QpMaHDZ6EaaySu5dyEvw6oUs4t8AiEc6HC7iTl1U2fxvuukk6P3e96V5w+fb+S\
UFUUaO+oocsKOOxwXcfJ1uQorMsEns1PjYB9weOOYYQoE2KY34AE6+zRT3w8uMXX\
pBmazZbPhUP8cGAOimUv4nSIK4n/nwBezEEeFM5dREaxabiDBe9HvOXmu8EfO2/P\
MsE5K9x/GP/wNbE+yzP+rC6rr3mgJNugUmE7BB1Usl7pS1myukiFz+PXoE/nibed\
k5FWzL5jeV8M8F7AZ404DdVhyN5dbLvwAI8jnnJ1nNRVEh5+1H0rvwSSlTAo+Po+\
bwIDAQAB

auth.jwt.rsa.privateKey=MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCsr404r/z2s0BZ\
4CC4e2L3VCkxocNnoRprJK7l3IS/DqhSzi3wCIRzocLuJOXVTZ/G+66STo/d73pX\
nD59v5JQVRRo76ihywo47HBdx8nW5CisywSezU+NgH3B445hhCgTYpjfgATr7NFP\
fDy4xdekGZrNls+FQ/xwYA6KZS/idIgrif+fAF7MQR4Uzl1ERrFpuIMF70e85ea7\
wR87b88ywTkr3H8Y//A1sT7LM/6sLquveaAk26BSYTsEHVSyXulLWbK6SIXP49eg\
T+eJt52TkVbMvmN5XwzwXsBnjTgN1WHI3l1su/AAjyOecnWc1FUSHn7UfSu/BJKV\
MCj4+j5vAgMBAAECggEBAJ9/djzJsChc4C8jKJW8wWgYQAQrmUR6NOCJfVGqIKIn\
c6kn7p4p/8yduGIlinM9wzoS9OcF0TP4IVQSaFXVP9sa+kMCOQtXchWprQ+xnOfy\
zO7shVP35maYK4+OEtBXNHzTMMgegm02yw1TfvJbKhXT4HvLs9kvNlbFIikJ1PSf\
kRdruq8/SiqDAiwtN4OUn7X3/pIx6b9P7hbO95aNUi1Dxb8xjQA05QVlqA8OwNyq\
ORUHI0ayZI6dmyTA5FUkZZf1tS0PzVLjubBOjZHRSq1a8Eg2qV+e/zDNPkuKQZ3g\
jyy6PamkRlSbfel6+8zacQVC8QRe1AAX68HFe/WKz2ECgYEA2WZySOPBJF85KuK1\
Tv8rgNAoRZZNZbH/0YT2OkBOprOX7bOtvSx+nTZPw0U7nR3nMqnJ9uk/gVfmkbD6\
WzHaSNEpxim2lT+A9jMC5FZcaQxJDHHBpUdMbPssvPGkE8i0XY+rxyQCugVp7+Jg\
mTHISfaZCSBmAG09qtp3Wuk8li0CgYEAy1iv53kChnVvBTckQYHWI5R5ByzPOEum\
EHLo8fvEvUSWaVlDDoPeFw1XtybNVBeyeu/c3HLi7/Z1836PwtpCCAF9XSIq8N/B\
PUR2hKDlg4j3m6BvR25Pu54ORbyevL1LugV+iGVfQ9lWjeV6XeYoN/jGTwSY/Hb+\
dc4rur8sBIsCgYBYlFx2hI460q3JYow7fs7r8mSmTeKFUCyK4yEshO1HESATU0W0\
Mb/5MJr5Vmk+0GNWikXnXAxrGDSzIigwJjTpvIfH3VEuqKxUJF7GSMXoa4AMGQGs\
5UsnkIQfDFotUXbkNFjqkCqoPvJ2Mofng5g3QsoCJPhKrjgVOGSvXx83lQKBgQCG\
0Y8WveFRumxYHd4Y3HdYcajoe+oLngRFJZqSTWV8QwwiXr8Z0Y4e5IbCdKRv26JG\
5d8d/cG+bT54qPGxs7lRy4MNi4jC2OcqssiNWIuy8M2RzgXZaybL8pft3oe0BSE+\
/UOONP+7YU6El5/Qv7bsnTEF1LuFr3M4MfBGSVdqzwKBgQDJBulXrWWQujQlQ+9/\
u7YoCwIr6N/ZL0fpnKtaQ7WfHs7zy6QUhu6skufFJKmWehOD6i+SWBmuhv4PPMCS\
IPhjChIh8AL8AVfSCjrksP0YENOHtbBhSE9bBHdH4u9VBy+6lbErSLl0867Qy4Z4\
LAN0Bjc+5MNy0vMQmqat/EKlHA==

web层对应的目录结构:


最后

OK,就说这么多了,我们下文见!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
dotnet C# 创建 X11 应用时设置窗口背景颜色
在 dotnet C# 设置 X11 应用窗口背景透明 的基础上,可以通过创建 XColor 结构体,将 XColor 赋值给到 XSetWindowAttributes 的 background_pixel 进行设置窗口的初始化背景颜色
林德熙
2024/06/01
2140
C语言怎么改变窗口的字体颜色和背景颜色?
如果学C语言久了,难免会对弹出的黑窗口感到厌烦,那这时候如果能改一下黑窗口的背景颜色和字体颜色,也许会给自己一个好一点的心情。废话不多说,现在开始教你怎么简单地改变窗口的字体颜色和背景颜色。
全栈程序员站长
2022/09/06
6.8K0
C语言怎么改变窗口的字体颜色和背景颜色?
Windows常用快捷键和常用的cmd命令(亲测用了办公效率提升明显)
大家好,又见面了,我是你们的朋友全栈君。 文章目录 Widows常用快捷键 常用的运行窗口命令大全 常用的cmd命令 Widows常用快捷键 Win + D :回到桌面(Win + M也可以实现回到桌面,不过Win + D 可以快速回到桌面,再按一次又能回到原网页,这是Win+M做不到的。) Win + E :打开文件资源管理器(计算机)(一键打开各种盘😇) Win + I: 打开设置 Win + L : 锁屏(尤其是对于程序员来说,电脑里会有很多资料,办公室里有时会有外部人员
全栈程序员站长
2022/09/13
8.7K0
Windows常用快捷键和常用的cmd命令(亲测用了办公效率提升明显)
window cmd 命令大全 (order) Windows CMD命令大全
CMD命令:开始->运行->键入cmd或command(在命令行里可以看到系统版本、文件系统版本)
小蔚
2019/09/11
14.2K0
c语言基础学习02_windows系统下的cmd命令
============================================================================= 注意:cmd的命令很多,需要用的时候可以查询即可!
黑泽君
2018/10/11
1.5K0
Windows CMD命令大全
虽然随着计算机产业的发展,Windows 操作系统的应用越来越广泛,DOS 面临着被淘汰的命运,但是因为它运行安全、稳定,有的用户还在使用,所以一般Windows 的各种版本都与其兼容,用户可以在Windows 系统下运行DOS,中文版Windows XP 中的命令提示符进一步提高了与DOS 下操作命令的兼容性,用户可以在命令提示符直接输入中文调用文件。
monster_moya
2020/07/09
5.2K1
cmd命令【实施工程师技能】
【cli】模式相信作为【实施工程师】的人们肯定的用很多,测试网络连通性,测试环境变量搭建是否成功都会用到,那么,对于小白的【实施工程师】本篇文章会有很高的价值。
红目香薰
2022/11/29
9650
cmd命令【实施工程师技能】
Windows Longhorn_Windows优化
一、系统优化设置。 1、删除Windows强加的附件: 1) 用记事本NOTEPAD修改/winnt/inf/sysoc.inf,用查找/替换功能,在查找框中输入,hide(一个英文逗号紧跟hide),将“替换为”框设为空。并选全部替换,这样,就把所有的,hide都去掉了, 2) 存盘退出, 3)再运行“添加-删除程序”,就会看见“添加/删除 Windows 组件”中多出了好几个选项;这样你可以删除好些没有用的附件 2、关掉调试器Dr. Watson; Dr.Watson是自带的系统维护工具,它会在程序加载失败或崩溃时显示。运行drwtsn32,把除了“转储全部线程上下文”之外的全都去掉。否则一旦有程序出错,硬盘会响很久,而且会占用很多空间。如果你以前遇到过这种情况,请查找user.dmp文件并删掉,可能会省掉几十M的空间。这是出错程序的现场,对我们没用。然后打开注册表,找到HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug子键分支,双击在它下面的Auto键值名称,将其“数值数据”改为0,最后按F5刷新使设置生效,这样就彻底来取消它的运行了。 另外蓝屏时出现的memory.dmp也可删掉。可在我的电脑/属性中关掉BSOD时的DUMP 3、关闭“系统还原”:鼠标右健单击桌面上的“我的电脑”,选择“属性”,找到“系统还原”,如果你不是老噼里啪啦安装一些软件(难道你比我还厉害??),你也可以去掉,这样可以节省好多空间。
全栈程序员站长
2022/11/01
2.4K0
超级文本编辑器Sublime Text3「建议收藏」
Sublime Text3是一个超强的文本编辑工具,跨平台(Windows、Linux、Mac);几乎你需要的功能都有,一切可修改(快捷键、插件包etc.);界面优美;可惜的是不开源,不过即使不注册也可以使用。Lime Text是其开源版的一种实现,我还没打算用这个。
全栈程序员站长
2022/09/02
3.1K0
超级文本编辑器Sublime Text3「建议收藏」
MacBook苹果电脑如何安装Adobe PS、AU、PR等软件?
Adobe作为全球领先的多媒体设计软件供应商,出品了一系列图形设计、影像编辑与网络开发的Adobe软件产品套装,涵盖平面,插画,音视频,动画等创意类相关应用。从事多媒体设计相关的工作者基本都会用Adobe全系列软件。adobe系列软件有哪些?
office小助手
2022/05/14
9.5K2
MacBook苹果电脑如何安装Adobe PS、AU、PR等软件?
windows bat批处理基础命令学习教程「建议收藏」
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,分享一下给大家。点击跳转到教程。
全栈程序员站长
2022/09/23
19.9K0
人脸检测发展:从VJ到深度学习(上)
本文分上下两篇,上篇主要介绍人脸检测的基本流程,以及传统的VJ人脸检测器及其改进,下篇介绍基于深度网络的检测器,以及对目前人脸检测技术发展的思考与讨论。为了让本文更适合非计算机视觉和机器学习背景的读者,文中对所涉及到的专业术语尽量以通俗的语言和用举例的方式来进行解释,同时力求严谨,以体现实事求是和一丝不苟的科学研究精神。 这是一个看脸的世界!自拍,我们要艺术美颜;出门,我么要靓丽美妆。上班,我们要刷脸签到;回家,我们要看脸相亲。 当手机把你的脸变得美若天仙,当考勤机认出你的脸对你表示欢迎,你知道是什么
AI科技评论
2018/03/07
1.8K0
人脸检测发展:从VJ到深度学习(上)
最新最全的微信小程序入门学习教程,微信小程序零基础入门到精通
讲解课程:https://edu.csdn.net/course/detail/9531
全栈程序员站长
2022/09/13
2.7K0
bat批处理命令教程_windows批处理命令脚本
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/171945.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/23
7.7K0
Python 自动化指南(繁琐工作自动化)第二版:十九、处理图像
输入 Python。Pillow 是一个用于与图像文件交互的第三方 Python 模块。该模块有几个功能,可以轻松地裁剪、调整和编辑图像的内容。Python 能够像处理 Microsoft Paint 或 Adobe Photoshop 等软件一样处理图像,因此可以轻松地自动编辑成百上千的图像。运行pip install --user -U pillow==6.0.0就可以安装 Pillow 了。附录 A 有更多关于安装模块的细节。
ApacheCN_飞龙
2023/04/04
2.9K0
Python 自动化指南(繁琐工作自动化)第二版:十九、处理图像
使用 Python 和 Pygame 制作游戏:第一章到第五章
在阅读本书之前,如果您了解一些 Python 编程知识(或者知道如何使用 Python 之外的其他语言进行编程),可能会有所帮助;但是即使您没有,您仍然可以阅读本书。编程并不像人们想象的那么难。如果您遇到问题,可以在线阅读免费书籍“使用 Python 发明自己的电脑游戏”http://inventwithpython.com,或者在 Invent with Python 维基 http://inventwithpython.com/wiki 上查找您觉得困惑的主题。
ApacheCN_飞龙
2024/01/15
2.4K0
使用 Python 和 Pygame 制作游戏:第一章到第五章
css笔记
从HTML被发明开始,样式就以各种形式存在。不同的浏览器结合它们各自的样式语言为用户提供页面效果的控制。最初的HTML只包含很少的显示属性。 随着HTML的成长,为了满足页面设计者的要求,HTML添加了很多显示功能。但是随着这些功能的增加,HTML变的越来越杂乱,而且HTML页面也越来越臃肿。于是CSS便生了。
用户6362579
2019/09/29
8.3K0
css笔记
网络安全攻击与防护--HTML学习
  HTML的官方介绍什么的我就不说了,打字也挺累的,只简单介绍一下吧,其他的懂不懂都没关系。   HTML全称为Hypertext Markup Language,中文解释为超文本标记语言。   在HTML语言中,所有的标记都必须用尖括号(即大于号“<”和小于号“>”)括起来,一般情况下,每个标记单独占一行,
程序员小藕
2022/05/09
3.2K0
OpenCV 图像处理学习手册:1~5
本章旨在与 OpenCV,其安装和第一个基本程序进行首次接触。 我们将涵盖以下主题:
ApacheCN_飞龙
2023/04/27
3.2K0
OpenCV 图像处理学习手册:1~5
BAT 批处理脚本教程
第一节 常用批处理内部命令简介 批处理定义:顾名思义,批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件,其扩展名为BAT或者CMD。这些命令统称批处理命令。 小知识:可以在键盘上按下Ctrl+C组合键来强行终止一个批处理的执行过程。 了解了大概意思后,我们正式开始学习.先看一个简单的例子!
用户3519280
2023/07/08
1.2K0
推荐阅读
相关推荐
dotnet C# 创建 X11 应用时设置窗口背景颜色
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档