Commit 97db4586a82d2ce42e2a4ff0ecd47202ccb70212

Authored by YunaiV
1 parent 1f36af8e

完成 OAuth2 的客户端模块

Showing 33 changed files with 498 additions and 91 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
@@ -113,8 +113,7 @@ public class JsonUtils { @@ -113,8 +113,7 @@ public class JsonUtils {
113 } 113 }
114 } 114 }
115 115
116 - // TODO @Li:和上面的风格保持一致哈。parseTree  
117 - public static JsonNode readTree(String text) { 116 + public static JsonNode parseTree(String text) {
118 try { 117 try {
119 return objectMapper.readTree(text); 118 return objectMapper.readTree(text);
120 } catch (IOException e) { 119 } catch (IOException e) {
@@ -123,7 +122,7 @@ public class JsonUtils { @@ -123,7 +122,7 @@ public class JsonUtils {
123 } 122 }
124 } 123 }
125 124
126 - public static JsonNode readTree(byte[] text) { 125 + public static JsonNode parseTree(byte[] text) {
127 try { 126 try {
128 return objectMapper.readTree(text); 127 return objectMapper.readTree(text);
129 } catch (IOException e) { 128 } catch (IOException e) {
@@ -132,4 +131,8 @@ public class JsonUtils { @@ -132,4 +131,8 @@ public class JsonUtils {
132 } 131 }
133 } 132 }
134 133
  134 + public static boolean isJson(String text) {
  135 + return JSONUtil.isJson(text);
  136 + }
  137 +
135 } 138 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
@@ -18,7 +18,7 @@ import cn.iocoder.yudao.module.system.api.logger.LoginLogApi; @@ -18,7 +18,7 @@ import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
18 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; 18 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
19 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 19 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
20 import cn.iocoder.yudao.module.system.api.social.SocialUserApi; 20 import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
21 -import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum; 21 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
22 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 22 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
23 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 23 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
24 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 24 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
@@ -120,7 +120,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -120,7 +120,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
120 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS); 120 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
121 // 创建 Token 令牌 121 // 创建 Token 令牌
122 OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO() 122 OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
123 - .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientIdEnum.DEFAULT.getId())); 123 + .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
124 // 构建返回结果 124 // 构建返回结果
125 return AuthConvert.INSTANCE.convert(accessTokenRespDTO); 125 return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
126 } 126 }
@@ -212,7 +212,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -212,7 +212,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
212 212
213 @Override 213 @Override
214 public AppAuthLoginRespVO refreshToken(String refreshToken) { 214 public AppAuthLoginRespVO refreshToken(String refreshToken) {
215 - OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId()); 215 + OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
216 return AuthConvert.INSTANCE.convert(accessTokenDO); 216 return AuthConvert.INSTANCE.convert(accessTokenDO);
217 } 217 }
218 218
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java
@@ -44,6 +44,6 @@ public interface OAuth2TokenApi { @@ -44,6 +44,6 @@ public interface OAuth2TokenApi {
44 * @param clientId 客户端编号 44 * @param clientId 客户端编号
45 * @return 访问令牌的信息 45 * @return 访问令牌的信息
46 */ 46 */
47 - OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId); 47 + OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);
48 48
49 } 49 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java
@@ -30,6 +30,6 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable { @@ -30,6 +30,6 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable {
30 * 客户端编号 30 * 客户端编号
31 */ 31 */
32 @NotNull(message = "客户端编号不能为空") 32 @NotNull(message = "客户端编号不能为空")
33 - private Long clientId; 33 + private String clientId;
34 34
35 } 35 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -125,5 +125,6 @@ public interface ErrorCodeConstants { @@ -125,5 +125,6 @@ public interface ErrorCodeConstants {
125 125
126 // ========== 系统敏感词 1002020000 ========= 126 // ========== 系统敏感词 1002020000 =========
127 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在"); 127 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
  128 + ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在");
128 129
129 } 130 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.enums.auth;
  2 +
  3 +/**
  4 + * OAuth2.0 客户端的通用枚举
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface OAuth2ClientConstants {
  9 +
  10 + String CLIENT_ID_DEFAULT = "default";
  11 +
  12 +}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java renamed to yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java
@@ -4,14 +4,21 @@ import lombok.AllArgsConstructor; @@ -4,14 +4,21 @@ import lombok.AllArgsConstructor;
4 import lombok.Getter; 4 import lombok.Getter;
5 5
6 /** 6 /**
7 - * OAuth2.0 客户端的编号枚举 7 + * OAuth2 授权类型(模式)的枚举
  8 + *
  9 + * @author 芋道源码
8 */ 10 */
9 @AllArgsConstructor 11 @AllArgsConstructor
10 @Getter 12 @Getter
11 -public enum OAuth2ClientIdEnum { 13 +public enum OAuth2GrantTypeEnum {
12 14
13 - DEFAULT(1L); // 系统默认 15 + PASSWORD("password"), // 密码模式
  16 + AUTHORIZATION_CODE("authorization_code"), // 授权码模式
  17 + IMPLICIT("implicit"), // 简化模式
  18 + CLIENT_CREDENTIALS("client_credentials"), // 客户端模式
  19 + REFRESH_TOKEN("refresh_token"), // 刷新模式
  20 + ;
14 21
15 - private final Long id; 22 + private final String grantType;
16 23
17 } 24 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
@@ -40,7 +40,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi { @@ -40,7 +40,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
40 } 40 }
41 41
42 @Override 42 @Override
43 - public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId) { 43 + public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId) {
44 OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId); 44 OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
45 return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO); 45 return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
46 } 46 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.http 0 → 100644
  1 +### 请求 /login 接口 => 成功
  2 +POST {{baseUrl}}/system/oauth2-client/create
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenentId}}
  6 +
  7 +{
  8 + "id": "1",
  9 + "secret": "admin123",
  10 + "name": "芋道源码",
  11 + "logo": "https://www.iocoder.cn/images/favicon.ico",
  12 + "description": "我是描述",
  13 + "status": 0,
  14 + "accessTokenValiditySeconds": 180,
  15 + "refreshTokenValiditySeconds": 8640,
  16 + "redirectUris": ["https://www.iocoder.cn"],
  17 + "autoApprove": true,
  18 + "authorizedGrantTypes": ["password"],
  19 + "scopes": ["user_info"],
  20 + "authorities": ["system:user:query"],
  21 + "resource_ids": ["1024"],
  22 + "additionalInformation": "{}"
  23 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java
1 package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; 1 package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
2 2
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
3 import io.swagger.annotations.ApiModelProperty; 5 import io.swagger.annotations.ApiModelProperty;
4 import lombok.Data; 6 import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
5 8
  9 +import javax.validation.constraints.AssertTrue;
  10 +import javax.validation.constraints.NotEmpty;
6 import javax.validation.constraints.NotNull; 11 import javax.validation.constraints.NotNull;
7 import java.util.List; 12 import java.util.List;
8 13
@@ -15,7 +20,7 @@ public class OAuth2ClientBaseVO { @@ -15,7 +20,7 @@ public class OAuth2ClientBaseVO {
15 20
16 @ApiModelProperty(value = "客户端编号", required = true) 21 @ApiModelProperty(value = "客户端编号", required = true)
17 @NotNull(message = "客户端编号不能为空") 22 @NotNull(message = "客户端编号不能为空")
18 - private Long id; 23 + private String clientId;
19 24
20 @ApiModelProperty(value = "客户端密钥", required = true) 25 @ApiModelProperty(value = "客户端密钥", required = true)
21 @NotNull(message = "客户端密钥不能为空") 26 @NotNull(message = "客户端密钥不能为空")
@@ -27,6 +32,7 @@ public class OAuth2ClientBaseVO { @@ -27,6 +32,7 @@ public class OAuth2ClientBaseVO {
27 32
28 @ApiModelProperty(value = "应用图标", required = true) 33 @ApiModelProperty(value = "应用图标", required = true)
29 @NotNull(message = "应用图标不能为空") 34 @NotNull(message = "应用图标不能为空")
  35 + @URL(message = "应用图标的地址不正确")
30 private String logo; 36 private String logo;
31 37
32 @ApiModelProperty(value = "应用描述") 38 @ApiModelProperty(value = "应用描述")
@@ -46,6 +52,32 @@ public class OAuth2ClientBaseVO { @@ -46,6 +52,32 @@ public class OAuth2ClientBaseVO {
46 52
47 @ApiModelProperty(value = "可重定向的 URI 地址", required = true) 53 @ApiModelProperty(value = "可重定向的 URI 地址", required = true)
48 @NotNull(message = "可重定向的 URI 地址不能为空") 54 @NotNull(message = "可重定向的 URI 地址不能为空")
49 - private List<String> redirectUris; 55 + private List<@NotEmpty(message = "重定向的 URI 不能为空")
  56 + @URL(message = "重定向的 URI 格式不正确") String> redirectUris;
  57 +
  58 + @ApiModelProperty(value = "是否自动授权", required = true, example = "true")
  59 + @NotNull(message = "是否自动授权不能为空")
  60 + private Boolean autoApprove;
  61 +
  62 + @ApiModelProperty(value = "授权类型", required = true, example = "password", notes = "参见 OAuth2GrantTypeEnum 枚举")
  63 + @NotNull(message = "授权类型不能为空")
  64 + private List<String> authorizedGrantTypes;
  65 +
  66 + @ApiModelProperty(value = "授权范围", example = "user_info")
  67 + private List<String> scopes;
  68 +
  69 + @ApiModelProperty(value = "权限", example = "system:user:query")
  70 + private List<String> authorities;
  71 +
  72 + @ApiModelProperty(value = "资源", example = "1024")
  73 + private List<String> resourceIds;
  74 +
  75 + @ApiModelProperty(value = "附加信息", example = "{yunai: true}")
  76 + private String additionalInformation;
  77 +
  78 + @AssertTrue(message = "附加信息必须是 JSON 格式")
  79 + public boolean isAdditionalInformationJson() {
  80 + return StrUtil.isEmpty(additionalInformation) || JsonUtils.isJson(additionalInformation);
  81 + }
50 82
51 } 83 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java
@@ -14,6 +14,9 @@ import java.util.Date; @@ -14,6 +14,9 @@ import java.util.Date;
14 @ToString(callSuper = true) 14 @ToString(callSuper = true)
15 public class OAuth2ClientRespVO extends OAuth2ClientBaseVO { 15 public class OAuth2ClientRespVO extends OAuth2ClientBaseVO {
16 16
  17 + @ApiModelProperty(value = "编号", required = true)
  18 + private Long id;
  19 +
17 @ApiModelProperty(value = "创建时间", required = true) 20 @ApiModelProperty(value = "创建时间", required = true)
18 private Date createTime; 21 private Date createTime;
19 22
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java
1 package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; 1 package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
4 import lombok.Data; 5 import lombok.Data;
5 import lombok.EqualsAndHashCode; 6 import lombok.EqualsAndHashCode;
6 import lombok.ToString; 7 import lombok.ToString;
7 8
  9 +import javax.validation.constraints.NotNull;
  10 +
8 @ApiModel("管理后台 - OAuth2 客户端更新 Request VO") 11 @ApiModel("管理后台 - OAuth2 客户端更新 Request VO")
9 @Data 12 @Data
10 @EqualsAndHashCode(callSuper = true) 13 @EqualsAndHashCode(callSuper = true)
11 @ToString(callSuper = true) 14 @ToString(callSuper = true)
12 public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO { 15 public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO {
13 16
  17 + @ApiModelProperty(value = "编号", required = true)
  18 + @NotNull(message = "编号不能为空")
  19 + private Long id;
  20 +
14 } 21 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java
@@ -18,6 +18,6 @@ public class OAuth2AccessTokenPageReqVO extends PageParam { @@ -18,6 +18,6 @@ public class OAuth2AccessTokenPageReqVO extends PageParam {
18 private Integer userType; 18 private Integer userType;
19 19
20 @ApiModelProperty(value = "客户端编号", required = true, example = "2") 20 @ApiModelProperty(value = "客户端编号", required = true, example = "2")
21 - private Long clientId; 21 + private String clientId;
22 22
23 } 23 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java
@@ -30,7 +30,7 @@ public class OAuth2AccessTokenRespVO { @@ -30,7 +30,7 @@ public class OAuth2AccessTokenRespVO {
30 private Integer userType; 30 private Integer userType;
31 31
32 @ApiModelProperty(value = "客户端编号", required = true, example = "2") 32 @ApiModelProperty(value = "客户端编号", required = true, example = "2")
33 - private Long clientId; 33 + private String clientId;
34 34
35 @ApiModelProperty(value = "创建时间", required = true) 35 @ApiModelProperty(value = "创建时间", required = true)
36 private Date createTime; 36 private Date createTime;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java
@@ -52,7 +52,7 @@ public class OAuth2AccessTokenDO extends TenantBaseDO { @@ -52,7 +52,7 @@ public class OAuth2AccessTokenDO extends TenantBaseDO {
52 * 52 *
53 * 关联 {@link OAuth2ClientDO#getId()} 53 * 关联 {@link OAuth2ClientDO#getId()}
54 */ 54 */
55 - private Long clientId; 55 + private String clientId;
56 /** 56 /**
57 * 过期时间 57 * 过期时间
58 */ 58 */
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java
@@ -2,39 +2,38 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; @@ -2,39 +2,38 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import com.baomidou.mybatisplus.annotation.IdType; 5 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
6 import com.baomidou.mybatisplus.annotation.TableField; 6 import com.baomidou.mybatisplus.annotation.TableField;
7 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
8 import com.baomidou.mybatisplus.annotation.TableName; 8 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.List; 13 import java.util.List;
15 14
16 /** 15 /**
17 * OAuth2 客户端 DO 16 * OAuth2 客户端 DO
18 * 17 *
19 - * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持:  
20 - * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope  
21 - *  
22 * @author 芋道源码 18 * @author 芋道源码
23 */ 19 */
24 @TableName(value = "system_oauth2_client", autoResultMap = true) 20 @TableName(value = "system_oauth2_client", autoResultMap = true)
25 @Data 21 @Data
26 @EqualsAndHashCode(callSuper = true) 22 @EqualsAndHashCode(callSuper = true)
27 -@Accessors(chain = true)  
28 public class OAuth2ClientDO extends BaseDO { 23 public class OAuth2ClientDO extends BaseDO {
29 24
30 /** 25 /**
31 - * 客户端编号 26 + * 编号,数据库自增
32 * 27 *
33 * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型 28 * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型
34 */ 29 */
35 - @TableId(type = IdType.INPUT) 30 + @TableId
36 private Long id; 31 private Long id;
37 /** 32 /**
  33 + * 客户端编号
  34 + */
  35 + private String clientId;
  36 + /**
38 * 客户端密钥 37 * 客户端密钥
39 */ 38 */
40 private String secret; 39 private String secret;
@@ -69,5 +68,35 @@ public class OAuth2ClientDO extends BaseDO { @@ -69,5 +68,35 @@ public class OAuth2ClientDO extends BaseDO {
69 */ 68 */
70 @TableField(typeHandler = JacksonTypeHandler.class) 69 @TableField(typeHandler = JacksonTypeHandler.class)
71 private List<String> redirectUris; 70 private List<String> redirectUris;
  71 + /**
  72 + * 是否自动授权
  73 + */
  74 + private Boolean autoApprove;
  75 + /**
  76 + * 授权类型(模式)
  77 + *
  78 + * 枚举 {@link OAuth2GrantTypeEnum}
  79 + */
  80 + @TableField(typeHandler = JacksonTypeHandler.class)
  81 + private List<String> authorizedGrantTypes;
  82 + /**
  83 + * 授权范围
  84 + */
  85 + @TableField(typeHandler = JacksonTypeHandler.class)
  86 + private List<String> scopes;
  87 + /**
  88 + * 权限
  89 + */
  90 + @TableField(typeHandler = JacksonTypeHandler.class)
  91 + private List<String> authorities;
  92 + /**
  93 + * 资源
  94 + */
  95 + @TableField(typeHandler = JacksonTypeHandler.class)
  96 + private List<String> resourceIds;
  97 + /**
  98 + * 附加信息,JSON 格式
  99 + */
  100 + private String additionalInformation;
72 101
73 } 102 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import com.baomidou.mybatisplus.annotation.TableName; 5 import com.baomidou.mybatisplus.annotation.TableName;
6 import lombok.Data; 6 import lombok.Data;
7 import lombok.EqualsAndHashCode; 7 import lombok.EqualsAndHashCode;
8 -import lombok.experimental.Accessors;  
9 8
10 import java.util.Date; 9 import java.util.Date;
11 10
@@ -17,7 +16,6 @@ import java.util.Date; @@ -17,7 +16,6 @@ import java.util.Date;
17 @TableName("system_oauth2_code") 16 @TableName("system_oauth2_code")
18 @Data 17 @Data
19 @EqualsAndHashCode(callSuper = true) 18 @EqualsAndHashCode(callSuper = true)
20 -@Accessors(chain = true)  
21 public class OAuth2CodeDO extends BaseDO { 19 public class OAuth2CodeDO extends BaseDO {
22 20
23 /** 21 /**
@@ -43,7 +41,7 @@ public class OAuth2CodeDO extends BaseDO { @@ -43,7 +41,7 @@ public class OAuth2CodeDO extends BaseDO {
43 * 41 *
44 * 关联 {@link OAuth2ClientDO#getId()} 42 * 关联 {@link OAuth2ClientDO#getId()}
45 */ 43 */
46 - private Long clientId; 44 + private String clientId;
47 /** 45 /**
48 * 刷新令牌 46 * 刷新令牌
49 * 47 *
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java
@@ -43,7 +43,7 @@ public class OAuth2RefreshTokenDO extends BaseDO { @@ -43,7 +43,7 @@ public class OAuth2RefreshTokenDO extends BaseDO {
43 * 43 *
44 * 关联 {@link OAuth2ClientDO#getId()} 44 * 关联 {@link OAuth2ClientDO#getId()}
45 */ 45 */
46 - private Long clientId; 46 + private String clientId;
47 /** 47 /**
48 * 过期时间 48 * 过期时间
49 */ 49 */
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java
@@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; 6 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Select;
  10 +
  11 +import java.util.Date;
9 12
10 /** 13 /**
11 * OAuth2 客户端 Mapper 14 * OAuth2 客户端 Mapper
@@ -22,4 +25,11 @@ public interface OAuth2ClientMapper extends BaseMapperX&lt;OAuth2ClientDO&gt; { @@ -22,4 +25,11 @@ public interface OAuth2ClientMapper extends BaseMapperX&lt;OAuth2ClientDO&gt; {
22 .orderByDesc(OAuth2ClientDO::getId)); 25 .orderByDesc(OAuth2ClientDO::getId));
23 } 26 }
24 27
  28 + default OAuth2ClientDO selectByClientId(String clientId) {
  29 + return selectOne(OAuth2ClientDO::getClientId, clientId);
  30 + }
  31 +
  32 + @Select("SELECT COUNT(*) FROM system_oauth2_client WHERE update_time > #{maxUpdateTime}")
  33 + int selectCountByUpdateTimeGt(Date maxUpdateTime);
  34 +
25 } 35 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.consumer.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
  5 +import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link OAuth2ClientRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class OAuth2ClientRefreshConsumer extends AbstractChannelMessageListener<OAuth2ClientRefreshMessage> {
  19 +
  20 + @Resource
  21 + private OAuth2ClientService oauth2ClientService;
  22 +
  23 + @Override
  24 + public void onMessage(OAuth2ClientRefreshMessage message) {
  25 + log.info("[onMessage][收到 OAuth2Client 刷新消息]");
  26 + oauth2ClientService.initLocalCache();
  27 + }
  28 +
  29 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.message.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +/**
  8 + * OAuth 2.0 客户端的数据刷新 Message
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +public class OAuth2ClientRefreshMessage extends AbstractChannelMessage {
  15 +
  16 + @Override
  17 + public String getChannel() {
  18 + return "system.oauth2-client.refresh";
  19 + }
  20 +
  21 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.producer.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
  4 +import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +import javax.annotation.Resource;
  8 +
  9 +/**
  10 + * OAuth 2.0 客户端相关消息的 Producer
  11 + */
  12 +@Component
  13 +public class OAuth2ClientProducer {
  14 +
  15 + @Resource
  16 + private RedisMQTemplate redisMQTemplate;
  17 +
  18 + /**
  19 + * 发送 {@link OAuth2ClientRefreshMessage} 消息
  20 + */
  21 + public void sendOAuth2ClientRefreshMessage() {
  22 + OAuth2ClientRefreshMessage message = new OAuth2ClientRefreshMessage();
  23 + redisMQTemplate.send(message);
  24 + }
  25 +
  26 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; @@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
12 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 12 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
13 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
15 -import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum; 15 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
17 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 17 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
18 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 18 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
@@ -197,7 +197,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -197,7 +197,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
197 197
198 @Override 198 @Override
199 public AuthLoginRespVO refreshToken(String refreshToken) { 199 public AuthLoginRespVO refreshToken(String refreshToken) {
200 - OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId()); 200 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
201 return AuthConvert.INSTANCE.convert(accessTokenDO); 201 return AuthConvert.INSTANCE.convert(accessTokenDO);
202 } 202 }
203 203
@@ -206,7 +206,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -206,7 +206,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
206 createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); 206 createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
207 // 创建访问令牌 207 // 创建访问令牌
208 OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), 208 OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
209 - OAuth2ClientIdEnum.DEFAULT.getId()); 209 + OAuth2ClientConstants.CLIENT_ID_DEFAULT);
210 // 构建返回结果 210 // 构建返回结果
211 return AuthConvert.INSTANCE.convert(accessTokenDO); 211 return AuthConvert.INSTANCE.convert(accessTokenDO);
212 } 212 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java
@@ -18,7 +18,12 @@ import javax.validation.Valid; @@ -18,7 +18,12 @@ import javax.validation.Valid;
18 public interface OAuth2ClientService { 18 public interface OAuth2ClientService {
19 19
20 /** 20 /**
21 - * 创建OAuth2 客户端 21 + * 初始化 OAuth2Client 的本地缓存
  22 + */
  23 + void initLocalCache();
  24 +
  25 + /**
  26 + * 创建 OAuth2 客户端
22 * 27 *
23 * @param createReqVO 创建信息 28 * @param createReqVO 创建信息
24 * @return 编号 29 * @return 编号
@@ -26,21 +31,21 @@ public interface OAuth2ClientService { @@ -26,21 +31,21 @@ public interface OAuth2ClientService {
26 Long createOAuth2Client(@Valid OAuth2ClientCreateReqVO createReqVO); 31 Long createOAuth2Client(@Valid OAuth2ClientCreateReqVO createReqVO);
27 32
28 /** 33 /**
29 - * 更新OAuth2 客户端 34 + * 更新 OAuth2 客户端
30 * 35 *
31 * @param updateReqVO 更新信息 36 * @param updateReqVO 更新信息
32 */ 37 */
33 void updateOAuth2Client(@Valid OAuth2ClientUpdateReqVO updateReqVO); 38 void updateOAuth2Client(@Valid OAuth2ClientUpdateReqVO updateReqVO);
34 39
35 /** 40 /**
36 - * 删除OAuth2 客户端 41 + * 删除 OAuth2 客户端
37 * 42 *
38 * @param id 编号 43 * @param id 编号
39 */ 44 */
40 void deleteOAuth2Client(Long id); 45 void deleteOAuth2Client(Long id);
41 46
42 /** 47 /**
43 - * 获得OAuth2 客户端 48 + * 获得 OAuth2 客户端
44 * 49 *
45 * @param id 编号 50 * @param id 编号
46 * @return OAuth2 客户端 51 * @return OAuth2 客户端
@@ -48,7 +53,7 @@ public interface OAuth2ClientService { @@ -48,7 +53,7 @@ public interface OAuth2ClientService {
48 OAuth2ClientDO getOAuth2Client(Long id); 53 OAuth2ClientDO getOAuth2Client(Long id);
49 54
50 /** 55 /**
51 - * 获得OAuth2 客户端分页 56 + * 获得 OAuth2 客户端分页
52 * 57 *
53 * @param pageReqVO 分页查询 58 * @param pageReqVO 分页查询
54 * @return OAuth2 客户端分页 59 * @return OAuth2 客户端分页
@@ -58,9 +63,9 @@ public interface OAuth2ClientService { @@ -58,9 +63,9 @@ public interface OAuth2ClientService {
58 /** 63 /**
59 * 从缓存中,校验客户端是否合法 64 * 从缓存中,校验客户端是否合法
60 * 65 *
61 - * @param id 客户端编号 66 + * @param clientId 客户端编号
62 * @return 客户端 67 * @return 客户端
63 */ 68 */
64 - OAuth2ClientDO validOAuthClientFromCache(Long id); 69 + OAuth2ClientDO validOAuthClientFromCache(String clientId);
65 70
66 } 71 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java
1 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; 5 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
5 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; 6 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
@@ -7,11 +8,24 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie @@ -7,11 +8,24 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie
7 import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert; 8 import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
8 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
9 import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper; 10 import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
  11 +import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
  12 +import com.google.common.annotations.VisibleForTesting;
  13 +import lombok.Getter;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.scheduling.annotation.Scheduled;
10 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
  17 +import org.springframework.validation.annotation.Validated;
11 18
  19 +import javax.annotation.PostConstruct;
12 import javax.annotation.Resource; 20 import javax.annotation.Resource;
  21 +import java.util.Date;
  22 +import java.util.List;
  23 +import java.util.Map;
13 24
14 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
  28 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_EXISTS;
15 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS; 29 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
16 30
17 /** 31 /**
@@ -20,35 +34,113 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLI @@ -20,35 +34,113 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLI
20 * @author 芋道源码 34 * @author 芋道源码
21 */ 35 */
22 @Service 36 @Service
  37 +@Validated
  38 +@Slf4j
23 public class OAuth2ClientServiceImpl implements OAuth2ClientService { 39 public class OAuth2ClientServiceImpl implements OAuth2ClientService {
24 40
  41 + /**
  42 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  43 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  44 + */
  45 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  46 +
  47 + /**
  48 + * 客户端缓存
  49 + * key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
  50 + *
  51 + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
  52 + */
  53 + @Getter
  54 + private volatile Map<String, OAuth2ClientDO> clientCache;
  55 + /**
  56 + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
  57 + */
  58 + @Getter
  59 + private volatile Date maxUpdateTime;
  60 +
25 @Resource 61 @Resource
26 private OAuth2ClientMapper oauth2ClientMapper; 62 private OAuth2ClientMapper oauth2ClientMapper;
27 63
  64 + @Resource
  65 + private OAuth2ClientProducer oauth2ClientProducer;
  66 +
  67 + /**
  68 + * 初始化 {@link #clientCache} 缓存
  69 + */
  70 + @Override
  71 + @PostConstruct
  72 + public void initLocalCache() {
  73 + // 获取客户端列表,如果有更新
  74 + List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
  75 + if (CollUtil.isEmpty(tenantList)) {
  76 + return;
  77 + }
  78 +
  79 + // 写入缓存
  80 + clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
  81 + maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
  82 + log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
  83 + }
  84 +
  85 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  86 + public void schedulePeriodicRefresh() {
  87 + initLocalCache();
  88 + }
  89 +
  90 + /**
  91 + * 如果客户端发生变化,从数据库中获取最新的全量客户端。
  92 + * 如果未发生变化,则返回空
  93 + *
  94 + * @param maxUpdateTime 当前客户端的最大更新时间
  95 + * @return 客户端列表
  96 + */
  97 + private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(Date maxUpdateTime) {
  98 + // 第一步,判断是否要更新。
  99 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  100 + log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
  101 + } else { // 判断数据库中是否有更新的客户端
  102 + if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
  103 + return null;
  104 + }
  105 + log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
  106 + }
  107 + // 第二步,如果有更新,则从数据库加载所有客户端
  108 + return oauth2ClientMapper.selectList();
  109 + }
  110 +
28 @Override 111 @Override
29 public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) { 112 public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
  113 + validateClientIdExists(null, createReqVO.getClientId());
30 // 插入 114 // 插入
31 - OAuth2ClientDO oAuth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);  
32 - oauth2ClientMapper.insert(oAuth2Client);  
33 - // 返回  
34 - return oAuth2Client.getId(); 115 + OAuth2ClientDO oauth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
  116 + oauth2ClientMapper.insert(oauth2Client);
  117 + // 发送刷新消息
  118 + oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
  119 + return oauth2Client.getId();
35 } 120 }
36 121
37 @Override 122 @Override
38 public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) { 123 public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
39 // 校验存在 124 // 校验存在
40 - this.validateOAuth2ClientExists(updateReqVO.getId()); 125 + validateOAuth2ClientExists(updateReqVO.getId());
  126 + // 校验 Client 未被占用
  127 + validateClientIdExists(updateReqVO.getId(), updateReqVO.getClientId());
  128 +
41 // 更新 129 // 更新
42 OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO); 130 OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
43 oauth2ClientMapper.updateById(updateObj); 131 oauth2ClientMapper.updateById(updateObj);
  132 + // 发送刷新消息
  133 + oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
44 } 134 }
45 135
46 @Override 136 @Override
47 public void deleteOAuth2Client(Long id) { 137 public void deleteOAuth2Client(Long id) {
48 // 校验存在 138 // 校验存在
49 - this.validateOAuth2ClientExists(id); 139 + validateOAuth2ClientExists(id);
50 // 删除 140 // 删除
51 oauth2ClientMapper.deleteById(id); 141 oauth2ClientMapper.deleteById(id);
  142 + // 发送刷新消息
  143 + oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
52 } 144 }
53 145
54 private void validateOAuth2ClientExists(Long id) { 146 private void validateOAuth2ClientExists(Long id) {
@@ -57,6 +149,21 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { @@ -57,6 +149,21 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
57 } 149 }
58 } 150 }
59 151
  152 + @VisibleForTesting
  153 + public void validateClientIdExists(Long id, String clientId) {
  154 + OAuth2ClientDO client = oauth2ClientMapper.selectByClientId(clientId);
  155 + if (client == null) {
  156 + return;
  157 + }
  158 + // 如果 id 为空,说明不用比较是否为相同 id 的客户端
  159 + if (id == null) {
  160 + throw exception(OAUTH2_CLIENT_EXISTS);
  161 + }
  162 + if (!client.getClientId().equals(clientId)) {
  163 + throw exception(OAUTH2_CLIENT_EXISTS);
  164 + }
  165 + }
  166 +
60 @Override 167 @Override
61 public OAuth2ClientDO getOAuth2Client(Long id) { 168 public OAuth2ClientDO getOAuth2Client(Long id) {
62 return oauth2ClientMapper.selectById(id); 169 return oauth2ClientMapper.selectById(id);
@@ -68,10 +175,8 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { @@ -68,10 +175,8 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
68 } 175 }
69 176
70 @Override 177 @Override
71 - public OAuth2ClientDO validOAuthClientFromCache(Long id) {  
72 - return new OAuth2ClientDO().setId(id)  
73 - .setAccessTokenValiditySeconds(60 * 30)  
74 - .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30); 178 + public OAuth2ClientDO validOAuthClientFromCache(String clientId) {
  179 + return clientCache.get(clientId);
75 } 180 }
76 181
77 } 182 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java
@@ -24,7 +24,7 @@ public interface OAuth2TokenService { @@ -24,7 +24,7 @@ public interface OAuth2TokenService {
24 * @param clientId 客户端编号 24 * @param clientId 客户端编号
25 * @return 访问令牌的信息 25 * @return 访问令牌的信息
26 */ 26 */
27 - OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId); 27 + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId);
28 28
29 /** 29 /**
30 * 刷新访问令牌 30 * 刷新访问令牌
@@ -35,7 +35,7 @@ public interface OAuth2TokenService { @@ -35,7 +35,7 @@ public interface OAuth2TokenService {
35 * @param clientId 客户端编号 35 * @param clientId 客户端编号
36 * @return 访问令牌的信息 36 * @return 访问令牌的信息
37 */ 37 */
38 - OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId); 38 + OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId);
39 39
40 /** 40 /**
41 * 获得访问令牌 41 * 获得访问令牌
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java
@@ -45,7 +45,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -45,7 +45,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
45 45
46 @Override 46 @Override
47 @Transactional 47 @Transactional
48 - public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) { 48 + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId) {
49 OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); 49 OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
50 // 创建刷新令牌 50 // 创建刷新令牌
51 OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); 51 OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
@@ -54,7 +54,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -54,7 +54,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
54 } 54 }
55 55
56 @Override 56 @Override
57 - public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) { 57 + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) {
58 // 查询访问令牌 58 // 查询访问令牌
59 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); 59 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
60 if (refreshTokenDO == null) { 60 if (refreshTokenDO == null) {
@@ -134,7 +134,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -134,7 +134,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
134 134
135 private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { 135 private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
136 OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) 136 OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
137 - .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId()) 137 + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getClientId())
138 .setRefreshToken(refreshTokenDO.getRefreshToken()) 138 .setRefreshToken(refreshTokenDO.getRefreshToken())
139 .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); 139 .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
140 accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 140 accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
@@ -146,7 +146,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -146,7 +146,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
146 146
147 private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { 147 private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
148 OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) 148 OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())
149 - .setUserId(userId).setUserType(userType).setClientId(clientDO.getId()) 149 + .setUserId(userId).setUserType(userType).setClientId(clientDO.getClientId())
150 .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds())); 150 .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds()));
151 oauth2RefreshTokenMapper.insert(refreshToken); 151 oauth2RefreshTokenMapper.insert(refreshToken);
152 return refreshToken; 152 return refreshToken;
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
@@ -197,7 +197,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -197,7 +197,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
197 // mock 缓存登录用户到 Redis 197 // mock 缓存登录用户到 Redis
198 OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) 198 OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
199 .setUserType(UserTypeEnum.ADMIN.getValue())); 199 .setUserType(UserTypeEnum.ADMIN.getValue()));
200 - when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(1L))) 200 + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default")))
201 .thenReturn(accessTokenDO); 201 .thenReturn(accessTokenDO);
202 202
203 // 调用, 并断言异常 203 // 调用, 并断言异常
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java
@@ -8,19 +8,23 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie @@ -8,19 +8,23 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie
8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; 8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
9 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
10 import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper; 10 import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
  11 +import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
11 import org.junit.jupiter.api.Disabled; 12 import org.junit.jupiter.api.Disabled;
12 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
  14 +import org.springframework.boot.test.mock.mockito.MockBean;
13 import org.springframework.context.annotation.Import; 15 import org.springframework.context.annotation.Import;
14 16
15 import javax.annotation.Resource; 17 import javax.annotation.Resource;
  18 +import java.util.Map;
16 19
17 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 20 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  21 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 22 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
19 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 23 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
20 -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;  
21 -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 24 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
22 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS; 25 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
23 import static org.junit.jupiter.api.Assertions.*; 26 import static org.junit.jupiter.api.Assertions.*;
  27 +import static org.mockito.Mockito.verify;
24 28
25 /** 29 /**
26 * {@link OAuth2ClientServiceImpl} 的单元测试类 30 * {@link OAuth2ClientServiceImpl} 的单元测试类
@@ -31,40 +35,66 @@ import static org.junit.jupiter.api.Assertions.*; @@ -31,40 +35,66 @@ import static org.junit.jupiter.api.Assertions.*;
31 public class OAuth2ClientServiceImplTest extends BaseDbUnitTest { 35 public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
32 36
33 @Resource 37 @Resource
34 - private OAuth2ClientServiceImpl oAuth2ClientService; 38 + private OAuth2ClientServiceImpl oauth2ClientService;
35 39
36 @Resource 40 @Resource
37 - private OAuth2ClientMapper oAuth2ClientMapper; 41 + private OAuth2ClientMapper oauth2ClientMapper;
  42 +
  43 + @MockBean
  44 + private OAuth2ClientProducer oauth2ClientProducer;
  45 +
  46 + @Test
  47 + public void testInitLocalCache() {
  48 + // mock 数据
  49 + OAuth2ClientDO clientDO1 = randomPojo(OAuth2ClientDO.class);
  50 + oauth2ClientMapper.insert(clientDO1);
  51 + OAuth2ClientDO clientDO2 = randomPojo(OAuth2ClientDO.class);
  52 + oauth2ClientMapper.insert(clientDO2);
  53 +
  54 + // 调用
  55 + oauth2ClientService.initLocalCache();
  56 + // 断言 clientCache 缓存
  57 + Map<String, OAuth2ClientDO> clientCache = oauth2ClientService.getClientCache();
  58 + assertEquals(2, clientCache.size());
  59 + assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
  60 + assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
  61 + // 断言 maxUpdateTime 缓存
  62 + assertEquals(max(clientDO1.getUpdateTime(), clientDO2.getUpdateTime()), oauth2ClientService.getMaxUpdateTime());
  63 + }
38 64
39 @Test 65 @Test
40 public void testCreateOAuth2Client_success() { 66 public void testCreateOAuth2Client_success() {
41 // 准备参数 67 // 准备参数
42 - OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class); 68 + OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class,
  69 + o -> o.setLogo(randomString()));
43 70
44 // 调用 71 // 调用
45 - Long oauth2ClientId = oAuth2ClientService.createOAuth2Client(reqVO); 72 + Long oauth2ClientId = oauth2ClientService.createOAuth2Client(reqVO);
46 // 断言 73 // 断言
47 assertNotNull(oauth2ClientId); 74 assertNotNull(oauth2ClientId);
48 // 校验记录的属性是否正确 75 // 校验记录的属性是否正确
49 - OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(oauth2ClientId); 76 + OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);
50 assertPojoEquals(reqVO, oAuth2Client); 77 assertPojoEquals(reqVO, oAuth2Client);
  78 + verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
51 } 79 }
52 80
53 @Test 81 @Test
54 public void testUpdateOAuth2Client_success() { 82 public void testUpdateOAuth2Client_success() {
55 // mock 数据 83 // mock 数据
56 OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class); 84 OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
57 - oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据 85 + oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
58 // 准备参数 86 // 准备参数
59 OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class, o -> { 87 OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class, o -> {
60 o.setId(dbOAuth2Client.getId()); // 设置更新的 ID 88 o.setId(dbOAuth2Client.getId()); // 设置更新的 ID
  89 + o.setLogo(randomString());
61 }); 90 });
62 91
63 // 调用 92 // 调用
64 - oAuth2ClientService.updateOAuth2Client(reqVO); 93 + oauth2ClientService.updateOAuth2Client(reqVO);
65 // 校验是否更新正确 94 // 校验是否更新正确
66 - OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(reqVO.getId()); // 获取最新的 95 + OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
67 assertPojoEquals(reqVO, oAuth2Client); 96 assertPojoEquals(reqVO, oAuth2Client);
  97 + verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
68 } 98 }
69 99
70 @Test 100 @Test
@@ -73,21 +103,22 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest { @@ -73,21 +103,22 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
73 OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class); 103 OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class);
74 104
75 // 调用, 并断言异常 105 // 调用, 并断言异常
76 - assertServiceException(() -> oAuth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS); 106 + assertServiceException(() -> oauth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);
77 } 107 }
78 108
79 @Test 109 @Test
80 public void testDeleteOAuth2Client_success() { 110 public void testDeleteOAuth2Client_success() {
81 // mock 数据 111 // mock 数据
82 OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class); 112 OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
83 - oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据 113 + oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
84 // 准备参数 114 // 准备参数
85 Long id = dbOAuth2Client.getId(); 115 Long id = dbOAuth2Client.getId();
86 116
87 // 调用 117 // 调用
88 - oAuth2ClientService.deleteOAuth2Client(id);  
89 - // 校验数据不存在了  
90 - assertNull(oAuth2ClientMapper.selectById(id)); 118 + oauth2ClientService.deleteOAuth2Client(id);
  119 + // 校验数据不存在了
  120 + assertNull(oauth2ClientMapper.selectById(id));
  121 + verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
91 } 122 }
92 123
93 @Test 124 @Test
@@ -96,7 +127,7 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest { @@ -96,7 +127,7 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
96 Long id = randomLongId(); 127 Long id = randomLongId();
97 128
98 // 调用, 并断言异常 129 // 调用, 并断言异常
99 - assertServiceException(() -> oAuth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS); 130 + assertServiceException(() -> oauth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);
100 } 131 }
101 132
102 @Test 133 @Test
@@ -107,18 +138,18 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest { @@ -107,18 +138,18 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
107 o.setName("潜龙"); 138 o.setName("潜龙");
108 o.setStatus(CommonStatusEnum.ENABLE.getStatus()); 139 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
109 }); 140 });
110 - oAuth2ClientMapper.insert(dbOAuth2Client); 141 + oauth2ClientMapper.insert(dbOAuth2Client);
111 // 测试 name 不匹配 142 // 测试 name 不匹配
112 - oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰"))); 143 + oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
113 // 测试 status 不匹配 144 // 测试 status 不匹配
114 - oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()))); 145 + oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
115 // 准备参数 146 // 准备参数
116 OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO(); 147 OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
117 reqVO.setName("long"); 148 reqVO.setName("long");
118 reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); 149 reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
119 150
120 // 调用 151 // 调用
121 - PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(reqVO); 152 + PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
122 // 断言 153 // 断言
123 assertEquals(1, pageResult.getTotal()); 154 assertEquals(1, pageResult.getTotal());
124 assertEquals(1, pageResult.getList().size()); 155 assertEquals(1, pageResult.getList().size());
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
@@ -473,6 +473,7 @@ CREATE TABLE IF NOT EXISTS &quot;system_sensitive_word&quot; ( @@ -473,6 +473,7 @@ CREATE TABLE IF NOT EXISTS &quot;system_sensitive_word&quot; (
473 473
474 CREATE TABLE IF NOT EXISTS "system_oauth2_client" ( 474 CREATE TABLE IF NOT EXISTS "system_oauth2_client" (
475 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 475 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  476 + "client_id" varchar NOT NULL,
476 "secret" varchar NOT NULL, 477 "secret" varchar NOT NULL,
477 "name" varchar NOT NULL, 478 "name" varchar NOT NULL,
478 "logo" varchar NOT NULL, 479 "logo" varchar NOT NULL,
@@ -481,6 +482,12 @@ CREATE TABLE IF NOT EXISTS &quot;system_oauth2_client&quot; ( @@ -481,6 +482,12 @@ CREATE TABLE IF NOT EXISTS &quot;system_oauth2_client&quot; (
481 "access_token_validity_seconds" int NOT NULL, 482 "access_token_validity_seconds" int NOT NULL,
482 "refresh_token_validity_seconds" int NOT NULL, 483 "refresh_token_validity_seconds" int NOT NULL,
483 "redirect_uris" varchar NOT NULL, 484 "redirect_uris" varchar NOT NULL,
  485 + "auto_approve" bit NOT NULL DEFAULT FALSE,
  486 + "authorized_grant_types" varchar NOT NULL,
  487 + "scopes" varchar NOT NULL DEFAULT '',
  488 + "authorities" varchar NOT NULL DEFAULT '',
  489 + "resource_ids" varchar NOT NULL DEFAULT '',
  490 + "additional_information" varchar NOT NULL DEFAULT '',
484 "creator" varchar DEFAULT '', 491 "creator" varchar DEFAULT '',
485 "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 492 "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
486 "updater" varchar DEFAULT '', 493 "updater" varchar DEFAULT '',
yudao-ui-admin/src/components/generator/config.js
@@ -499,7 +499,7 @@ export const selectComponents = [ @@ -499,7 +499,7 @@ export const selectComponents = [
499 __slot__: { 499 __slot__: {
500 'list-type': true 500 'list-type': true
501 }, 501 },
502 - action: 'https://jsonplaceholder.typicode.com/posts/', 502 + action: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
503 disabled: false, 503 disabled: false,
504 accept: '', 504 accept: '',
505 name: 'file', 505 name: 'file',
yudao-ui-admin/src/utils/dict.js
@@ -23,6 +23,7 @@ export const DICT_TYPE = { @@ -23,6 +23,7 @@ export const DICT_TYPE = {
23 SYSTEM_SMS_SEND_STATUS: 'system_sms_send_status', 23 SYSTEM_SMS_SEND_STATUS: 'system_sms_send_status',
24 SYSTEM_SMS_RECEIVE_STATUS: 'system_sms_receive_status', 24 SYSTEM_SMS_RECEIVE_STATUS: 'system_sms_receive_status',
25 SYSTEM_ERROR_CODE_TYPE: 'system_error_code_type', 25 SYSTEM_ERROR_CODE_TYPE: 'system_error_code_type',
  26 + SYSTEM_OAUTH2_GRANT_TYPE: 'system_oauth2_grant_type',
26 27
27 // ========== INFRA 模块 ========== 28 // ========== INFRA 模块 ==========
28 INFRA_BOOLEAN_STRING: 'infra_boolean_string', 29 INFRA_BOOLEAN_STRING: 'infra_boolean_string',
yudao-ui-admin/src/views/system/oauth2/client/index.vue
@@ -29,19 +29,32 @@ @@ -29,19 +29,32 @@
29 29
30 <!-- 列表 --> 30 <!-- 列表 -->
31 <el-table v-loading="loading" :data="list"> 31 <el-table v-loading="loading" :data="list">
32 - <el-table-column label="客户端编号" align="center" prop="id" /> 32 + <el-table-column label="客户端编号" align="center" prop="clientId" />
33 <el-table-column label="客户端密钥" align="center" prop="secret" /> 33 <el-table-column label="客户端密钥" align="center" prop="secret" />
34 <el-table-column label="应用名" align="center" prop="name" /> 34 <el-table-column label="应用名" align="center" prop="name" />
35 - <el-table-column label="应用图标" align="center" prop="logo" />  
36 - <el-table-column label="应用描述" align="center" prop="description" /> 35 + <el-table-column label="应用图标" align="center" prop="logo">
  36 + <template slot-scope="scope">
  37 + <img width="40px" height="40px" :src="scope.row.logo">
  38 + </template>
  39 + </el-table-column>
37 <el-table-column label="状态" align="center" prop="status"> 40 <el-table-column label="状态" align="center" prop="status">
38 <template slot-scope="scope"> 41 <template slot-scope="scope">
39 <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> 42 <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
40 </template> 43 </template>
41 </el-table-column> 44 </el-table-column>
42 - <el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds" />  
43 - <el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds" />  
44 - <el-table-column label="可重定向的 URI 地址" align="center" prop="redirectUris" /> 45 + <el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds">
  46 + <template slot-scope="scope">{{ scope.row.accessTokenValiditySeconds }} 秒</template>
  47 + </el-table-column>
  48 + <el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds">
  49 + <template slot-scope="scope">{{ scope.row.refreshTokenValiditySeconds }} 秒</template>
  50 + </el-table-column>
  51 + <el-table-column label="授权类型" align="center" prop="authorizedGrantTypes">
  52 + <template slot-scope="scope">
  53 + <el-tag :disable-transitions="true" v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes" :index="index">
  54 + {{ authorizedGrantType }}
  55 + </el-tag>
  56 + </template>
  57 + </el-table-column>
45 <el-table-column label="创建时间" align="center" prop="createTime" width="180"> 58 <el-table-column label="创建时间" align="center" prop="createTime" width="180">
46 <template slot-scope="scope"> 59 <template slot-scope="scope">
47 <span>{{ parseTime(scope.row.createTime) }}</span> 60 <span>{{ parseTime(scope.row.createTime) }}</span>
@@ -63,6 +76,9 @@ @@ -63,6 +76,9 @@
63 <!-- 对话框(添加 / 修改) --> 76 <!-- 对话框(添加 / 修改) -->
64 <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body> 77 <el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
65 <el-form ref="form" :model="form" :rules="rules" label-width="160px"> 78 <el-form ref="form" :model="form" :rules="rules" label-width="160px">
  79 + <el-form-item label="客户端编号" prop="secret">
  80 + <el-input v-model="form.clientId" placeholder="请输入客户端编号" />
  81 + </el-form-item>
66 <el-form-item label="客户端密钥" prop="secret"> 82 <el-form-item label="客户端密钥" prop="secret">
67 <el-input v-model="form.secret" placeholder="请输入客户端密钥" /> 83 <el-input v-model="form.secret" placeholder="请输入客户端密钥" />
68 </el-form-item> 84 </el-form-item>
@@ -70,8 +86,7 @@ @@ -70,8 +86,7 @@
70 <el-input v-model="form.name" placeholder="请输入应用名" /> 86 <el-input v-model="form.name" placeholder="请输入应用名" />
71 </el-form-item> 87 </el-form-item>
72 <el-form-item label="应用图标"> 88 <el-form-item label="应用图标">
73 -<!-- <imageUpload v-model="form.logo" :limit="1"/>-->  
74 - <file-upload v-model="form.logo" :limit="1"/> 89 + <imageUpload v-model="form.logo" :limit="1"/>
75 </el-form-item> 90 </el-form-item>
76 <el-form-item label="应用描述"> 91 <el-form-item label="应用描述">
77 <el-input type="textarea" v-model="form.description" placeholder="请输入应用名" /> 92 <el-input type="textarea" v-model="form.description" placeholder="请输入应用名" />
@@ -89,7 +104,39 @@ @@ -89,7 +104,39 @@
89 <el-input-number v-model="form.refreshTokenValiditySeconds" placeholder="单位:秒" /> 104 <el-input-number v-model="form.refreshTokenValiditySeconds" placeholder="单位:秒" />
90 </el-form-item> 105 </el-form-item>
91 <el-form-item label="可重定向的 URI 地址" prop="redirectUris"> 106 <el-form-item label="可重定向的 URI 地址" prop="redirectUris">
92 - <el-input v-model="form.redirectUris" placeholder="请输入可重定向的 URI 地址" /> 107 + <el-select v-model="form.redirectUris" multiple filterable allow-create placeholder="请输入可重定向的 URI 地址" style="width: 500px" >
  108 + <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/>
  109 + </el-select>
  110 + </el-form-item>
  111 + <el-form-item label="是否自动授权" prop="autoApprove">
  112 + <el-radio-group v-model="form.autoApprove">
  113 + <el-radio :key="true" :label="true">自动登录</el-radio>
  114 + <el-radio :key="false" :label="false">手动登录</el-radio>
  115 + </el-radio-group>
  116 + </el-form-item>
  117 + <el-form-item label="授权类型" prop="authorizedGrantTypes">
  118 + <el-select v-model="form.authorizedGrantTypes" multiple filterable placeholder="请输入授权类型" style="width: 500px" >
  119 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)"
  120 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  121 + </el-select>
  122 + </el-form-item>
  123 + <el-form-item label="授权范围" prop="scopes">
  124 + <el-select v-model="form.scopes" multiple filterable allow-create placeholder="请输入授权范围" style="width: 500px" >
  125 + <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/>
  126 + </el-select>
  127 + </el-form-item>
  128 + <el-form-item label="权限" prop="authorities">
  129 + <el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" >
  130 + <el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/>
  131 + </el-select>
  132 + </el-form-item>
  133 + <el-form-item label="资源" prop="resourceIds">
  134 + <el-select v-model="form.resourceIds" multiple filterable allow-create placeholder="请输入资源" style="width: 500px" >
  135 + <el-option v-for="resourceId in form.resourceIds" :key="resourceId" :label="resourceId" :value="resourceId"/>
  136 + </el-select>
  137 + </el-form-item>
  138 + <el-form-item label="附加信息" prop="additionalInformation">
  139 + <el-input type="textarea" v-model="form.additionalInformation" placeholder="请输入附加信息,JSON 格式数据" />
93 </el-form-item> 140 </el-form-item>
94 </el-form> 141 </el-form>
95 <div slot="footer" class="dialog-footer"> 142 <div slot="footer" class="dialog-footer">
@@ -141,6 +188,7 @@ export default { @@ -141,6 +188,7 @@ export default {
141 form: {}, 188 form: {},
142 // 表单校验 189 // 表单校验
143 rules: { 190 rules: {
  191 + clientId: [{ required: true, message: "客户端编号不能为空", trigger: "blur" }],
144 secret: [{ required: true, message: "客户端密钥不能为空", trigger: "blur" }], 192 secret: [{ required: true, message: "客户端密钥不能为空", trigger: "blur" }],
145 name: [{ required: true, message: "应用名不能为空", trigger: "blur" }], 193 name: [{ required: true, message: "应用名不能为空", trigger: "blur" }],
146 logo: [{ required: true, message: "应用图标不能为空", trigger: "blur" }], 194 logo: [{ required: true, message: "应用图标不能为空", trigger: "blur" }],
@@ -148,6 +196,8 @@ export default { @@ -148,6 +196,8 @@ export default {
148 accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }], 196 accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }],
149 refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }], 197 refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }],
150 redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }], 198 redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }],
  199 + autoApprove: [{ required: true, message: "是否自动授权不能为空", trigger: "blur" }],
  200 + authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }],
151 } 201 }
152 }; 202 };
153 }, 203 },
@@ -176,14 +226,21 @@ export default { @@ -176,14 +226,21 @@ export default {
176 reset() { 226 reset() {
177 this.form = { 227 this.form = {
178 id: undefined, 228 id: undefined,
  229 + clientId: undefined,
179 secret: undefined, 230 secret: undefined,
180 name: undefined, 231 name: undefined,
181 logo: undefined, 232 logo: undefined,
182 description: undefined, 233 description: undefined,
183 status: CommonStatusEnum.ENABLE, 234 status: CommonStatusEnum.ENABLE,
184 - accessTokenValiditySeconds: undefined,  
185 - refreshTokenValiditySeconds: undefined,  
186 - redirectUris: undefined, 235 + accessTokenValiditySeconds: 30 * 60,
  236 + refreshTokenValiditySeconds: 30 * 24 * 60,
  237 + redirectUris: [],
  238 + autoApprove: true,
  239 + authorizedGrantTypes: [],
  240 + scopes: [],
  241 + authorities: [],
  242 + resourceIds: [],
  243 + additionalInformation: undefined,
187 }; 244 };
188 this.resetForm("form"); 245 this.resetForm("form");
189 }, 246 },
@@ -239,7 +296,7 @@ export default { @@ -239,7 +296,7 @@ export default {
239 /** 删除按钮操作 */ 296 /** 删除按钮操作 */
240 handleDelete(row) { 297 handleDelete(row) {
241 const id = row.id; 298 const id = row.id;
242 - this.$modal.confirm('是否确认删除 OAuth2 客户端编号为"' + id + '"的数据项?').then(function() { 299 + this.$modal.confirm('是否确认删除客户端编号为"' + row.clientId + '"的数据项?').then(function() {
243 return deleteOAuth2Client(id); 300 return deleteOAuth2Client(id);
244 }).then(() => { 301 }).then(() => {
245 this.getList(); 302 this.getList();