提交 87b52420 编写于 作者: shengzhang_'s avatar shengzhang_

优化文档

上级 34a3f68a
......@@ -41,6 +41,9 @@ sa-token是一个轻量级Java权限认证框架,主要解决:登录认证
有了sa-token,你所有的权限认证问题,都不再是问题!
## Sa-Token 能做什么?
![sa-token-js](https://oss.dev33.cn/sa-token/doc/sa-token-js.png)
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 适配RBAC权限模型,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
......@@ -122,7 +125,7 @@ sa-token API 众多,请恕此处无法为您逐一展示,更多示例请戳
## Star 趋势
[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/77YQZ6UK.png 'Gitee')](https://giteye.net/chart/77YQZ6UK)
[![giteye-chart](https://chart.giteye.net/gitee/dromara/sa-token/77YQZ6UK.png 'Gitee')](https://giteye.net/chart/77YQZ6UK)
[![github-chart](https://starchart.cc/dromara/sa-token.svg 'GitHub')](https://starchart.cc/dromara/sa-token)
......
......@@ -5,14 +5,14 @@ import java.util.Map;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
import cn.dev33.satoken.aop.SaTokenListener;
import cn.dev33.satoken.aop.SaTokenListenerDefaultImpl;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.listener.SaTokenListenerDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
......
......@@ -5,18 +5,30 @@ import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("user")
@RequestMapping("/user/")
public class UserController {
// 测试 浏览器访问: http://localhost:8081/user/doLogin
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String test(String username, String password) {
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.setLoginId(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin(String username, String password) {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
......@@ -5,11 +5,7 @@ import org.springframework.context.annotation.Configuration;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouterUtil;
import cn.dev33.satoken.stp.StpUtil;
/**
* [Sa-Token 权限认证] 配置类
......@@ -18,42 +14,28 @@ import cn.dev33.satoken.stp.StpUtil;
*/
@Configuration
public class SaTokenConfigure {
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
.addInclude("/**")
.addExclude("/favicon.ico");
}
/**
* 注册 [sa-token全局过滤器-认证策略]
*/
@Bean
public SaFilterAuthStrategy getSaFilterStrategy() {
return r -> {
System.out.println("---------- 进入sa-token全局过滤器 -----------");
SaRouterUtil.match("/test/test333", () -> StpUtil.checkLogin());
// SaRouterUtil.match("/user/**", () -> StpUtil.checkPermission("user"));
// SaRouterUtil.match("/admin/**", () -> StpUtil.checkPermission("admin"));
// SaRouterUtil.match("/goods/**", () -> StpUtil.checkPermission("goods"));
// SaRouterUtil.match("/orders/**", () -> StpUtil.checkPermission("orders"));
// SaRouterUtil.match("/notice/**", () -> StpUtil.checkPermission("notice"));
// SaRouterUtil.match("/comment/**", () -> StpUtil.checkPermission("comment"));
};
}
/**
* 注册 [sa-token全局过滤器-异常处理策略]
*/
@Bean
public SaFilterErrorStrategy getSaFilterErrorStrategy() {
return e -> AjaxJson.getError(e.getMessage());
}
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 指定 [拦截路由]
.addInclude("/**")
// 指定 [放行路由]
.addExclude("/favicon.ico")
// 指定[认证函数]: 每次请求执行
.setAuth(r -> {
System.out.println("---------- sa全局认证");
// SaRouterUtil.match("/test/test", () -> StpUtil.checkLogin());
})
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(e.getMessage());
})
;
}
}
......@@ -41,6 +41,9 @@ sa-token是一个轻量级Java权限认证框架,主要解决:登录认证
有了sa-token,你所有的权限认证问题,都不再是问题!
## Sa-Token 能做什么?
![sa-token-js](https://oss.dev33.cn/sa-token/doc/sa-token-js.png)
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 适配RBAC权限模型,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
......
......@@ -8,9 +8,9 @@
- `自定义Session`: 指的是以一个`特定的值`作为SessionId,来分配的`Session`
User-Session和Token-Session到底有什么不同?下面这张图可以解释两者的区别
**假设三个客户端登录同一账号,且配置了不共享token,那么此时的Session模型是:**
![session-model](https://oss.dev33.cn/sa-token/doc/session-model2.png 's-w')
![session-model](https://oss.dev33.cn/sa-token/doc/session-model3.png 's-w')
简而言之:
- `Token-Session` 以token为主,只要token不同,那么对应的Session对象就不同
......
......@@ -12,6 +12,10 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu
.logo-box {display: none;}
.main-box .markdown-section{max-width: 1000px;}
}
/* @media screen and (min-width: 1700px) {
.main-box .markdown-section{max-width: 70%;}
} */
/* 调整一下左侧树的字体样式 */
.sidebar .sidebar-nav>ul>li>p{font-size: 1.2em; margin-top: 10px;}
......
......@@ -9,7 +9,7 @@
在IDE中新建一个SpringBoot项目,例如:`sa-token-demo-springboot`(不会的同学请自行百度或者参考github示例)
### 2、设置pom包依赖
### 2、设置依赖
`pom.xml` 中添加依赖:
``` xml
......@@ -26,6 +26,10 @@
你可以**零配置启动项目** ,但同时你也可以在`application.yml`中增加如下配置,定制性使用框架:
``` java
server:
# 端口
port: 8081
spring:
# sa-token配置
sa-token:
......@@ -41,9 +45,11 @@ spring:
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
```
如果你习惯于 `application.properties` 类型的配置文件,那也很好办: <br>
如果你习惯于 `application.properties` 类型的配置文件,那也很好办: <br>
百度: [springboot properties与yml 配置文件的区别](https://www.baidu.com/s?ie=UTF-8&wd=springboot%20properties%E4%B8%8Eyml%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8C%BA%E5%88%AB)
......@@ -60,14 +66,43 @@ public class SaTokenDemoApplication {
}
```
### 5、运行
运行代码,当你从控制台看到类似下面的内容时,就代表框架已经成功集成了
### 5、创建测试Controller
``` java
@RestController
@RequestMapping("/user/")
public class UserController {
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.setLoginId(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin(String username, String password) {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
```
### 6、运行
启动代码,从浏览器依次访问上述测试接口:
![运行结果](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/app-run.jpg)
![运行结果](https://oss.dev33.cn/sa-token/doc/test-do-login.png)
![运行结果](https://oss.dev33.cn/sa-token/doc/test-is-login.png)
<!--
### 普通Spring环境
普通spring环境与springboot环境大体无异,只不过需要在项目根目录手动创建配置文件`sa-token.properties`来完成配置
普通spring环境与springboot环境大体无异,只不过需要在项目根目录手动创建配置文件`sa-token.properties`来完成配置
-->
### 详细了解
......
......@@ -5,14 +5,13 @@ WebFlux基于Reactor响应式模型开发,有着与标准ServletAPI完全不
整合示例在官方仓库的`/sa-token-demo-webflux`文件夹下,如遇到难点可结合源码进行测试学习
---
### 1、创建项目
在IDE中新建一个SpringBoot项目,例如:`sa-token-demo-webflux`(不会的同学请自行百度或者参考github示例)
### 2、设置pom包依赖
### 2、设置依赖
`pom.xml` 中添加依赖:
``` xml
......@@ -25,31 +24,6 @@ WebFlux基于Reactor响应式模型开发,有着与标准ServletAPI完全不
```
### 3、设置配置文件
你可以**零配置启动项目** ,但同时你也可以在`application.yml`中增加如下配置,定制性使用框架:
``` java
spring:
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位:
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: false
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
```
如果你习惯于 `application.properties` 类型的配置文件,那也很好办: <br>
百度: [springboot properties与yml 配置文件的区别](https://www.baidu.com/s?ie=UTF-8&wd=springboot%20properties%E4%B8%8Eyml%20%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8C%BA%E5%88%AB)
### 4、创建启动类
在项目中新建包 `com.pj` ,在此包内新建主类 `SaTokenDemoApplication.java`,输入以下代码:
......@@ -63,14 +37,78 @@ public class SaTokenDemoApplication {
}
```
### 5、运行
运行代码,当你从控制台看到类似下面的内容时,就代表框架已经成功集成了
### 5、创建全局过滤器
新建`SaTokenConfigure.java`,注册Sa-Token的全局过滤器
``` java
/**
* [Sa-Token 权限认证] 全局配置类
*/
@Configuration
public class SaTokenConfigure {
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 指定 [拦截路由]
.addInclude("/**")
// 指定 [放行路由]
.addExclude("/favicon.ico")
// 指定[认证函数]: 每次请求执行
.setAuth(r -> {
System.out.println("---------- sa全局认证");
// SaRouterUtil.match("/test/test", () -> StpUtil.checkLogin());
})
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(e.getMessage());
})
;
}
}
```
?> 你只需要按照此格式复制代码即可,有关过滤器的详细用法,会在之后的章节详细介绍
### 6、创建测试Controller
``` java
@RestController
@RequestMapping("/user/")
public class UserController {
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.setLoginId(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin(String username, String password) {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
```
### 7、运行
启动代码,从浏览器依次访问上述测试接口:
![运行结果](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/app-run.jpg)
![运行结果](https://oss.dev33.cn/sa-token/doc/test-do-login.png)
![运行结果](https://oss.dev33.cn/sa-token/doc/test-is-login.png)
**注意事项**
更多使用示例请参考官方仓库demo
......@@ -3,12 +3,12 @@
Sa-token默认将会话数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:重启后数据会丢失,无法在集群模式下共享数据
为此,sa-token将数据持久操作全部抽象到 `SaTokenDao` 接口中,此设计可以保证开发者对框架进行灵活扩展,比如我们可以将会话数据存储在 `Redis``Memcached`等专业的缓存中间件中,做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
为此,sa-token将数据持久操作全部抽象到 `SaTokenDao` 接口中,保证大家对框架进行灵活扩展,比如我们可以将会话数据存储在 `Redis``Memcached`等专业的缓存中间件中,做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
除了框架内部对`SaTokenDao`提供的基于内存的默认实现,官方仓库还提供了以下扩展方案:<br>
### 1. sa-token 整合 Redis (使用jdk默认序列化方式)
### 1. Sa-Token 整合 Redis (使用jdk默认序列化方式)
``` xml
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<dependency>
......@@ -20,7 +20,7 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
优点:兼容性好,缺点:Session序列化后基本不可读,对开发者来讲等同于乱码
### 2. sa-token 整合 Redis (使用jackson序列化方式)
### 2. Sa-Token 整合 Redis (使用jackson序列化方式)
``` xml
<!-- sa-token整合redis (使用jackson序列化方式) -->
<dependency>
......@@ -34,7 +34,7 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
<br>
### 集成Redis请注意
### 集成Redis请注意
**1. 无论使用哪种序列化方式,你都必须为项目提供一个Redis实例化方案,例如:**
......
......@@ -8,15 +8,14 @@
有,就让你通过。没有?那么禁止访问!
再往底了说,就是每个账号都会拥有一个权限码集合,我来验证这个集合中是否包含指定的权限码 <br/>
例如:当前账号拥有权限码集合:`["user:add", "user:delete", "user:get"]`,这时候我来验证权限 `"user:update"`,则其结果就是:**验证失败,禁止访问** <br/>
(注意: 冒号无特殊含义,可有可无)
例如:当前账号拥有权限码集合:`["user-add", "user-delete", "user-get"]`,这时候我来验证权限 `"user-update"`,则其结果就是:**验证失败,禁止访问** <br/>
所以现在问题的核心就是:
1. 如何获取一个账号所拥有的的权限码集合
2. 本次操作需要验证的权限码是哪个
### 获取当前账号权限码集合
因为每个项目的需求不同,其权限设计也千变万化,【获取当前账号权限码集合】这一操作不可能内置到框架中,
因为每个项目的需求不同,其权限设计也千变万化,因此【获取当前账号权限码集合】这一操作不可能内置到框架中,
所以`sa-token`将此操作以接口的方式暴露给你,以方便的你根据自己的业务逻辑进行重写
你需要做的就是新建一个类,实现`StpInterface`接口,例如以下代码:
......@@ -67,7 +66,8 @@ public class StpInterfaceImpl implements StpInterface {
```
可参考代码:[码云:StpInterfaceImpl.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpInterfaceImpl.java)
注意: 这里的getPermissionList()和getRoleList()是每次权限或者角色验证都会重新执行
<!-- todo: 缓存逻辑 -->
......@@ -76,16 +76,16 @@ public class StpInterfaceImpl implements StpInterface {
``` java
// 当前账号是否含有指定权限, 返回true或false
StpUtil.hasPermission("user:update");
StpUtil.hasPermission("user-update");
// 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
StpUtil.checkPermission("user:update");
StpUtil.checkPermission("user-update");
// 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user:update", "user:delete");
StpUtil.checkPermissionAnd("user-update", "user-delete");
// 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user:update", "user:delete");
StpUtil.checkPermissionOr("user-update", "user-delete");
```
扩展:`NotPermissionException` 对象可通过 `getLoginKey()` 方法获取具体是哪个 `StpLogic` 抛出的异常
......@@ -96,16 +96,16 @@ StpUtil.checkPermissionOr("user:update", "user:delete");
``` java
// 当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasRole("user:update");
StpUtil.hasRole("super-admin");
// 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("user:update");
StpUtil.checkRole("super-admin");
// 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("user:update", "user:delete");
StpUtil.checkRoleAnd("super-admin", "shop-admin");
// 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
StpUtil.checkRoleOr("user:update", "user:delete");
StpUtil.checkRoleOr("super-admin", "shop-admin");
```
扩展:`NotRoleException` 对象可通过 `getLoginKey()` 方法获取具体是哪个 `StpLogic` 抛出的异常
......
......@@ -54,6 +54,3 @@ StpUtil.logoutByLoginId(10001);
StpUtil.disableLoginId(10001, 86400);
```
> 如果想管理当前账号的session怎么办? 请参考:[Session会话](/use/session)
> 如果想查询所有账号的session怎么办? 请参考:[会话治理](/use/search-session)
\ No newline at end of file
......@@ -160,6 +160,3 @@ public void reset(HttpSession session) {
1. `SaSession``HttpSession` 没有任何关系,在`HttpSession`上写入的值,在`SaSession`中无法取出
2. `HttpSession`并未被框架接管,在使用sa-token时,请在任何情况下均使用`SaSession`,不要使用`HttpSession`
> 如果想管理其他账号的session怎么办? 请参考:[踢人下线](/use/kick)
> 如果想查询所有账号的session怎么办? 请参考:[会话治理](/use/search-session)
\ No newline at end of file
......@@ -7,39 +7,29 @@
## 内置风格
sa-token默认的token生成策略是uuid风格, 其模样类似于:`623368f0-ae5e-4475-a53f-93e4225f16ae`<br>
Sa-Token默认的token生成策略是uuid风格, 其模样类似于:`623368f0-ae5e-4475-a53f-93e4225f16ae`<br>
如果你对这种风格不太感冒,还可以将token生成设置为其他风格
怎么设置呢?只需要在yml配置文件里设置 `spring.sa-token.token-style=风格类型` 即可,其有多种取值:
1、token-style=`uuid`,uuid风格 **(默认风格)**
``` html
623368f0-ae5e-4475-a53f-93e4225f16ae
```
``` java
// 1. token-style=uuid —— uuid风格 (默认风格)
"623368f0-ae5e-4475-a53f-93e4225f16ae"
2、token-style=`simple-uuid`,同上,uuid风格, 只不过去掉了中划线:
``` html
6fd4221395024b5f87edd34bc3258ee8
```
// 2. token-style=simple-uuid —— 同上,uuid风格, 只不过去掉了中划线
"6fd4221395024b5f87edd34bc3258ee8"
3、token-style=`random-32`,随机32位字符串:
``` html
qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W
```
// 3. token-style=random-32 —— 随机32位字符串
"qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W"
4、token-style=`random-64`,随机64位字符串:
``` html
v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc
```
// 4. token-style=random-64 —— 随机64位字符串
"v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc"
5、token-style=`random-128`,随机128位字符串:
``` html
nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj
```
// 5. token-style=random-128 —— 随机128位字符串
"nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj"
6、token-style=`tik`,tik风格:
``` html
gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__
// 6. token-style=tik —— tik风格
"gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"
```
......
......@@ -8,11 +8,11 @@ import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.aop.SaTokenListener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.StpInterface;
/**
......
......@@ -8,10 +8,10 @@ import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.aop.SaTokenListener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.StpInterface;
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册