编码 :字节数组(文本或其他格式)64个字符的表示形式
-解码 :
散列函数生成信息的摘要(数据的指纹。唯一标识),摘要信息长度固定
MD5,SHA-128,SHA-256
数据完整性的校验,
秒传,先发散列值,判断服务器是否存在
散列值无法变成原始数据(不可逆)
密码在数据库的存储,散列函数+盐不能找回密码,只能重置
对称密钥(一个密钥)
非对称密钥(公钥,私钥)
SSH ,HTTPS
令牌是在用户验证通过后颁发给用户的身份标示,之后请求只需携带令牌,令牌中用户的标示,不存储敏感信息,
如何防止被伪造,追加签名???下面将以代码形式带你领略,分为三个模块,初级,中级,高级
工程目录:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jwt2</groupId>
<artifactId>jwt2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>13</release>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- JWT 的 java 实现 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
App.java
package jwt2;
import java.security.Key;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
public class App {
public static void main(String[] args) {
// JWT 密钥长度不小于 256 位
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
System.out.println(key.getAlgorithm());
// System.out.println(key.getFormat());
// System.out.println(key.toString());
System.out.println(key.getEncoded().length * 8);
// System.out.println(Arrays.toString(key.getEncoded()));
// 密钥
System.out.println(Base64.getEncoder().encodeToString(key.getEncoded()));
// 负载数据
Claims claims = Jwts.claims();
claims.put("user", "king");
claims.put("role", "admin");
claims.put("roles", new String[] {"admin", "test", "user"});
// 生成了一个 JWT 令牌(未签名)
String token = Jwts.builder()
.setSubject("demo")
.setIssuer("newer")
.setAudience("you")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
.addClaims(claims) // 自定义数据
.signWith(key)
.compact();// 压缩
// header.payload
System.out.println(token);
// 收到客户端请求,从请求头获得令牌解析令牌中的信息
Claims c = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
System.out.println(c);
}
}
运行程序,控制台输出:
工程目录:
pom.xml(依赖)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.newer</groupId>
<artifactId>jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwt</name>
<properties>
<java.version>13</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml(配置信息)
jwt:
secret: OhtVGp0YixAH5+NnruPpXY/yU2E22uYNk4rwZDOAEvQ=
JwtApplication.java
package com.newer.jwt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
}
JWTController.java
package com.newer.jwt.controller;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
public class JWTController {
// 读取配置文件中的值,Spring EL
@Value("${jwt.secret}")
String base64Key;
/**
* 生一个Token
*
* @return String token
*/
@PostMapping("/login")
public String login(@RequestParam(name = "user", defaultValue = "alice") String user,
@RequestParam(name = "info", defaultValue = "负载信息") String info) {
Map<String, Object> claims = new HashMap<>();
claims.put("user", user);
claims.put("info", info);
Date nowDate = new Date();
// 建造者模式(创建对象的设计模式)
// 生成 JWT 令牌
return Jwts.builder()
.setClaims(claims)
.setSubject("jwt")
.setIssuedAt(nowDate) // 令牌生成时间
.setExpiration(new Date(nowDate.getTime() + 1000 * 60 * 5)) // 令牌的有效期
.signWith(getSecretKey()) // 使用了密钥
.compact(); // 压缩
}
/**
* 解码一个Token
*/
@PostMapping("/dec")
public String decode(@RequestParam(name = "jwt") String jwt) {
try {
// 令牌解码
Claims claims = Jwts.parser().setSigningKey(getSecretKey()).parseClaimsJws(jwt).getBody();
return claims.toString();
} catch (ExpiredJwtException e) {
return "Token已过期";
} catch (UnsupportedJwtException e) {
e.printStackTrace();
} catch (MalformedJwtException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return "未知的异常";
}
/**
* 随机生成一个base64的key
*
* @return String base64Key
*/
@GetMapping("/key")
public String generateKey() {
// 生成一个 HS256 算法的密钥
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 用 Base64 把密钥编码成 字符串
return Base64.encodeBase64String(key.getEncoded());
}
/**
* 从配置文件中获取加密key,并构造SecretKey对象返回
*
* @return SecretKey
*/
private SecretKey getSecretKey() {
return new SecretKeySpec(Base64.decodeBase64(base64Key), "HmacSHA256");
}
}
程序运行,浏览器打开
同时可以通过postman去测试/login以及/dec ,在此就不做演示。
工程目录:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>es.softtek</groupId>
<artifactId>jwtDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwtDemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JSON WEB TOKEN -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.http.log-request-details=true
logging.level.web=debug
jwt.secret=V1beFinb07YUJuAjdBevbvCqv9FNqyw4KhM5bMKxCyU=
User.java
package es.softtek.jwtDemo.dto;
public class User {
private String user;
private String pwd;
private String token;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
JwtDemoApplication.java
package es.softtek.jwtDemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JwtDemoApplication.class, args);
}
}
JWTAuthorizationFilter.java
package es.softtek.jwtDemo.security;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
/**
* 基于 JWT 用户认证的过滤器
*
* @author wtao
*
*/
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
String base64Key = "V1beFinb07YUJuAjdBevbvCqv9FNqyw4KhM5bMKxCyU=";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
if (checkJWTToken(request, response)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
}
private Claims validateToken(HttpServletRequest request) {
String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
return Jwts.parser().setSigningKey(getSecretKey()).parseClaimsJws(jwtToken).getBody();
}
private SecretKey getSecretKey() {
return new SecretKeySpec(Base64.decodeBase64(base64Key), "HmacSHA256");
}
/**
* Authentication method in Spring flow
*
* @param claims
*/
private void setUpSpringAuthentication(Claims claims) {
@SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
String authenticationHeader = request.getHeader(HEADER);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
}
WebSecurityConfig.java
package es.softtek.jwtDemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import es.softtek.jwtDemo.security.JWTAuthorizationFilter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers(HttpMethod.POST, "/user").permitAll()
.anyRequest().authenticated();
}
}
HelloWorldController.java
package es.softtek.jwtDemo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public String helloWorld(@RequestParam(value="name", defaultValue="World") String name) {
return "Hello "+name+"!!";
}
}
UserController.java
package es.softtek.jwtDemo.controller;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import es.softtek.jwtDemo.dto.User;
import io.jsonwebtoken.Jwts;
@RestController
public class UserController {
@Value("${jwt.secret}")
String base64Key;
@PostMapping("/user")
public User login(@RequestParam("user") String username, @RequestParam("password") String pwd) {
String token = getJWTToken(username);
User user = new User();
user.setUser(username);
user.setToken(token);
return user;
}
private String getJWTToken(String username) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
String token = Jwts.builder().setSubject(username)
.claim("authorities",
grantedAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 600000)).signWith(getSecretKey()).compact();
return "Bearer " + token;
}
private SecretKey getSecretKey() {
return new SecretKeySpec(Base64.decodeBase64(base64Key), "HmacSHA256");
}
}
JwtDemoApplicationTests.java
package es.softtek.jwtDemo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class JwtDemoApplicationTests {
@Test
public void contextLoads() {
}
}
程序运行,此时访问/hello是被禁止的,我们先通过post /user获得令牌信息
上图中我们已经获取到令牌信息,然后我们可以用这个令牌访问hello
以上就是 JWT令牌的使用以及一些算法,有问题的小伙伴,欢迎留言!!!
文章浏览阅读4.1w次。Uni. GB Uni. GB Uni. GB Uni. GB Uni. GB 00A4 A1E8 ¤ 00A7 A1EC § 00A8 A1A7 ¨ 00B0 A1E3 ° 00B1 A1C0 ±00B7 A1A4 · 00D7 A1C1 × 00E0 A8A4 à 00E1 A8A2 á 00_53ea3747-853d-4c37-8b70-992f8256fadc
文章浏览阅读673次,点赞9次,收藏10次。kafka-ui 项目是由 Provectus 公司开发和维护的,旨在为 Kafka 用户提供一个可视化管理工具,简化 Kafka 集群的管理和监控任务。kafka-ui 在 /api/clusters/local/topics/{topic}/messages 的 q 参数中存在远程代码执行漏洞,攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个web服务器。_cve-2023-52251
文章浏览阅读5.4k次,点赞7次,收藏39次。开发思路1.引入需要的模块,配置图片路径,设置界面宽高背景颜色,创建游戏主入口。#1引入需要的模块import pygameimport random#1配置图片地址IMAGE_PATH = 'imgs/'#1设置页面宽高scrrr_width=800scrrr_height =560#1创建控制游戏结束的状态GAMEOVER = False#1主程序class M..._python hook pvz
文章浏览阅读532次,点赞6次,收藏17次。activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,一套完整并且实际运用在多套项目中的案例,满足日常业务流程审批需求。
文章浏览阅读3.1k次,点赞19次,收藏18次。在设计和实现哈希表时,我们面临着一个重要的问题,即哈希冲突。哈希冲突发生在不同的键映射到相同的哈希桶位置时,这可能导致数据的丢失或者影响哈希表的性能。因此,解决哈希冲突是构建高效、稳定哈希表的关键一环。在面对哈希冲突时,我们需要采用一些巧妙的方法来保证数据的唯一性、高效的查找和插入操作。下面将介绍几种常见的解决哈希冲突的方法,包括开放寻址法、链地址法以及其他一些策略。解决哈希冲突是哈希表设计中不可忽视的重要问题。选择合适的冲突解决策略直接影响了哈希表的性能和稳定性。_解决哈希冲突的主要方法有
文章浏览阅读1.1w次。使用webapi做rest的服务接口时,有些读取数据表的操作,数据本身变化不频繁,但是访问量却不小,比如频道分类,地市选择信息等等等等。这时,必然想到使用缓存。 在普通controller下,由于controller实现了一堆接口,其中包括了很多的filter,所以,可以轻松的实现缓存,如果只需要页面级别缓存,则大可以使用之前提到的OutputCacheAttribute,轻松搞定。_c#webapi返回后内存没有清理
文章浏览阅读3.9k次,点赞3次,收藏9次。ios测试证书配置流程打包证书,我们需要去生成AdHoc证书。首先,我们需要先添加,我们需要测试的IOS手机的设备。点击加号。右侧选文件的不用管,我们看一下这个设备的UDID值,这个值是我们ios设备的一个类似序列号的标识,我们在手机上是查询不到的。推荐三种方式,来获取设备的UDID号1.蒲公英获取(需要配置证书)2.通过数据线链接方式,使用ITunes Store。3.通过数据线链接方式,使用爱思助手。这样我们就能拿到UDID。接下来我们创建一._uniapp打包的.ipa可以自己安装吗
文章浏览阅读980次。一、类与对象 (1)类的定义:类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。 (2)对象的定义:对象是具有类类型的变量。类和对象是面向对象编程技术中的最基本的概念。 (3)类与对象的关系:类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的..._unity中方法和类的区别
文章浏览阅读262次。昆明理工大学C语言程序设计课后习题答案第1章认识C语言(一)、是非题1.程序是指挥计算机进行各种信息处理任务的一组指令序列。A.对B.错2.机器语言与硬件平台相关,但汇编语言和硬件平台无关。A.对B.错3.编译型高级语言明显优于解释型高级语言。A.对B.错4.C语言把高级语言的基本结构和低级语言的实用性紧密结合起来,不仅适合编写应用软件,而且适于编写系统软件。A.对B.错5.面向对象的程序设计方法..._]c语言允许在同一条语句中定义多个相同类型的变量,其间用分号进行分隔。
文章浏览阅读255次,点赞3次,收藏10次。Cool Clock - 创意无限的数字时钟Cool Clock 是一款开源、跨平台的数字时钟应用,由 Simon Baird 开发并维护。它可以让你在计算机上以独特的方式显示当前时间,并且支持自定义皮肤,让你的桌面更具个性。什么是 Cool Clock?Cool Clock 是一个基于 JavaFX 的数字时钟软件,可以在 Windows, macOS 和 Linux 等操作系统上运行。它...
文章浏览阅读9k次,点赞5次,收藏21次。更改键盘皮肤1、先导入模块import QtQuick.VirtualKeyboard.Settings 2.22、设置“复古”皮肤/主题,目前除了默认的皮肤就这个了,感觉这个更漂亮VirtualKeyboardSettings.styleName = &amp;quot;retro&amp;quot;效果: 设置键盘大小和位置InputPanel { id: inputPanel_import qtquick.virtualkeyboard import qtquick.virtualkeyboard.settings
文章浏览阅读115次。开发框架ZQCMS v1.0是使用layui2.4.5+laravel5.7搭建的Zqcms介绍系统主要是志在更快的开发后台,减少代码冗余,所以本cms基本大部分通过js渲染html,php代码均为模块化写法,只需要配置好你需要的就可以生成页面,使用了模块化的开发模式第三方扩展treeGrid树状表格:安装1、在数据库中创建数据库,并修改env文件中的数据库配置连到你的创建的数据库2、运行comp..._layui php管理系统