JWT介绍
JWT
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),提供一种简洁且自包含的方式,以JSON形式在通信双方间传递信息。这些信息可通过数字签名进行验证,确保其可信度。JWT 可以使用密钥(HMAC)或 RSA 或 ECDSA 的公钥/私钥对进行签名。虽然JWT可以加密以提供私密消息,但我们一般指的是已签名的 tokens。
JWT作用
- 身份验证和授权:JWT常被用来在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从资源服务器获取资源。这种方式特别适用于分布式站点的登录场景357。此外,JWT还可以包含用户标识、用户角色和权限等信息,进一步实现细粒度的授权控制。
- 信息交换:由于JWT的载荷部分可以自定义,它可以存储任何JSON格式的数据,这使得JWT成为一种便捷的方式来进行信息交换。例如,可以使用JWT来存储用户喜好、配置信息等自定义功能所需的信息。
- 单点登录(SSO):JWT因其小巧和自包含的特性,特别适合用于实现单点登录(SSO)。在单点登录场景中,用户只需进行一次登录操作,便可以在多个服务之间自由切换,而无需重复登录,极大地提高了用户体验。
- API密钥管理:JWT也广泛应用于API密钥管理领域。通过使用JWT作为API请求的一部分,可以有效地管理和控制对API的访问权限。
- 替代传统的Session和Cookie机制:在某些情况下,JWT可以用来替代传统的Session和Cookie机制。与Session和Cookie相比,JWT具有更小的服务器开销和更好的扩展性,因为它直接将用户数据下发给客户端,每次请求时附带发送给服务器。
- 跨域请求(Cross-Origin Requests):JWT可用于跨域请求的身份验证。由于JWT是自包含的,它不依赖于任何服务器端的会话存储,因此非常适合用于跨域API调用。
认证流程
- 前端通过Web将用户名和密码发送至后端,通常采用HttpPost请求。
- 后端在核对用户名和密码后,会将用户ID及其他信息作为JWT payload,并生成一个token返回给前端。
- 前端保存这个token,可以选择将其保存在localStorage或sessionStorage中,退出登录时删除token即可。
- 在后续的每次请求中,前端需要在header中放入token。
- 后端会检查token的有效性,包括签名是否正确,token是否过期等。
- 当后端验证token通过后,就会进行业务处理,并返回相应结果。
JWT的组成:
一个JWT实际上由三个部分组成,它们之间用点(.
)分隔:
- 头部(Header):
- 通常包含两个部分:token的类型(typ),这里固定为JWT;以及所使用的签名算法(alg),例如HS256表示使用HMAC和SHA256算法进行签名。
- 例如:
{"alg": "HS256", "typ": "JWT"}
- 载荷(Payload):
- 包含了一系列可以被添加到JWT中的声明(claims)。这些声明可以分为三类:
- 公共声明(Public Claims): 任何JWT都可以包含的声明,如
exp
(过期时间)、iat
(发行时间)和sub
(主题)。 - 注册声明(Registered Claims): 一组预定义的声明,具有特定的语义意义,如
iss
(发行者)、aud
(接收方)等。 - 私有声明(Private Claims): 由应用程序定义的声明,可以在标准声明之外添加额外的信息。
- 公共声明(Public Claims): 任何JWT都可以包含的声明,如
- 例如:
{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
- 包含了一系列可以被添加到JWT中的声明(claims)。这些声明可以分为三类:
- 签名(Signature):
- 为了确保JWT的安全性,使用头部中指定的算法和服务器的密钥对头部和载荷进行签名。接收方可以使用相同的密钥和算法来验证签名的有效性。
- 签名的生成通常使用Base64 URL编码的头部和载荷,然后与密钥一起通过签名算法进行加密。
将这三部分编码(Base64 URL编码)并用点连接起来,就形成了一个完整的JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cU5O62AvvewJHsfqh1EJpTbuE7sBqVb64hjSDgZSTrZDRbky3ofGkeXBXBQgTJgzTyTTh0b2tlbg
在这个例子中,JWT由三部分组成,每部分都用.
分隔。第一部分是头部,指定了签名算法;第二部分是载荷,包含了用户信息和过期时间等声明;第三部分是签名,用于验证JWT的真实性。
代码实例
1.安装JWT库
安装golang-jwt/jwt库
go get -u [github.com/golang-jwt/jwt](http://github.com/golang-jwt/jwt)
2.创建JWT密钥
import ("github.com/golang-jwt/jwt"
)// 生成JWT密钥
key := jwt.New(jwt.SigningMethodHS256)
3.创建JWT令牌
创建JWT令牌时,你需要定义载荷(Payload)中的声明(Claims),并使用密钥对令牌进行签名
import ("github.com/golang-jwt/jwt""time"
)
//创建JWT令牌
func createToken(claims map[string]interface{})(string,error){//创建JWT令牌token:=jwt.NewWithClaims(key,jwy.MapClaims(claims))//设置过期时间token.Claims.(jwt.MapClaims)["exp"]=time.Now().Add(5*time.Minute).Unix()//签发令牌tokenString, err := token.SignedString()if err != nil {return "", err}return tokenString, nil
}
4.验证JWT令牌
验证JWT令牌时,你需要使用相同的密钥和签名方法来验证签名的有效性,并检查令牌是否过期:
// parseToken 从给定的token字符串中解析出一个有效的jwt.Token实例。
// 函数接受一个tokenString参数,表示待解析的JWT字符串,
// 并返回一个指向jwt.Token的指针以及一个error。
// 如果解析成功,返回的jwt.Token将包含解码后的JWT声明信息,
// 否则返回一个非nil error说明解析失败的原因。
func parseToken(tokenString string)(*jwt.Token,error){
// 使用jwt.ParseWithClaims方法解析tokenString,// 将声明部分解析为jwt.MapClaims类型(一个可索引的声明映射)。// 提供一个自定义的解析密钥验证函数作为第三个参数。tokne,err:=jwt.ParseWithClaims(tokenString,&jwt.MapClaims{},func(tokekn *jwt.Token)(interface{},error){return key,nil})if err!=nil{return nil,error}// 如果token解析成功且有效(即其声明经过验证且未过期等),// 则尝试将其声明部分转换为jwt.MapClaims类型,并检查转换是否成功。// 若成功且token仍处于有效状态(token.Valid == true),则返回解析得到的token及其原始声明。if claims, ok := token.Claims.(*jwt.MapClaims); ok && token.Valid {return token, nil}// 如果上述条件均不满足(即解析或验证过程中出现错误,// 或token已过期、被篡改等导致无效),则返回一个表示无效JWT的预定义错误。return nil, jwt.ErrInvalid
}
此函数的主要功能是接收一个JWT字符串,通过调用jwt.ParseWithClaims
方法对其进行解析。在解析过程中,它会使用传入的密钥(key
)验证JWT的签名,确保其完整性和有效性。如果解析和验证成功,函数返回解析后的jwt.Token
实例以及nil
错误。否则,返回nil
的jwt.Token
指针和相应的错误信息。
5.实现登录和注册逻辑
在后端服务中,实现登录和注册逻辑,并在登录成功后返回JWT令牌:
func login(w http.ResponseWriter, r *http.Request) {// 假设你已经验证了用户的凭据// 创建JWT令牌claims := jwt.MapClaims{"userID": "user-id","exp": time.Now().Add(5 * time.Minute).Unix(),}tokenString, err := createToken(claims)if err != nil {// 处理错误}// 返回JWT令牌w.Header().Set("Content-Type", "application/json")json.NewEncoder(w).Encode(map[string]string{"token": tokenString})
}
6. 保护路由
创建一个中间件来保护你的路由,确保只有携带有效JWT令牌的请求才能访问:
// requireAuth 是一个HTTP中间件函数,用于对请求进行JWT身份验证。
// 它接收一个http.HandlerFunc类型的next参数,代表待执行的下一个HTTP处理器。
// 函数返回一个新的http.HandlerFunc,该处理器在实际执行next之前,
// 会对传入的请求进行JWT令牌验证。若验证失败,则返回HTTP 401 Unauthorized响应。
func requireAuth(next http.HandlerFunc) http.HandlerFunc {// 返回一个匿名http.HandlerFunc,作为实际执行的身份验证中间件。return func(w http.ResponseWriter, r *http.Request) {// 从请求头的"Authorization"字段中提取JWT令牌字符串。tokenString := r.Header.Get("Authorization")// 如果请求头中没有提供"Authorization"字段或其值为空,// 则向客户端发送HTTP 401 Unauthorized响应,并附带错误消息。if tokenString == "" {http.Error(w, "Authorization header is required", http.StatusUnauthorized)return}// 使用parseToken函数解析并验证JWT令牌字符串。// 如果解析或验证过程中发生错误,或者令牌本身无效,// 则向客户端发送HTTP 401 Unauthorized响应,并附带错误消息。token, err := parseToken(tokenString)if err != nil || !token.Valid {http.Error(w, "Invalid token", http.StatusUnauthorized)return}// 如果JWT令牌验证成功,继续执行传入的next处理器(即后续的HTTP处理逻辑)。next(w, r)}
}
此代码实现了HTTP中间件requireAuth
,其作用是在接收到HTTP请求时,检查请求头中的Authorization
字段是否存在并含有有效的JWT令牌。如果请求缺少Authorization
字段或令牌无效,中间件会立即向客户端返回HTTP 401 Unauthorized状态码及相应的错误消息。反之,若令牌验证通过,则允许请求继续传递到下一个HTTP处理器(next
参数指定的函数)进行后续处理。这样的设计使得您可以轻松地将requireAuth
中间件应用于特定路由或全局,以确保只有持有有效JWT令牌的客户端才能访问受保护的资源。
然后,使用这个中间件来保护你的路由:
http.HandleFunc("/protected", requireAuth(func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Welcome to the protected route!")
}))
实例
在Go语言中实现JWT鉴权的完整应用实例。
package mainimport ("fmt""net/http""time"jwtgo "github.com/golang-jwt/jwt"
)// jwtKey 是用于签署 JWT 令牌的秘密密钥,必须保密。
var jwtKey = []byte("my_secret_key")// User 定义了用户的结构体,包含用户的ID和姓名。
// 这些字段将被编码到 JWT 令牌中。
type User struct {ID int `json:"id"` // 用户的唯一标识符Name string `json:"name"` // 用户的姓名
}// createToken 根据提供的 User 对象创建一个新的 JWT 令牌。
// 它设置了一个 "userID" 声明和一个 5 分钟后过期的时间。
func createToken(user *User) (string, error) {// 使用 HS256 签名方法创建一个新的 JWT 实例。token := jwtgo.New(jwtgo.SigningMethodHS256)// 将 token 的 Claims 设置为 MapClaims 类型,以便我们可以添加自定义声明。claims := token.Claims.(jwtgo.MapClaims)// 添加 "userID" 声明到 JWT 载荷中。claims["userID"] = user.ID// 设置 "exp" 声明为当前时间加上 5 分钟的 Unix 时间戳。claims["exp"] = time.Now().Add(5 * time.Minute).Unix()// 使用 jwtKey 签署令牌,并将其转换为字符串。tokenString, err := token.SignedString(jwtKey)if err != nil {return "", err}return tokenString, nil
}// parseToken 接受一个 JWT 令牌字符串,并使用 jwtKey 验证其签名。
// 如果令牌有效,它将返回解析后的 jwtgo.Token 对象。
func parseToken(tokenString string) (*jwtgo.Token, error) {// 使用 jwtgo.ParseWithClaims 解析 JWT 令牌,并使用 MapClaims 类型作为期望的声明类型。token, err := jwtgo.ParseWithClaims(tokenString, &jwtgo.MapClaims{}, func(token *jwtgo.Token) (interface{}, error) {// 提供 jwtKey 作为密钥来验证令牌的签名。return jwtKey, nil})if err != nil {return nil, err}// 确保解析后的声明是 MapClaims 类型。if _, ok := token.Claims.(*jwtgo.MapClaims); !ok {return nil, fmt.Errorf("invalid claims")}// 检查令牌是否有效(例如,是否已过期)。if !token.Valid {return nil, fmt.Errorf("token is not valid")}return token, nil
}// register 是一个 HTTP 处理函数,用于处理用户注册请求。
// 它创建一个新的 User 对象,并为其生成一个 JWT 令牌。
func register(w http.ResponseWriter, r *http.Request) {// 示例中省略了实际的用户注册逻辑(如保存用户信息到数据库)。// 直接创建一个示例用户和令牌。user := &User{ID: 1, Name: "John Doe"}token, err := createToken(user)if err != nil {http.Error(w, "Error creating token", http.StatusInternalServerError)return}// 设置响应头为 application/json 并将令牌编码为 JSON 返回。w.Header().Set("Content-Type", "application/json")json.NewEncoder(w).Encode(map[string]string{"token": token})
}// login 是一个 HTTP 处理函数,用于处理用户登录请求。
// 它验证用户凭据(示例中省略),并返回一个 JWT 令牌。
func login(w http.ResponseWriter, r *http.Request) {// 示例中省略了实际的用户登录逻辑(如验证用户凭据)。// 直接创建一个示例用户和令牌。user := &User{ID: 1, Name: "John Doe"}token, err := createToken(user)if err != nil {http.Error(w, "Error creating token", http.StatusInternalServerError)return}w.Header().Set("Content-Type", "application/json")json.NewEncoder(w).Encode(map[string]string{"token": token})
}// protected 是一个 HTTP 处理函数,用于处理需要身份验证的请求。
// 它从 HTTP 头中提取 JWT 令牌,并验证其有效性。
func protected(w http.ResponseWriter, r *http.Request) {// 从 "Authorization" HTTP 头中提取 JWT 令牌。tokenString := r.Header.Get("Authorization")if tokenString == "" {http.Error(w, "Unauthorized access", http.StatusUnauthorized)return}// 解析并验证 JWT 令牌。token, err := parseToken(tokenString)if err != nil {http.Error(w, "Unauthorized access", http.StatusUnauthorized)return}// 如果令牌有效,返回成功响应。w.WriteHeader(http.StatusOK)w.Write([]byte("Welcome to the protected route!"))
}func main() {// 设置 HTTP 路由处理函数。http.HandleFunc("/register", register)http.HandleFunc("/login", login)http.HandleFunc("/protected", protected)// 启动 HTTP 服务并监听 8080 端口。http.ListenAndServe(":8080")
}
这个程序实现了一个简单的 JWT 鉴权系统。它提供了用户注册和登录功能,这两个功能都会生成一个 JWT 令牌。此外,它还提供了一个受保护的路由,该路由只允许携带有效 JWT 令牌的请求访问。
相关文章:

JWT介绍
JWT JSON Web Token (JWT) 是一种开放标准 (RFC 7519),提供一种简洁且自包含的方式,以JSON形式在通信双方间传递信息。这些信息可通过数字签名进行验证,确保其可信度。JWT 可以使用密钥(HMAC)或 RSA 或 ECDSA 的公钥/…...

如何实现YOLOv8保存目标检测后的视频文件
首先安装所需的库和依赖项,确保你已经安装了OpenCV和YOLOv8的相关库和依赖项。你可以使用pip或conda来安装它们。 其次加载YOLOv8模型,使用YOLOv8的训练权重文件和配置文件,加载模型并进行初始化。这可以通过使用适当的库函数来完成&…...

LlamaIndex 组件 - Prompts
文章目录 一、关于 Prompts1、概念2、使用模式概览3、示例指南 二、使用模式1、定义自定义提示2、获取和设置自定义提示2.1 常用提示2.2 访问提示2.3 更新提示2.4 修改查询引擎中使用的提示2.5 修改索引构建中使用的提示 3、[高级]高级提示功能3.1 部分格式化3.2 模板变量映射3…...

Github 2024-04-16Python开源项目日报 Top10
根据Github Trendings的统计,今日(2024-04-16统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10TypeScript项目1Vue项目1系统设计指南 创建周期:2507 天开发语言:Python协议类型:OtherStar数量:241693 个Fork数量:42010 次…...

ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理
ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理 环境介绍 ElasticSearch 版本号: 6.7.0 需求说明 用户会传入多个关键字去ES查询ElasticSearch nested 字段 的多个字段,要求在返回的结果中被搜索的字段需要高亮所有匹配的关键字。…...

python_31-32
目录 1.进程 2.同步进程: 3.守护进程: 1.进程 # ### 进程 process import os,time""" # ps -aux 查看进程号 # ps -aux | grep 2784 过滤查找2784这个进程# 强制杀死进程 kill -9 进程号# 获取当前进程号 res os.getpid() print(res)…...

关于机器学习/深度学习的一些事-答知乎问(四)
如何评估和量化深度学习的可解释性问题? 针对深度学习模型,评估指标能够全面衡量模型是否满足可解释性。与分类的评估指标(准确度、精确度和召回率)一样,模型可解释性的评估指标应能从特定角度证明模型的性能。但是&a…...

[spring] Spring Boot REST API - 项目实现
Spring Boot REST API - 项目实现 书接上文 Spring Boot REST API - CRUD 操作,一些和数据库相关联的注解在 [spring] spring jpa - hibernate CRUD 主要的 layer 如下: #mermaid-svg-QE1PR1gyrkz4XIT0 {font-family:"trebuchet ms",verdana…...

ELK之Filebeat实用配置及批量部署(部署200+可用)
跟我之前Zabbix-agent批量部署脚本Linux and Windows(部署300可用)文章的套路一样,在使用该脚本前,请先准备好安装包及配置好安装包的资源下载点,由于我这边是纯内网,所以我就找了一个NAS做了共享目录&…...

用odin实现的资源复制编辑器
用odin实现了一个资源复制编辑器,使用要安装odin,功能是把要复制的资源路径一个个添加设置,点copy能把列表里的资源全部复制,支持目录复制到目录,文件复制到目录,文件复制替换。提升效率,让自己…...

linux监控文件操作行为
linux监控文件操作行为 使用 auditd 系统 auditd 是Linux系统的一个安全和审计系统,它能够跟踪系统上发生的安全相关事件。要使用 auditd 来监控文件,你需要首先确保 auditd 已经安装并且运行在你的系统上。 然后,你可以使用 auditctl 命令…...

单链表接口函数的实现(增删查改)
一、单链表的实现形式以及接口函数的声明 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int DataType ;typedef struct SListNode {DataType data;struct SListNode* next; }SLTNODE; void SLTPrint(SLTNODE* phead);//打印链表 SLTNO…...

超低功耗Sub-1G收发芯片DP32RF002 M0内核(G)FSK/OOK 无线收发机的32位SoC芯片
产品概述 DP32RF002是深圳市动能世纪科技有限公司研制的基于ARMCortex-MO内核的超低功耗 高性能的、单片集成(G)FSK/OOK 无线收发机的32位SoC芯片。工作于200 ~960MHz范围内,支持灵活可设的数据包格式,支持自动应答和自动重发功能,支持跳频…...

uniapp_微信小程序_NaN
一、定义 isNaN() 函数用于检查一个值是否为 NaN。它接受一个参数,该参数可以是任何 JavaScript 数据类型,包括数字、字符串、对象等。如果参数是 NaN,或者不能被转换为数字,则 isNaN() 返回 true;否则返回 false。 …...

1043: 利用栈完成后缀表达式的计算
解法: #include<iostream> #include<stack> using namespace std; int main() {char a;stack<int> sk;while (cin >> a && a ! #) {if (a > 0 && a < 9) {sk.push(a - 0);}else {int num2 sk.top();sk.pop();int n…...

初学ELK - elk部署
一、简介 ELK是3个开源软件组合,分别是 Elasticsearch ,Logstash,Kibana Elasticsearch :是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自…...

[Java EE] 计算机工作原理与操作系统简明概要
1. 计算机工作原理 1.1 生活中常见的计算机 计算机分为通用计算机和专用计算机,计算机并不单单指的是电脑,还有我们平时使用的手机,ipad,智能手表等终端设备都是计算机.还有我们用户不常见的计算机,比如服务器. 还有许多嵌入式设备(针对特定场景定制的"专用计算机"…...

【尚硅谷】Git与GitLab的企业实战 学习笔记
目录 第1章 Git概述 1. 何为版本控制 2. 为什么需要版本控制 3. 版本控制工具 4. Git简史 5. Git工作机制 6. Git和代码托管中心 第2章 Git安装 第3章 Git常用命令 1. 设置用户签名 1.1 基本语法 1.2 案例实操 2. 初始化本地库 2.1 基本语法 2.2 案例实操 3. 查…...

如何在MobaXterm上使用rz命令
1、首先输入命令和想下载的文件,如下图: 2、按住ctrl鼠标右键,选择如下选项: 上传命令是rz,选择Receive...... 下载命令是sz,选择Send...... 3、我这里是要把Linux上的文件下载到我的本地window磁盘&…...

【计算机考研】408网课汇总+资源分享
408王道的视频就比较通俗易懂 王道的教材非常契合408的大纲,是专门为408大纲而编写的,而教材是方方面面都讲解的透彻。 建议王道为主,网络搜索为辅! 王道中讲解不清楚,看不懂的知识点,可以尝试在网络上进…...

如何在OceanBase v4.2 中快速生成随机数据
在使用传统数据库如 MySQL 和 Oracle 时,由于缺乏多样化的随机数据生成方案,或者实现成本过高,构造随机数据的开发成本受到了影响。OceanBase在老版本中虽然有相应的解决方案,但语法复杂和性能较差等问题仍然存在。 现在…...

nvm node.js的安装
说明:部分但不全面的记录 因为过程中没有截图,仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2,了解后 发现是nvm使用的版本较低,于是涉及nvm卸载 重新下载最新版本的nvm 2…...

【Docker】安装Redis、Nginx
1、安装redis mkdir -p /docker/redis mkdir -p /docker/redis/data touch /docker/redis/redis.conf touch /docker/redis/redis.bash编辑配置文件 vim /docker/redis/redis.conf # Redis配置文件# Redis默认不是以守护进程的方式运行,可以通过该配置项修改&…...

RK3568 UBUNTU修改网卡名称
RK3568 UBUNTU系统有两个网卡,ETH0和ETH1,于设备机壳丝印ETH1、ETH2无法对应,于是百度了一下相关的修改办法,有修改设备树的等等,挑了一个最简单,验证通过 #第1步,将原网卡关闭ip …...

【华为OD机试C++】统计字符
《最新华为OD机试题目带答案解析》:最新华为OD机试题目带答案解析,语言包括C、C++、Python、Java、JavaScript等。订阅专栏,获取专栏内所有文章阅读权限,持续同步更新! 文章目录 描述输入描述输出描述示例代码描述 输入一行字符,分别统计出包含英文字母、空格、数字和其它…...

百货商场用户画像描绘and价值分析(下)
目录 内容概述数据说明技术点主要内容4 会员用户画像和特征字段创造4.1 构建会员用户基本特征标签4.2 会员用户词云分析 5 会员用户细分和营销方案制定5.1 会员用户的聚类分析及可视化5.2 对会员用户进行精细划分并分析不同群体带来的价值差异 内容概述 本项目内容主要是基于P…...

spring-cloud微服务gateway
核心部分:routes(路由), predicates(断言),filters(过滤器) id:可以理解为是这组配置的一个id值,请保证他的唯一的,可以设置为和服务名一致 uri:可以理解为是通过条件匹配之后需要路由到&…...

【python】在pycharm创建一个新的项目
双击打开pycharm,选择create new project 选择create,后进入项目 右键项目根目录,选择new一个新的python file 随意命名一下 输入p 然后后面就会出现智能补全提示,此时轻敲一下tab,代码就写好了,非常的方便 右键执行一下代码,下面两个直接运行和debug运行都是可以的 小结 …...

java小作业(9)----用函数实现斐波那契数列(第二遍)
代码: public class Main {public static void main(String[] args) {int n 20; // 你可以更改这个值来计算和输出前n个斐波那契数for (int i 0; i < n; i) {System.out.print(fibonacci(i) " ");}}public static int fibonacci(int n) {if (n <…...

部署项目的时候的一些错误
项目打jar包,找不到资源,连接不上数据库 项目打包后无法运行 直接在idea运行可以 解决方法:pom文件中增加(配置文件如果是yml,写yml) <resources><resource><directory>src/main/java&…...