Commit 522d70a29b755930665b8f4dc4bb574ed2015175

Authored by YunaiV
1 parent 65d2dffe

完善 OAuth2TokenServiceImplTest 单元测试

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java
@@ -9,7 +9,6 @@ import com.baomidou.mybatisplus.annotation.TableName; @@ -9,7 +9,6 @@ import com.baomidou.mybatisplus.annotation.TableName;
9 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; 9 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
10 import lombok.Data; 10 import lombok.Data;
11 import lombok.EqualsAndHashCode; 11 import lombok.EqualsAndHashCode;
12 -import lombok.experimental.Accessors;  
13 12
14 import java.util.Date; 13 import java.util.Date;
15 import java.util.List; 14 import java.util.List;
@@ -22,11 +21,10 @@ import java.util.List; @@ -22,11 +21,10 @@ import java.util.List;
22 * 21 *
23 * @author 芋道源码 22 * @author 芋道源码
24 */ 23 */
25 -@TableName("system_oauth2_access_token") 24 +@TableName(value = "system_oauth2_access_token", autoResultMap = true)
26 @KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 25 @KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
27 @Data 26 @Data
28 @EqualsAndHashCode(callSuper = true) 27 @EqualsAndHashCode(callSuper = true)
29 -@Accessors(chain = true)  
30 public class OAuth2AccessTokenDO extends TenantBaseDO { 28 public class OAuth2AccessTokenDO extends TenantBaseDO {
31 29
32 /** 30 /**
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java
@@ -18,7 +18,7 @@ import java.util.List; @@ -18,7 +18,7 @@ import java.util.List;
18 * 18 *
19 * @author 芋道源码 19 * @author 芋道源码
20 */ 20 */
21 -@TableName("system_oauth2_refresh_token") 21 +@TableName(value = "system_oauth2_refresh_token", autoResultMap = true)
22 // 由于 Oracle 的 SEQ 的名字长度有限制,所以就先用 system_oauth2_access_token_seq 吧,反正也没啥问题 22 // 由于 Oracle 的 SEQ 的名字长度有限制,所以就先用 system_oauth2_access_token_seq 吧,反正也没啥问题
23 @KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 23 @KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
24 @Data 24 @Data
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java
@@ -25,7 +25,7 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO @@ -25,7 +25,7 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
25 return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>() 25 return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()
26 .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId()) 26 .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())
27 .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType()) 27 .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType())
28 - .eqIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId()) 28 + .likeIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId())
29 .gt(OAuth2AccessTokenDO::getExpiresTime, new Date()) 29 .gt(OAuth2AccessTokenDO::getExpiresTime, new Date())
30 .orderByDesc(OAuth2AccessTokenDO::getId)); 30 .orderByDesc(OAuth2AccessTokenDO::getId));
31 } 31 }
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.oauth2;
  2 +
  3 +import cn.hutool.core.util.RandomUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  5 +import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  8 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  9 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
  10 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
  12 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
  13 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
  14 +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;
  15 +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;
  16 +import cn.iocoder.yudao.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
  17 +import org.assertj.core.util.Lists;
  18 +import org.junit.jupiter.api.Test;
  19 +import org.springframework.boot.test.mock.mockito.MockBean;
  20 +import org.springframework.context.annotation.Import;
  21 +
  22 +import javax.annotation.Resource;
  23 +import java.time.Duration;
  24 +import java.util.Date;
  25 +import java.util.List;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
  28 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  29 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  30 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  31 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  32 +import static org.junit.jupiter.api.Assertions.*;
  33 +import static org.mockito.ArgumentMatchers.eq;
  34 +import static org.mockito.Mockito.when;
  35 +
  36 +/**
  37 + * {@link OAuth2TokenServiceImpl} 的单元测试类
  38 + *
  39 + * @author 芋道源码
  40 + */
  41 +@Import({OAuth2TokenServiceImpl.class, OAuth2AccessTokenRedisDAO.class})
  42 +public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
  43 +
  44 + @Resource
  45 + private OAuth2TokenServiceImpl oauth2TokenService;
  46 +
  47 + @Resource
  48 + private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
  49 + @Resource
  50 + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
  51 +
  52 + @Resource
  53 + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
  54 +
  55 + @MockBean
  56 + private OAuth2ClientService oauth2ClientService;
  57 +
  58 + @Test
  59 + public void testCreateAccessToken() {
  60 + TenantContextHolder.setTenantId(0L);
  61 + // 准备参数
  62 + Long userId = randomLongId();
  63 + Integer userType = RandomUtil.randomEle(UserTypeEnum.values()).getValue();
  64 + String clientId = randomString();
  65 + List<String> scopes = Lists.newArrayList("read", "write");
  66 + // mock 方法
  67 + OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId)
  68 + .setAccessTokenValiditySeconds(30).setRefreshTokenValiditySeconds(60);
  69 + when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);
  70 +
  71 + // 调用
  72 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, userType, clientId, scopes);
  73 + // 断言访问令牌
  74 + OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken());
  75 + assertPojoEquals(accessTokenDO, dbAccessTokenDO, "createTime", "updateTime", "deleted");
  76 + assertEquals(userId, accessTokenDO.getUserId());
  77 + assertEquals(userType, accessTokenDO.getUserType());
  78 + assertEquals(clientId, accessTokenDO.getClientId());
  79 + assertEquals(scopes, accessTokenDO.getScopes());
  80 + assertFalse(DateUtils.isExpired(accessTokenDO.getExpiresTime()));
  81 + // 断言访问令牌的缓存
  82 + OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken());
  83 + assertPojoEquals(accessTokenDO, redisAccessTokenDO, "createTime", "updateTime", "deleted");
  84 + // 断言刷新令牌
  85 + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectList().get(0);
  86 + assertPojoEquals(accessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted");
  87 + assertFalse(DateUtils.isExpired(refreshTokenDO.getExpiresTime()));
  88 + }
  89 +
  90 + @Test
  91 + public void testRefreshAccessToken_null() {
  92 + // 准备参数
  93 + String refreshToken = randomString();
  94 + String clientId = randomString();
  95 + // mock 方法
  96 +
  97 + // 调用,并断言
  98 + assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),
  99 + new ErrorCode(400, "无效的刷新令牌"));
  100 + }
  101 +
  102 + @Test
  103 + public void testRefreshAccessToken_clientIdError() {
  104 + // 准备参数
  105 + String refreshToken = randomString();
  106 + String clientId = randomString();
  107 + // mock 方法
  108 + OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId);
  109 + when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);
  110 + // mock 数据(访问令牌)
  111 + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
  112 + .setRefreshToken(refreshToken).setClientId("error");
  113 + oauth2RefreshTokenMapper.insert(refreshTokenDO);
  114 +
  115 + // 调用,并断言
  116 + assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),
  117 + new ErrorCode(400, "刷新令牌的客户端编号不正确"));
  118 + }
  119 +
  120 + @Test
  121 + public void testRefreshAccessToken_expired() {
  122 + // 准备参数
  123 + String refreshToken = randomString();
  124 + String clientId = randomString();
  125 + // mock 方法
  126 + OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId);
  127 + when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);
  128 + // mock 数据(访问令牌)
  129 + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
  130 + .setRefreshToken(refreshToken).setClientId(clientId)
  131 + .setExpiresTime(addTime(Duration.ofDays(-1)));
  132 + oauth2RefreshTokenMapper.insert(refreshTokenDO);
  133 +
  134 + // 调用,并断言
  135 + assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),
  136 + new ErrorCode(401, "刷新令牌已过期"));
  137 + }
  138 +
  139 + @Test
  140 + public void testRefreshAccessToken_success() {
  141 + TenantContextHolder.setTenantId(0L);
  142 + // 准备参数
  143 + String refreshToken = randomString();
  144 + String clientId = randomString();
  145 + // mock 方法
  146 + OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId)
  147 + .setAccessTokenValiditySeconds(30);
  148 + when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);
  149 + // mock 数据(访问令牌)
  150 + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
  151 + .setRefreshToken(refreshToken).setClientId(clientId)
  152 + .setExpiresTime(addTime(Duration.ofDays(1)));
  153 + oauth2RefreshTokenMapper.insert(refreshTokenDO);
  154 + // mock 数据(访问令牌)
  155 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setRefreshToken(refreshToken);
  156 + oauth2AccessTokenMapper.insert(accessTokenDO);
  157 + oauth2AccessTokenRedisDAO.set(accessTokenDO);
  158 +
  159 + // 调用
  160 + OAuth2AccessTokenDO newAccessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
  161 + // 断言,老的访问令牌被删除
  162 + assertNull(oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken()));
  163 + assertNull(oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken()));
  164 + // 断言,新的访问令牌
  165 + OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(newAccessTokenDO.getAccessToken());
  166 + assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, "createTime", "updateTime", "deleted");
  167 + assertPojoEquals(newAccessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted",
  168 + "creator", "updater");
  169 + assertFalse(DateUtils.isExpired(newAccessTokenDO.getExpiresTime()));
  170 + // 断言,新的访问令牌的缓存
  171 + OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(newAccessTokenDO.getAccessToken());
  172 + assertPojoEquals(newAccessTokenDO, redisAccessTokenDO, "createTime", "updateTime", "deleted");
  173 + }
  174 +
  175 + @Test
  176 + public void testGetAccessToken() {
  177 + // mock 数据(访问令牌)
  178 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
  179 + .setExpiresTime(addTime(Duration.ofDays(1)));
  180 + oauth2AccessTokenMapper.insert(accessTokenDO);
  181 + // 准备参数
  182 + String accessToken = accessTokenDO.getAccessToken();
  183 +
  184 + // 调用
  185 + OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);
  186 + // 断言
  187 + assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted",
  188 + "creator", "updater");
  189 + assertPojoEquals(accessTokenDO, oauth2AccessTokenRedisDAO.get(accessToken), "createTime", "updateTime", "deleted",
  190 + "creator", "updater");
  191 + }
  192 +
  193 + @Test
  194 + public void testCheckAccessToken_null() {
  195 + // 调研,并断言
  196 + assertServiceException(() -> oauth2TokenService.checkAccessToken(randomString()),
  197 + new ErrorCode(401, "访问令牌不存在"));
  198 + }
  199 +
  200 + @Test
  201 + public void testCheckAccessToken_expired() {
  202 + // mock 数据(访问令牌)
  203 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
  204 + .setExpiresTime(addTime(Duration.ofDays(-1)));
  205 + oauth2AccessTokenMapper.insert(accessTokenDO);
  206 + // 准备参数
  207 + String accessToken = accessTokenDO.getAccessToken();
  208 +
  209 + // 调研,并断言
  210 + assertServiceException(() -> oauth2TokenService.checkAccessToken(accessToken),
  211 + new ErrorCode(401, "访问令牌已过期"));
  212 + }
  213 +
  214 + @Test
  215 + public void testCheckAccessToken_success() {
  216 + // mock 数据(访问令牌)
  217 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
  218 + .setExpiresTime(addTime(Duration.ofDays(1)));
  219 + oauth2AccessTokenMapper.insert(accessTokenDO);
  220 + // 准备参数
  221 + String accessToken = accessTokenDO.getAccessToken();
  222 +
  223 + // 调研,并断言
  224 + OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);
  225 + // 断言
  226 + assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted",
  227 + "creator", "updater");
  228 + }
  229 +
  230 + @Test
  231 + public void testRemoveAccessToken_null() {
  232 + // 调用,并断言
  233 + assertNull(oauth2TokenService.removeAccessToken(randomString()));
  234 + }
  235 +
  236 + @Test
  237 + public void testRemoveAccessToken_success() {
  238 + // mock 数据(访问令牌)
  239 + OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
  240 + .setExpiresTime(addTime(Duration.ofDays(1)));
  241 + oauth2AccessTokenMapper.insert(accessTokenDO);
  242 + // mock 数据(刷新令牌)
  243 + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
  244 + .setRefreshToken(accessTokenDO.getRefreshToken());
  245 + oauth2RefreshTokenMapper.insert(refreshTokenDO);
  246 + // 调用
  247 + OAuth2AccessTokenDO result = oauth2TokenService.removeAccessToken(accessTokenDO.getAccessToken());
  248 + assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted",
  249 + "creator", "updater");
  250 + // 断言数据
  251 + assertNull(oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken()));
  252 + assertNull(oauth2RefreshTokenMapper.selectByRefreshToken(accessTokenDO.getRefreshToken()));
  253 + assertNull(oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken()));
  254 + }
  255 +
  256 +
  257 + @Test
  258 + public void testGetAccessTokenPage() {
  259 + // mock 数据
  260 + OAuth2AccessTokenDO dbAccessToken = randomPojo(OAuth2AccessTokenDO.class, o -> { // 等会查询到
  261 + o.setUserId(10L);
  262 + o.setUserType(1);
  263 + o.setClientId("test_client");
  264 + o.setExpiresTime(DateUtils.addTime(Duration.ofDays(1)));
  265 + });
  266 + oauth2AccessTokenMapper.insert(dbAccessToken);
  267 + // 测试 userId 不匹配
  268 + oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setUserId(20L)));
  269 + // 测试 userType 不匹配
  270 + oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setUserType(2)));
  271 + // 测试 userType 不匹配
  272 + oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setClientId("it_client")));
  273 + // 测试 expireTime 不匹配
  274 + oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setExpiresTime(new Date())));
  275 + // 准备参数
  276 + OAuth2AccessTokenPageReqVO reqVO = new OAuth2AccessTokenPageReqVO();
  277 + reqVO.setUserId(10L);
  278 + reqVO.setUserType(1);
  279 + reqVO.setClientId("test");
  280 +
  281 + // 调用
  282 + PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);
  283 + // 断言
  284 + assertEquals(1, pageResult.getTotal());
  285 + assertEquals(1, pageResult.getList().size());
  286 + assertPojoEquals(dbAccessToken, pageResult.getList().get(0));
  287 + }
  288 +
  289 +}
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
@@ -22,3 +22,5 @@ DELETE FROM &quot;system_tenant_package&quot;; @@ -22,3 +22,5 @@ DELETE FROM &quot;system_tenant_package&quot;;
22 DELETE FROM "system_sensitive_word"; 22 DELETE FROM "system_sensitive_word";
23 DELETE FROM "system_oauth2_client"; 23 DELETE FROM "system_oauth2_client";
24 DELETE FROM "system_oauth2_approve"; 24 DELETE FROM "system_oauth2_approve";
  25 +DELETE FROM "system_oauth2_access_token";
  26 +DELETE FROM "system_oauth2_refresh_token";
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
@@ -511,3 +511,39 @@ CREATE TABLE IF NOT EXISTS &quot;system_oauth2_approve&quot; ( @@ -511,3 +511,39 @@ CREATE TABLE IF NOT EXISTS &quot;system_oauth2_approve&quot; (
511 "deleted" bit NOT NULL DEFAULT FALSE, 511 "deleted" bit NOT NULL DEFAULT FALSE,
512 PRIMARY KEY ("id") 512 PRIMARY KEY ("id")
513 ) COMMENT 'OAuth2 批准表'; 513 ) COMMENT 'OAuth2 批准表';
  514 +
  515 +CREATE TABLE IF NOT EXISTS "system_oauth2_access_token" (
  516 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  517 + "user_id" bigint NOT NULL,
  518 + "user_type" tinyint NOT NULL,
  519 + "access_token" varchar NOT NULL,
  520 + "refresh_token" varchar NOT NULL,
  521 + "client_id" varchar NOT NULL,
  522 + "scopes" varchar NOT NULL,
  523 + "approved" bit NOT NULL DEFAULT FALSE,
  524 + "expires_time" datetime NOT NULL,
  525 + "creator" varchar DEFAULT '',
  526 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  527 + "updater" varchar DEFAULT '',
  528 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  529 + "deleted" bit NOT NULL DEFAULT FALSE,
  530 + "tenant_id" bigint NOT NULL,
  531 + PRIMARY KEY ("id")
  532 +) COMMENT 'OAuth2 访问令牌';
  533 +
  534 +CREATE TABLE IF NOT EXISTS "system_oauth2_refresh_token" (
  535 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  536 + "user_id" bigint NOT NULL,
  537 + "user_type" tinyint NOT NULL,
  538 + "refresh_token" varchar NOT NULL,
  539 + "client_id" varchar NOT NULL,
  540 + "scopes" varchar NOT NULL,
  541 + "approved" bit NOT NULL DEFAULT FALSE,
  542 + "expires_time" datetime NOT NULL,
  543 + "creator" varchar DEFAULT '',
  544 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  545 + "updater" varchar DEFAULT '',
  546 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  547 + "deleted" bit NOT NULL DEFAULT FALSE,
  548 + PRIMARY KEY ("id")
  549 +) COMMENT 'OAuth2 刷新令牌';