Explorar o código

!379 合并 客户端授权功能

疯狂的狮子Li hai 1 ano
pai
achega
b0909dbe3d
Modificáronse 36 ficheiros con 1999 adicións e 318 borrados
  1. 23 66
      ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
  2. 40 1
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java
  3. 45 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java
  4. 22 204
      ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
  5. 109 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
  6. 130 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
  7. 109 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
  8. 88 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
  9. 1 0
      ruoyi-admin/src/main/resources/application.yml
  10. 3 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  11. 3 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  12. 3 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  13. 62 4
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
  14. 7 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java
  15. 7 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java
  16. 7 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java
  17. 7 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java
  18. 17 5
      ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
  19. 7 27
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
  20. 115 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java
  21. 77 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java
  22. 80 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java
  23. 90 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java
  24. 15 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java
  25. 60 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
  26. 144 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
  27. 5 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
  28. 7 0
      ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml
  29. 66 3
      script/sql/oracle/oracle_ry_vue_5.X.sql
  30. 66 4
      script/sql/postgres/postgres_ry_vue_5.X.sql
  31. 50 4
      script/sql/ry_vue_5.X.sql
  32. 167 0
      script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
  33. 70 0
      script/sql/update/oracle/update_5.0-5.1.sql
  34. 70 0
      script/sql/update/postgres/update_5.0-5.1.sql
  35. 174 0
      script/sql/update/sqlserver/update_5.0-5.1.sql
  36. 53 0
      script/sql/update/update_5.0-5.1.sql

+ 23 - 66
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java

@@ -4,33 +4,35 @@ import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import jakarta.servlet.http.HttpServletRequest;
-import jakarta.validation.constraints.NotBlank;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import me.zhyd.oauth.model.AuthCallback;
 import me.zhyd.oauth.model.AuthResponse;
 import me.zhyd.oauth.model.AuthUser;
 import me.zhyd.oauth.request.AuthRequest;
 import me.zhyd.oauth.utils.AuthStateUtils;
 import org.dromara.common.core.domain.R;
-import org.dromara.common.core.domain.model.EmailLoginBody;
 import org.dromara.common.core.domain.model.LoginBody;
 import org.dromara.common.core.domain.model.RegisterBody;
-import org.dromara.common.core.domain.model.SmsLoginBody;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.MessageUtils;
 import org.dromara.common.core.utils.StreamUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
 import org.dromara.common.social.config.properties.SocialProperties;
 import org.dromara.common.social.utils.SocialUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysClient;
 import org.dromara.system.domain.bo.SysTenantBo;
 import org.dromara.system.domain.vo.SysTenantVo;
-import org.dromara.system.service.ISysSocialService;
+import org.dromara.system.service.ISysClientService;
 import org.dromara.system.service.ISysConfigService;
+import org.dromara.system.service.ISysSocialService;
 import org.dromara.system.service.ISysTenantService;
 import org.dromara.web.domain.vo.LoginTenantVo;
 import org.dromara.web.domain.vo.LoginVo;
 import org.dromara.web.domain.vo.TenantListVo;
+import org.dromara.web.service.IAuthStrategy;
 import org.dromara.web.service.SysLoginService;
 import org.dromara.web.service.SysRegisterService;
 import org.springframework.validation.annotation.Validated;
@@ -44,6 +46,7 @@ import java.util.List;
  *
  * @author Lion Li
  */
+@Slf4j
 @SaIgnore
 @Validated
 @RequiredArgsConstructor
@@ -57,78 +60,32 @@ public class AuthController {
     private final ISysConfigService configService;
     private final ISysTenantService tenantService;
     private final ISysSocialService socialUserService;
+    private final ISysClientService clientService;
 
 
     /**
      * 登录方法
      *
-     * @param body 登录信息
+     * @param loginBody 登录信息
      * @return 结果
      */
     @PostMapping("/login")
-    public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
-        LoginVo loginVo = new LoginVo();
-        // 生成令牌
-        String token = loginService.login(
-            body.getTenantId(),
-            body.getUsername(), body.getPassword(),
-            body.getCode(), body.getUuid());
-        loginVo.setToken(token);
-        return R.ok(loginVo);
-    }
-
-    /**
-     * 短信登录
-     *
-     * @param body 登录信息
-     * @return 结果
-     */
-    @PostMapping("/smsLogin")
-    public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
-        LoginVo loginVo = new LoginVo();
-        // 生成令牌
-        String token = loginService.smsLogin(
-            body.getTenantId(),
-            body.getPhonenumber(),
-            body.getSmsCode());
-        loginVo.setToken(token);
-        return R.ok(loginVo);
-    }
-
-    /**
-     * 邮件登录
-     *
-     * @param body 登录信息
-     * @return 结果
-     */
-    @PostMapping("/emailLogin")
-    public R<LoginVo> emailLogin(@Validated @RequestBody EmailLoginBody body) {
-        LoginVo loginVo = new LoginVo();
-        // 生成令牌
-        String token = loginService.emailLogin(
-            body.getTenantId(),
-            body.getEmail(),
-            body.getEmailCode());
-        loginVo.setToken(token);
-        return R.ok(loginVo);
-    }
-
-    /**
-     * 小程序登录(示例)
-     *
-     * @param xcxCode 小程序code
-     * @return 结果
-     */
-    @PostMapping("/xcxLogin")
-    public R<LoginVo> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
-        LoginVo loginVo = new LoginVo();
-        // 生成令牌
-        String token = loginService.xcxLogin(xcxCode);
-        loginVo.setToken(token);
-        return R.ok(loginVo);
+    public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) {
+        // 授权类型和客户端id
+        String clientId = loginBody.getClientId();
+        String grantType = loginBody.getGrantType();
+        SysClient client = clientService.queryByClientId(clientId);
+        // 查询不到 client 或 client 内不包含 grantType
+        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
+            log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
+            return R.fail(MessageUtils.message("auth.grant.type.error"));
+        }
+        // 校验租户
+        loginService.checkTenant(loginBody.getTenantId());
+        // 登录
+        return R.ok(IAuthStrategy.login(loginBody, client));
     }
 
-
     /**
      * 认证授权
      *

+ 40 - 1
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java

@@ -1,5 +1,6 @@
 package org.dromara.web.domain.vo;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 /**
@@ -10,6 +11,44 @@ import lombok.Data;
 @Data
 public class LoginVo {
 
-    private String token;
+    /**
+     * 授权令牌
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+
+    /**
+     * 刷新令牌
+     */
+    @JsonProperty("refresh_token")
+    private String refreshToken;
+
+    /**
+     * 授权令牌 access_token 的有效期
+     */
+    @JsonProperty("expire_in")
+    private Long expireIn;
+
+    /**
+     * 刷新令牌 refresh_token 的有效期
+     */
+    @JsonProperty("refresh_expire_in")
+    private Long refreshExpireIn;
+
+    /**
+     * 应用id
+     */
+    @JsonProperty("client_id")
+    private String clientId;
+
+    /**
+     * 令牌权限
+     */
+    private String scope;
+
+    /**
+     * 用户 openid
+     */
+    private String openid;
 
 }

+ 45 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java

@@ -0,0 +1,45 @@
+package org.dromara.web.service;
+
+
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.system.domain.SysClient;
+import org.dromara.web.domain.vo.LoginVo;
+
+/**
+ * 授权策略
+ *
+ * @author Michelle.Chung
+ */
+public interface IAuthStrategy {
+
+    String BASE_NAME = "AuthStrategy";
+
+    /**
+     * 登录
+     */
+    static LoginVo login(LoginBody loginBody, SysClient client) {
+        // 授权类型和客户端id
+        String clientId = loginBody.getClientId();
+        String grantType = loginBody.getGrantType();
+        String beanName = grantType + BASE_NAME;
+        if (!SpringUtils.containsBean(beanName)) {
+            throw new ServiceException("授权类型不正确!");
+        }
+        IAuthStrategy instance = SpringUtils.getBean(beanName);
+        instance.validate(loginBody);
+        return instance.login(clientId, loginBody, client);
+    }
+
+    /**
+     * 参数校验
+     */
+    void validate(LoginBody loginBody);
+
+    /**
+     * 登录
+     */
+    LoginVo login(String clientId, LoginBody loginBody, SysClient client);
+
+}

+ 22 - 204
ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java

@@ -1,7 +1,7 @@
 package org.dromara.web.service;
 
 import cn.dev33.satoken.exception.NotLoginException;
-import cn.dev33.satoken.secure.BCrypt;
+import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -16,13 +16,10 @@ import org.dromara.common.core.constant.TenantConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.dto.RoleDTO;
 import org.dromara.common.core.domain.model.LoginUser;
-import org.dromara.common.core.domain.model.XcxLoginUser;
 import org.dromara.common.core.enums.DeviceType;
 import org.dromara.common.core.enums.LoginType;
 import org.dromara.common.core.enums.TenantStatus;
 import org.dromara.common.core.enums.UserStatus;
-import org.dromara.common.core.exception.user.CaptchaException;
-import org.dromara.common.core.exception.user.CaptchaExpireException;
 import org.dromara.common.core.exception.user.UserException;
 import org.dromara.common.core.utils.*;
 import org.dromara.common.log.event.LogininforEvent;
@@ -30,15 +27,14 @@ import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.tenant.exception.TenantException;
 import org.dromara.common.tenant.helper.TenantHelper;
-import org.dromara.common.web.config.properties.CaptchaProperties;
 import org.dromara.system.domain.SysUser;
 import org.dromara.system.domain.bo.SysSocialBo;
 import org.dromara.system.domain.vo.SysSocialVo;
 import org.dromara.system.domain.vo.SysTenantVo;
 import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.mapper.SysUserMapper;
-import org.dromara.system.service.ISysSocialService;
 import org.dromara.system.service.ISysPermissionService;
+import org.dromara.system.service.ISysSocialService;
 import org.dromara.system.service.ISysTenantService;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
@@ -59,108 +55,16 @@ import java.util.function.Supplier;
 @Service
 public class SysLoginService {
 
-    private final SysUserMapper userMapper;
-    private final ISysSocialService sysSocialService;
-    private final CaptchaProperties captchaProperties;
-    private final ISysPermissionService permissionService;
-    private final ISysTenantService tenantService;
-
     @Value("${user.password.maxRetryCount}")
     private Integer maxRetryCount;
 
     @Value("${user.password.lockTime}")
     private Integer lockTime;
 
-    /**
-     * 登录验证
-     *
-     * @param username 用户名
-     * @param password 密码
-     * @param code     验证码
-     * @param uuid     唯一标识
-     * @return 结果
-     */
-    public String login(String tenantId, String username, String password, String code, String uuid) {
-        boolean captchaEnabled = captchaProperties.getEnable();
-        // 验证码开关
-        if (captchaEnabled) {
-            validateCaptcha(tenantId, username, code, uuid);
-        }
-        // 校验租户
-        checkTenant(tenantId);
-
-        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
-        SysUserVo user = loadUserByUsername(tenantId, username);
-        checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
-        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
-        LoginUser loginUser = buildLoginUser(user);
-        // 生成token
-        LoginHelper.loginByDevice(loginUser, DeviceType.PC);
-
-        recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
-        recordLoginInfo(user.getUserId());
-        return StpUtil.getTokenValue();
-    }
-
-    public String smsLogin(String tenantId, String phonenumber, String smsCode) {
-        // 校验租户
-        checkTenant(tenantId);
-        // 通过手机号查找用户
-        SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
-
-        checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
-        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
-        LoginUser loginUser = buildLoginUser(user);
-        // 生成token
-        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
-
-        recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
-        recordLoginInfo(user.getUserId());
-        return StpUtil.getTokenValue();
-    }
-
-    public String emailLogin(String tenantId, String email, String emailCode) {
-        // 校验租户
-        checkTenant(tenantId);
-        // 通过邮箱查找用户
-        SysUserVo user = loadUserByEmail(tenantId, email);
-
-        checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
-        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
-        LoginUser loginUser = buildLoginUser(user);
-        // 生成token
-        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
-
-        recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
-        recordLoginInfo(user.getUserId());
-        return StpUtil.getTokenValue();
-    }
-
-
-    public String xcxLogin(String xcxCode) {
-        // xcxCode 为 小程序调用 wx.login 授权后获取
-        // todo 以下自行实现
-        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
-        String openid = "";
-        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
-        SysUserVo user = loadUserByOpenid(openid);
-        // 校验租户
-        checkTenant(user.getTenantId());
-
-        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
-        XcxLoginUser loginUser = new XcxLoginUser();
-        loginUser.setTenantId(user.getTenantId());
-        loginUser.setUserId(user.getUserId());
-        loginUser.setUsername(user.getUserName());
-        loginUser.setUserType(user.getUserType());
-        loginUser.setOpenid(openid);
-        // 生成token
-        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
-
-        recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
-        recordLoginInfo(user.getUserId());
-        return StpUtil.getTokenValue();
-    }
+    private final ISysTenantService tenantService;
+    private final ISysPermissionService permissionService;
+    private final ISysSocialService sysSocialService;
+    private final SysUserMapper userMapper;
 
     /**
      * 社交登录
@@ -214,7 +118,10 @@ public class SysLoginService {
     private R<String> loginAndRecord(String tenantId, String userName, AuthUser authUser) {
         checkTenant(tenantId);
         SysUserVo user = loadUserByUsername(tenantId, userName);
-        LoginHelper.loginByDevice(buildLoginUser(user), DeviceType.SOCIAL);
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(DeviceType.PC.getDevice());
+        // 生成token
+        LoginHelper.login(buildLoginUser(user), model);
         recordLogininfor(user.getTenantId(), userName, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
         recordLoginInfo(user.getUserId());
         return R.ok(StpUtil.getTokenValue());
@@ -244,7 +151,7 @@ public class SysLoginService {
      * @param status   状态
      * @param message  消息内容
      */
-    private void recordLogininfor(String tenantId, String username, String status, String message) {
+    public void recordLogininfor(String tenantId, String username, String status, String message) {
         LogininforEvent logininforEvent = new LogininforEvent();
         logininforEvent.setTenantId(tenantId);
         logininforEvent.setUsername(username);
@@ -254,56 +161,12 @@ public class SysLoginService {
         SpringUtils.context().publishEvent(logininforEvent);
     }
 
-    /**
-     * 校验短信验证码
-     */
-    private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
-        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
-        if (StringUtils.isBlank(code)) {
-            recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
-            throw new CaptchaExpireException();
-        }
-        return code.equals(smsCode);
-    }
-
-    /**
-     * 校验邮箱验证码
-     */
-    private boolean validateEmailCode(String tenantId, String email, String emailCode) {
-        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
-        if (StringUtils.isBlank(code)) {
-            recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
-            throw new CaptchaExpireException();
-        }
-        return code.equals(emailCode);
-    }
-
-    /**
-     * 校验验证码
-     *
-     * @param username 用户名
-     * @param code     验证码
-     * @param uuid     唯一标识
-     */
-    public void validateCaptcha(String tenantId, String username, String code, String uuid) {
-        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
-        String captcha = RedisUtils.getCacheObject(verifyKey);
-        RedisUtils.deleteObject(verifyKey);
-        if (captcha == null) {
-            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
-            throw new CaptchaExpireException();
-        }
-        if (!code.equalsIgnoreCase(captcha)) {
-            recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
-            throw new CaptchaException();
-        }
-    }
 
     private SysUserVo loadUserByUsername(String tenantId, String username) {
         SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
-            .select(SysUser::getUserName, SysUser::getStatus)
-            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
-            .eq(SysUser::getUserName, username));
+                .select(SysUser::getUserName, SysUser::getStatus)
+                .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+                .eq(SysUser::getUserName, username));
         if (ObjectUtil.isNull(user)) {
             log.info("登录用户:{} 不存在.", username);
             throw new UserException("user.not.exists", username);
@@ -317,60 +180,10 @@ public class SysLoginService {
         return userMapper.selectUserByUserName(username);
     }
 
-    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
-        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
-            .select(SysUser::getPhonenumber, SysUser::getStatus)
-            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
-            .eq(SysUser::getPhonenumber, phonenumber));
-        if (ObjectUtil.isNull(user)) {
-            log.info("登录用户:{} 不存在.", phonenumber);
-            throw new UserException("user.not.exists", phonenumber);
-        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
-            log.info("登录用户:{} 已被停用.", phonenumber);
-            throw new UserException("user.blocked", phonenumber);
-        }
-        if (TenantHelper.isEnable()) {
-            return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId);
-        }
-        return userMapper.selectUserByPhonenumber(phonenumber);
-    }
-
-    private SysUserVo loadUserByEmail(String tenantId, String email) {
-        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
-            .select(SysUser::getEmail, SysUser::getStatus)
-            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
-            .eq(SysUser::getEmail, email));
-        if (ObjectUtil.isNull(user)) {
-            log.info("登录用户:{} 不存在.", email);
-            throw new UserException("user.not.exists", email);
-        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
-            log.info("登录用户:{} 已被停用.", email);
-            throw new UserException("user.blocked", email);
-        }
-        if (TenantHelper.isEnable()) {
-            return userMapper.selectTenantUserByEmail(email, tenantId);
-        }
-        return userMapper.selectUserByEmail(email);
-    }
-
-    private SysUserVo loadUserByOpenid(String openid) {
-        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
-        // todo 自行实现 userService.selectUserByOpenid(openid);
-        SysUserVo user = new SysUserVo();
-        if (ObjectUtil.isNull(user)) {
-            log.info("登录用户:{} 不存在.", openid);
-            // todo 用户不存在 业务逻辑自行实现
-        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
-            log.info("登录用户:{} 已被停用.", openid);
-            // todo 用户已被停用 业务逻辑自行实现
-        }
-        return user;
-    }
-
     /**
      * 构建登录用户
      */
-    private LoginUser buildLoginUser(SysUserVo user) {
+    public LoginUser buildLoginUser(SysUserVo user) {
         LoginUser loginUser = new LoginUser();
         loginUser.setTenantId(user.getTenantId());
         loginUser.setUserId(user.getUserId());
@@ -402,7 +215,7 @@ public class SysLoginService {
     /**
      * 登录校验
      */
-    private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
+    public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
         String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
         String loginFail = Constants.LOGIN_FAIL;
 
@@ -433,7 +246,12 @@ public class SysLoginService {
         RedisUtils.deleteObject(errorKey);
     }
 
-    private void checkTenant(String tenantId) {
+    /**
+     * 校验租户
+     *
+     * @param tenantId 租户ID
+     */
+    public void checkTenant(String tenantId) {
         if (!TenantHelper.isEnable()) {
             return;
         }

+ 109 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java

@@ -0,0 +1,109 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.auth.EmailGroup;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 邮件认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("email" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class EmailAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public void validate(LoginBody loginBody) {
+        ValidatorUtils.validate(loginBody, EmailGroup.class);
+    }
+
+    @Override
+    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
+        String tenantId = loginBody.getTenantId();
+        String email = loginBody.getEmail();
+        String emailCode = loginBody.getEmailCode();
+
+        // 通过邮箱查找用户
+        SysUserVo user = loadUserByEmail(tenantId, email);
+
+        loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+        LoginUser loginUser = loginService.buildLoginUser(user);
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        loginService.recordLoginInfo(user.getUserId());
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        return loginVo;
+    }
+
+    /**
+     * 校验邮箱验证码
+     */
+    private boolean validateEmailCode(String tenantId, String email, String emailCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(emailCode);
+    }
+
+    private SysUserVo loadUserByEmail(String tenantId, String email) {
+        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+            .select(SysUser::getEmail, SysUser::getStatus)
+            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+            .eq(SysUser::getEmail, email));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", email);
+            throw new UserException("user.not.exists", email);
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", email);
+            throw new UserException("user.blocked", email);
+        }
+        if (TenantHelper.isEnable()) {
+            return userMapper.selectTenantUserByEmail(email, tenantId);
+        }
+        return userMapper.selectUserByEmail(email);
+    }
+
+}

+ 130 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java

@@ -0,0 +1,130 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.secure.BCrypt;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.exception.user.CaptchaException;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.auth.PasswordGroup;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 密码认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("password" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class PasswordAuthStrategy implements IAuthStrategy {
+
+    private final CaptchaProperties captchaProperties;
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public void validate(LoginBody loginBody) {
+        ValidatorUtils.validate(loginBody, PasswordGroup.class);
+    }
+
+    @Override
+    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
+        String tenantId = loginBody.getTenantId();
+        String username = loginBody.getUsername();
+        String password = loginBody.getPassword();
+        String code = loginBody.getCode();
+        String uuid = loginBody.getUuid();
+
+        boolean captchaEnabled = captchaProperties.getEnable();
+        // 验证码开关
+        if (captchaEnabled) {
+            validateCaptcha(tenantId, username, code, uuid);
+        }
+
+        SysUserVo user = loadUserByUsername(tenantId, username);
+        loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        LoginUser loginUser = loginService.buildLoginUser(user);
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginService.recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        loginService.recordLoginInfo(user.getUserId());
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        return loginVo;
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param username 用户名
+     * @param code     验证码
+     * @param uuid     唯一标识
+     */
+    private void validateCaptcha(String tenantId, String username, String code, String uuid) {
+        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
+        String captcha = RedisUtils.getCacheObject(verifyKey);
+        RedisUtils.deleteObject(verifyKey);
+        if (captcha == null) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha)) {
+            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+            throw new CaptchaException();
+        }
+    }
+
+    private SysUserVo loadUserByUsername(String tenantId, String username) {
+        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+            .select(SysUser::getUserName, SysUser::getStatus)
+            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+            .eq(SysUser::getUserName, username));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", username);
+            throw new UserException("user.not.exists", username);
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new UserException("user.blocked", username);
+        }
+        if (TenantHelper.isEnable()) {
+            return userMapper.selectTenantUserByUserName(username, tenantId);
+        }
+        return userMapper.selectUserByUserName(username);
+    }
+
+}

+ 109 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java

@@ -0,0 +1,109 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.LoginType;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.exception.user.CaptchaExpireException;
+import org.dromara.common.core.exception.user.UserException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.auth.SmsGroup;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("sms" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SmsAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+    private final SysUserMapper userMapper;
+
+    @Override
+    public void validate(LoginBody loginBody) {
+        ValidatorUtils.validate(loginBody, SmsGroup.class);
+    }
+
+    @Override
+    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
+        String tenantId = loginBody.getTenantId();
+        String phonenumber = loginBody.getPhonenumber();
+        String smsCode = loginBody.getSmsCode();
+
+        // 通过手机号查找用户
+        SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
+
+        loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
+        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+        LoginUser loginUser = loginService.buildLoginUser(user);
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        loginService.recordLoginInfo(user.getUserId());
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        return loginVo;
+    }
+
+    /**
+     * 校验短信验证码
+     */
+    private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
+        String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
+        if (StringUtils.isBlank(code)) {
+            loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
+            throw new CaptchaExpireException();
+        }
+        return code.equals(smsCode);
+    }
+
+    private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
+        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+            .select(SysUser::getPhonenumber, SysUser::getStatus)
+            .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
+            .eq(SysUser::getPhonenumber, phonenumber));
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", phonenumber);
+            throw new UserException("user.not.exists", phonenumber);
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", phonenumber);
+            throw new UserException("user.blocked", phonenumber);
+        }
+        if (TenantHelper.isEnable()) {
+            return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId);
+        }
+        return userMapper.selectUserByPhonenumber(phonenumber);
+    }
+
+}

+ 88 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java

@@ -0,0 +1,88 @@
+package org.dromara.web.service.impl;
+
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.domain.model.XcxLoginUser;
+import org.dromara.common.core.enums.UserStatus;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.auth.WechatGroup;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.web.domain.vo.LoginVo;
+import org.dromara.web.service.IAuthStrategy;
+import org.dromara.web.service.SysLoginService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 邮件认证策略
+ *
+ * @author Michelle.Chung
+ */
+@Slf4j
+@Service("xcx" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class XcxAuthStrategy implements IAuthStrategy {
+
+    private final SysLoginService loginService;
+
+    @Override
+    public void validate(LoginBody loginBody) {
+        ValidatorUtils.validate(loginBody, WechatGroup.class);
+    }
+
+    @Override
+    public LoginVo login(String clientId, LoginBody loginBody, SysClient client) {
+        // xcxCode 为 小程序调用 wx.login 授权后获取
+        String xcxCode = loginBody.getXcxCode();
+        // todo 以下自行实现
+        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
+        String openid = "";
+        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
+        SysUserVo user = loadUserByOpenid(openid);
+
+        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
+        XcxLoginUser loginUser = new XcxLoginUser();
+        loginUser.setTenantId(user.getTenantId());
+        loginUser.setUserId(user.getUserId());
+        loginUser.setUsername(user.getUserName());
+        loginUser.setUserType(user.getUserType());
+        loginUser.setOpenid(openid);
+
+        SaLoginModel model = new SaLoginModel();
+        model.setDevice(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
+        loginService.recordLoginInfo(user.getUserId());
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        return loginVo;
+    }
+
+    private SysUserVo loadUserByOpenid(String openid) {
+        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
+        // todo 自行实现 userService.selectUserByOpenid(openid);
+        SysUserVo user = new SysUserVo();
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", openid);
+            // todo 用户不存在 业务逻辑自行实现
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", openid);
+            // todo 用户已被停用 业务逻辑自行实现
+        }
+        return user;
+    }
+
+}

+ 1 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -140,6 +140,7 @@ tenant:
     - sys_role_menu
     - sys_user_post
     - sys_user_role
+    - sys_client
 
 # MyBatisPlus配置
 # https://baomidou.com/config/

+ 3 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -28,6 +28,9 @@ user.register.error=注册失败,请联系系统管理人员
 user.notfound=请重新登录
 user.forcelogout=管理员强制退出,请重新登录
 user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
 ##文件上传消息
 upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
 upload.filename.exceed.length=上传的文件名最长{0}个字符

+ 3 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -28,6 +28,9 @@ user.register.error=Register failed, please contact system administrator
 user.notfound=Please login again
 user.forcelogout=The administrator is forced to exit,please login again
 user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
 ##文件上传消息
 upload.exceed.maxSize=The uploaded file size exceeds the limit file size!<br/>the maximum allowed file size is:{0}MB!
 upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters

+ 3 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -28,6 +28,9 @@ user.register.error=注册失败,请联系系统管理人员
 user.notfound=请重新登录
 user.forcelogout=管理员强制退出,请重新登录
 user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
 ##文件上传消息
 upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
 upload.filename.exceed.length=上传的文件名最长{0}个字符

+ 62 - 4
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java

@@ -1,7 +1,12 @@
 package org.dromara.common.core.domain.model;
 
+import jakarta.validation.constraints.Email;
 import org.dromara.common.core.constant.UserConstants;
 import lombok.Data;
+import org.dromara.common.core.validate.auth.EmailGroup;
+import org.dromara.common.core.validate.auth.PasswordGroup;
+import org.dromara.common.core.validate.auth.SmsGroup;
+import org.dromara.common.core.validate.auth.WechatGroup;
 import org.hibernate.validator.constraints.Length;
 
 import jakarta.validation.constraints.NotBlank;
@@ -15,6 +20,28 @@ import jakarta.validation.constraints.NotBlank;
 @Data
 public class LoginBody {
 
+    /**
+     * 客户端id
+     */
+    @NotBlank(message = "{auth.clientid.not.blank}")
+    private String clientId;
+
+    /**
+     * 客户端key
+     */
+    private String clientKey;
+
+    /**
+     * 客户端秘钥
+     */
+    private String clientSecret;
+
+    /**
+     * 授权类型
+     */
+    @NotBlank(message = "{auth.grant.type.not.blank}")
+    private String grantType;
+
     /**
      * 租户ID
      */
@@ -24,15 +51,15 @@ public class LoginBody {
     /**
      * 用户名
      */
-    @NotBlank(message = "{user.username.not.blank}")
-    @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
+    @NotBlank(message = "{user.username.not.blank}", groups = {PasswordGroup.class})
+    @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}", groups = {PasswordGroup.class})
     private String username;
 
     /**
      * 用户密码
      */
-    @NotBlank(message = "{user.password.not.blank}")
-    @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
+    @NotBlank(message = "{user.password.not.blank}", groups = {PasswordGroup.class})
+    @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}", groups = {PasswordGroup.class})
     private String password;
 
     /**
@@ -45,4 +72,35 @@ public class LoginBody {
      */
     private String uuid;
 
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}", groups = {SmsGroup.class})
+    private String phonenumber;
+
+    /**
+     * 短信code
+     */
+    @NotBlank(message = "{sms.code.not.blank}", groups = {SmsGroup.class})
+    private String smsCode;
+
+    /**
+     * 邮箱
+     */
+    @NotBlank(message = "{user.email.not.blank}", groups = {EmailGroup.class})
+    @Email(message = "{user.email.not.valid}")
+    private String email;
+
+    /**
+     * 邮箱code
+     */
+    @NotBlank(message = "{email.code.not.blank}", groups = {EmailGroup.class})
+    private String emailCode;
+
+    /**
+     * 小程序code
+     */
+    @NotBlank(message = "{xcx.code.not.blank}", groups = {WechatGroup.class})
+    private String xcxCode;
+
 }

+ 7 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java

@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface EmailGroup {
+}

+ 7 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java

@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface PasswordGroup {
+}

+ 7 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java

@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface SmsGroup {
+}

+ 7 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java

@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface WechatGroup {
+}

+ 17 - 5
ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java

@@ -37,14 +37,26 @@ public class ExcelEnumConvert implements Converter<Object> {
 
     @Override
     public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
-        Object codeValue = cellData.getData();
+        cellData.checkEmpty();
+        // Excel中填入的是枚举中指定的描述
+        Object textValue = switch (cellData.getType()) {
+            case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue();
+            case NUMBER -> cellData.getNumberValue();
+            case BOOLEAN -> cellData.getBooleanValue();
+            default -> throw new IllegalArgumentException("单元格类型异常!");
+        };
         // 如果是空值
-        if (ObjectUtil.isNull(codeValue)) {
+        if (ObjectUtil.isNull(textValue)) {
             return null;
         }
-        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
-        String textValue = enumValueMap.get(codeValue);
-        return Convert.convert(contentProperty.getField().getType(), textValue);
+        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
+        // 从Java输出至Excel是code转text
+        // 因此从Excel转Java应该将text与code对调
+        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+        // 应该从text -> code中查找
+        Object codeValue = enumTextToCodeMap.get(textValue);
+        return Convert.convert(contentProperty.getField().getType(), codeValue);
     }
 
     @Override

+ 7 - 27
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java

@@ -7,13 +7,12 @@ import cn.dev33.satoken.stp.SaLoginModel;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
 import org.dromara.common.core.constant.TenantConstants;
 import org.dromara.common.core.constant.UserConstants;
 import org.dromara.common.core.domain.model.LoginUser;
-import org.dromara.common.core.enums.DeviceType;
 import org.dromara.common.core.enums.UserType;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 
 import java.util.Set;
 
@@ -36,41 +35,22 @@ public class LoginHelper {
     public static final String TENANT_KEY = "tenantId";
     public static final String USER_KEY = "userId";
 
-    /**
-     * 登录系统
-     *
-     * @param loginUser 登录用户信息
-     */
-    public static void login(LoginUser loginUser) {
-        loginByDevice(loginUser, null);
-    }
-
     /**
      * 登录系统 基于 设备类型
      * 针对相同用户体系不同设备
      *
      * @param loginUser 登录用户信息
+     * @param model     配置参数
      */
-    public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
+    public static void login(LoginUser loginUser, SaLoginModel model) {
         SaStorage storage = SaHolder.getStorage();
         storage.set(LOGIN_USER_KEY, loginUser);
         storage.set(TENANT_KEY, loginUser.getTenantId());
         storage.set(USER_KEY, loginUser.getUserId());
-        SaLoginModel model = new SaLoginModel();
-        if (ObjectUtil.isNotNull(deviceType)) {
-            model.setDevice(deviceType.getDevice());
-        }
-        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
-        // 例如: 后台用户30分钟过期 app用户1天过期
-//        UserType userType = UserType.getUserType(loginUser.getUserType());
-//        if (userType == UserType.SYS_USER) {
-//            model.setTimeout(86400).setActiveTimeout(1800);
-//        } else if (userType == UserType.APP_USER) {
-//            model.setTimeout(86400).setActiveTimeout(1800);
-//        }
+        model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
         StpUtil.login(loginUser.getLoginId(),
-                model.setExtra(TENANT_KEY, loginUser.getTenantId())
-                    .setExtra(USER_KEY, loginUser.getUserId()));
+            model.setExtra(TENANT_KEY, loginUser.getTenantId())
+                .setExtra(USER_KEY, loginUser.getUserId()));
         StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
     }
 

+ 115 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java

@@ -0,0 +1,115 @@
+package org.dromara.system.controller.system;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.system.service.ISysClientService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 客户端管理
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/client")
+public class SysClientController extends BaseController {
+
+    private final ISysClientService sysClientService;
+
+    /**
+     * 查询客户端管理列表
+     */
+    @SaCheckPermission("system:client:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysClientVo> list(SysClientBo bo, PageQuery pageQuery) {
+        return sysClientService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出客户端管理列表
+     */
+    @SaCheckPermission("system:client:export")
+    @Log(title = "客户端管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysClientBo bo, HttpServletResponse response) {
+        List<SysClientVo> list = sysClientService.queryList(bo);
+        ExcelUtil.exportExcel(list, "客户端管理", SysClientVo.class, response);
+    }
+
+    /**
+     * 获取客户端管理详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:client:query")
+    @GetMapping("/{id}")
+    public R<SysClientVo> getInfo(@NotNull(message = "主键不能为空")
+                                  @PathVariable Long id) {
+        return R.ok(sysClientService.queryById(id));
+    }
+
+    /**
+     * 新增客户端管理
+     */
+    @SaCheckPermission("system:client:add")
+    @Log(title = "客户端管理", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysClientBo bo) {
+        return toAjax(sysClientService.insertByBo(bo));
+    }
+
+    /**
+     * 修改客户端管理
+     */
+    @SaCheckPermission("system:client:edit")
+    @Log(title = "客户端管理", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysClientBo bo) {
+        return toAjax(sysClientService.updateByBo(bo));
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:client:edit")
+    @Log(title = "客户端管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysClientBo bo) {
+        return toAjax(sysClientService.updateUserStatus(bo.getId(), bo.getStatus()));
+    }
+
+    /**
+     * 删除客户端管理
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:client:remove")
+    @Log(title = "客户端管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(sysClientService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 77 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java

@@ -0,0 +1,77 @@
+package org.dromara.system.domain;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 授权管理对象 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_client")
+public class SysClient extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 客户端id
+     */
+    private String clientId;
+
+    /**
+     * 客户端key
+     */
+    private String clientKey;
+
+    /**
+     * 客户端秘钥
+     */
+    private String clientSecret;
+
+    /**
+     * 授权类型
+     */
+    private String grantType;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * token活跃超时时间
+     */
+    private Long activeTimeout;
+
+    /**
+     * token固定超时时间
+     */
+    private Long timeout;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 80 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java

@@ -0,0 +1,80 @@
+package org.dromara.system.domain.bo;
+
+import org.dromara.system.domain.SysClient;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+import java.util.List;
+
+/**
+ * 授权管理业务对象 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysClient.class, reverseConvertGenerate = false)
+public class SysClientBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotNull(message = "id不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 客户端id
+     */
+    private String clientId;
+
+    /**
+     * 客户端key
+     */
+    @NotBlank(message = "客户端key不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String clientKey;
+
+    /**
+     * 客户端秘钥
+     */
+    @NotBlank(message = "客户端秘钥不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String clientSecret;
+
+    /**
+     * 授权类型
+     */
+    @NotNull(message = "授权类型不能为空", groups = { AddGroup.class, EditGroup.class })
+    private List<String> grantTypeList;
+
+    /**
+     * 授权类型
+     */
+    private String grantType;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * token活跃超时时间
+     */
+    private Long activeTimeout;
+
+    /**
+     * token固定超时时间
+     */
+    private Long timeout;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+
+}

+ 90 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java

@@ -0,0 +1,90 @@
+package org.dromara.system.domain.vo;
+
+import org.dromara.system.domain.SysClient;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 授权管理视图对象 sys_client
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysClient.class)
+public class SysClientVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private Long id;
+
+    /**
+     * 客户端id
+     */
+    @ExcelProperty(value = "客户端id")
+    private String clientId;
+
+    /**
+     * 客户端key
+     */
+    @ExcelProperty(value = "客户端key")
+    private String clientKey;
+
+    /**
+     * 客户端秘钥
+     */
+    @ExcelProperty(value = "客户端秘钥")
+    private String clientSecret;
+
+    /**
+     * 授权类型
+     */
+    @ExcelProperty(value = "授权类型")
+    private List<String> grantTypeList;
+
+    /**
+     * 授权类型
+     */
+    private String grantType;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * token活跃超时时间
+     */
+    @ExcelProperty(value = "token活跃超时时间")
+    private Long activeTimeout;
+
+    /**
+     * token固定超时时间
+     */
+    @ExcelProperty(value = "token固定超时时间")
+    private Long timeout;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.system.mapper;
+
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 授权管理Mapper接口
+ *
+ * @author Michelle.Chung
+ * @date 2023-05-15
+ */
+public interface SysClientMapper extends BaseMapperPlus<SysClient, SysClientVo> {
+
+}

+ 60 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java

@@ -0,0 +1,60 @@
+package org.dromara.system.service;
+
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 客户端管理Service接口
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+public interface ISysClientService {
+
+    /**
+     * 查询客户端管理
+     */
+    SysClientVo queryById(Long id);
+
+    /**
+     * 查询客户端信息基于客户端id
+     */
+    SysClient queryByClientId(String clientId);
+
+    /**
+     * 查询客户端管理列表
+     */
+    TableDataInfo<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询客户端管理列表
+     */
+    List<SysClientVo> queryList(SysClientBo bo);
+
+    /**
+     * 新增客户端管理
+     */
+    Boolean insertByBo(SysClientBo bo);
+
+    /**
+     * 修改客户端管理
+     */
+    Boolean updateByBo(SysClientBo bo);
+
+    /**
+     * 修改状态
+     */
+    int updateUserStatus(Long id, String status);
+
+    /**
+     * 校验并批量删除客户端管理信息
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+}

+ 144 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java

@@ -0,0 +1,144 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.crypto.SecureUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.SysClient;
+import org.dromara.system.domain.bo.SysClientBo;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.mapper.SysClientMapper;
+import org.dromara.system.service.ISysClientService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 客户端管理Service业务层处理
+ *
+ * @author Michelle.Chung
+ * @date 2023-06-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysClientServiceImpl implements ISysClientService {
+
+    private final SysClientMapper baseMapper;
+
+    /**
+     * 查询客户端管理
+     */
+    @Override
+    public SysClientVo queryById(Long id) {
+        SysClientVo vo = baseMapper.selectVoById(id);
+        vo.setGrantTypeList(List.of(vo.getGrantType().split(",")));
+        return vo;
+    }
+
+
+    /**
+     * 查询客户端管理
+     */
+    @Override
+    public SysClient queryByClientId(String clientId) {
+        return baseMapper.selectOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
+    }
+
+    /**
+     * 查询客户端管理列表
+     */
+    @Override
+    public TableDataInfo<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
+        Page<SysClientVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        result.getRecords().forEach(r -> r.setGrantTypeList(List.of(r.getGrantType().split(","))));
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询客户端管理列表
+     */
+    @Override
+    public List<SysClientVo> queryList(SysClientBo bo) {
+        LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SysClient> buildQueryWrapper(SysClientBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<SysClient> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getClientId()), SysClient::getClientId, bo.getClientId());
+        lqw.eq(StringUtils.isNotBlank(bo.getClientKey()), SysClient::getClientKey, bo.getClientKey());
+        lqw.eq(StringUtils.isNotBlank(bo.getClientSecret()), SysClient::getClientSecret, bo.getClientSecret());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysClient::getStatus, bo.getStatus());
+        return lqw;
+    }
+
+    /**
+     * 新增客户端管理
+     */
+    @Override
+    public Boolean insertByBo(SysClientBo bo) {
+        SysClient add = MapstructUtils.convert(bo, SysClient.class);
+        validEntityBeforeSave(add);
+        add.setGrantType(String.join(",", bo.getGrantTypeList()));
+        // 生成clientid
+        String clientKey = bo.getClientKey();
+        String clientSecret = bo.getClientSecret();
+        add.setClientId(SecureUtil.md5(clientKey + clientSecret));
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改客户端管理
+     */
+    @Override
+    public Boolean updateByBo(SysClientBo bo) {
+        SysClient update = MapstructUtils.convert(bo, SysClient.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 修改状态
+     */
+    @Override
+    public int updateUserStatus(Long id, String status) {
+        return baseMapper.update(null,
+            new LambdaUpdateWrapper<SysClient>()
+                .set(SysClient::getStatus, status)
+                .eq(SysClient::getId, id));
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SysClient entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 批量删除客户端管理
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java

@@ -453,6 +453,11 @@ public class SysRoleServiceImpl implements ISysRoleService {
 
     @Override
     public void cleanOnlineUserByRole(Long roleId) {
+        // 如果角色未绑定用户 直接返回
+        Long num = userRoleMapper.selectCount(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, roleId));
+        if (num == 0) {
+            return;
+        }
         List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
         if (CollUtil.isEmpty(keys)) {
             return;

+ 7 - 0
ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.system.mapper.SysClientMapper">
+
+</mapper>

+ 66 - 3
script/sql/oracle/oracle_ry_vue_5.X.sql

@@ -439,7 +439,8 @@ insert into sys_menu values('113',  '缓存监控',     '2',   '5', 'cache',
 insert into sys_menu values('114',  '表单构建',     '3',   '1', 'build',            'tool/build/index',             '', 1, 0, 'C', '0', '0', 'tool:build:list',             'build',         103, 1, sysdate, null, null, '表单构建菜单');
 insert into sys_menu values('115',  '代码生成',     '3',   '2', 'gen',              'tool/gen/index',               '', 1, 0, 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, sysdate, null, null, '代码生成菜单');
 insert into sys_menu values('121',  '租户管理',     '6',   '1', 'tenant',           'system/tenant/index',          '', 1, 0, 'C', '0', '0', 'system:tenant:list',          'list',          103, 1, sysdate, null, null, '租户管理菜单');
-insert into sys_menu values('122',  '租户套餐管理',  '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, sysdate, null, null, '租户套餐管理菜单');
+insert into sys_menu values('122',  '租户套餐管理', '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, sysdate, null, null, '租户套餐管理菜单');
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', 1, 0, 'C', '0', '0', 'system:client:list',          'international', 103, 1, sysdate, null, null, '客户端管理菜单');
 -- springboot-admin监控
 insert into sys_menu values('117',  'Admin监控',   '2',    '5', 'Admin',            'monitor/admin/index',         '', 1, 0, 'C', '0', '0', 'monitor:admin:list',          'dashboard',     103, 1, sysdate, null, null, 'Admin监控菜单');
 -- oss菜单
@@ -536,7 +537,12 @@ insert into sys_menu values('1612', '租户套餐新增', '122', '2', '#', '', '
 insert into sys_menu values('1613', '租户套餐修改', '122', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:edit',    '#', 103, 1, sysdate, null, null, '');
 insert into sys_menu values('1614', '租户套餐删除', '122', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:remove',  '#', 103, 1, sysdate, null, null, '');
 insert into sys_menu values('1615', '租户套餐导出', '122', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:export',  '#', 103, 1, sysdate, null, null, '');
-
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', 1, 0, 'F', '0', '0', 'system:client:query',        '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', 1, 0, 'F', '0', '0', 'system:client:add',          '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', 1, 0, 'F', '0', '0', 'system:client:edit',         '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', 1, 0, 'F', '0', '0', 'system:client:remove',       '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', 1, 0, 'F', '0', '0', 'system:client:export',       '#', 103, 1, sysdate, null, null, '');
 
 -- ----------------------------
 -- 6、用户和角色关联表  用户N-1角色
@@ -655,6 +661,11 @@ insert into sys_role_menu values ('2', '1057');
 insert into sys_role_menu values ('2', '1058');
 insert into sys_role_menu values ('2', '1059');
 insert into sys_role_menu values ('2', '1060');
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
 
 -- ----------------------------
 -- 8、角色和部门关联表  角色1-N部门
@@ -789,7 +800,9 @@ insert into sys_dict_type values(6, '000000', '系统是否', 'sys_yes_no',
 insert into sys_dict_type values(7, '000000', '通知类型', 'sys_notice_type',     '0', 103, 1, sysdate, null, null, '通知类型列表');
 insert into sys_dict_type values(8, '000000', '通知状态', 'sys_notice_status',   '0', 103, 1, sysdate, null, null, '通知状态列表');
 insert into sys_dict_type values(9, '000000', '操作类型', 'sys_oper_type',       '0', 103, 1, sysdate, null, null, '操作类型列表');
-insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',   '0', 103, 1, sysdate, null, null, '登录状态列表');
+insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',  '0', 103, 1, sysdate, null, null, '登录状态列表');
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, sysdate, null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, sysdate, null, null, '客户端设备类型');
 
 
 -- ----------------------------
@@ -859,6 +872,13 @@ insert into sys_dict_data values(25, '000000', 8,  '生成代码', '8',       's
 insert into sys_dict_data values(26, '000000', 9,  '清空数据', '9',       'sys_oper_type',       '',   'danger',  'N', '0', 103, 1, sysdate, null, null, '清空操作');
 insert into sys_dict_data values(27, '000000', 1,  '成功',     '0',       'sys_common_status',   '',   'primary', 'N', '0', 103, 1, sysdate, null, null, '正常状态');
 insert into sys_dict_data values(28, '000000', 2,  '失败',     '1',       'sys_common_status',   '',   'danger',  'N', '0', 103, 1, sysdate, null, null, '停用状态');
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端',      'pc',        'sys_device_type',  '',   'default', 'N', '0', 103, 1, sysdate, null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端',     'app',       'sys_device_type',  '',   'default', 'N', '0', 103, 1, sysdate, null, null, 'APP端');
 
 
 -- ----------------------------
@@ -1178,6 +1198,49 @@ insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX',  'X
 insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1250000000',  '', 'cos.ap-beijing.myqcloud.com',   '','N', 'ap-beijing',  '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
 insert into sys_oss_config values (5, '000000', 'image',  'ruoyi',            'ruoyi123',        'ruoyi',             'image', '127.0.0.1:9000',           '','N', '',            '1', '1', '', NULL, 103, 1, sysdate, 1, sysdate);
 
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+create table sys_client (
+    id                  number(20)    not null,
+    client_id           varchar(64)   default null,
+    client_key          varchar(32)   default null,
+    client_secret       varchar(255)  default null,
+    grant_type          varchar(255)  default null,
+    device_type         varchar(32)   default null,
+    active_timeout      number(11)    default 1800,
+    timeout             number(11)    default 604800,
+    status              char(1)       default '0',
+    del_flag            char(1)       default '0',
+    create_dept         number(20)    default null,
+    create_by           number(20)    default null,
+    create_time         date,
+    update_by           number(20)    default null,
+    update_time         date
+)
+
+alter table sys_client add constraint pk_sys_client primary key (id);
+
+comment on table sys_client                         is '系统授权表';
+comment on column sys_client.id                     is '主建';
+comment on column sys_client.client_id              is '客户端id';
+comment on column sys_client.client_key             is '客户端key';
+comment on column sys_client.client_secret          is '客户端秘钥';
+comment on column sys_client.grant_type             is '授权类型';
+comment on column sys_client.device_type            is '设备类型';
+comment on column sys_client.active_timeout         is 'token活跃超时时间';
+comment on column sys_client.timeout                is 'token固定超时';
+comment on column sys_client.status                 is '状态(0正常 1停用)';
+comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
+comment on column sys_client.create_dept            is '创建部门';
+comment on column sys_client.create_by              is '创建者';
+comment on column sys_client.create_time            is '创建时间';
+comment on column sys_client.update_by              is '更新者';
+comment on column sys_client.update_time            is '更新时间';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+
 
 -- ----------------------------
 -- 钩子 ,用于session连接之后 自动设置默认的date类型格式化 简化时间查询

+ 66 - 4
script/sql/postgres/postgres_ry_vue_5.X.sql

@@ -447,7 +447,8 @@ insert into sys_menu values('113',  '缓存监控',     '2',   '5', 'cache',
 insert into sys_menu values('114',  '表单构建',     '3',   '1', 'build',            'tool/build/index',             '', '1', '0', 'C', '0', '0', 'tool:build:list',             'build',         103, 1, now(), null, null, '表单构建菜单');
 insert into sys_menu values('115',  '代码生成',     '3',   '2', 'gen',              'tool/gen/index',               '', '1', '0', 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, now(), null, null, '代码生成菜单');
 insert into sys_menu values('121',  '租户管理',     '6',   '1', 'tenant',           'system/tenant/index',          '', '1', '0', 'C', '0', '0', 'system:tenant:list',          'list',          103, 1, now(), null, null, '租户管理菜单');
-insert into sys_menu values('122',  '租户套餐管理',  '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', '1', '0', 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, now(), null, null, '租户套餐管理菜单');
+insert into sys_menu values('122',  '租户套餐管理', '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', '1', '0', 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, now(), null, null, '租户套餐管理菜单');
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', '1', '0', 'C', '0', '0', 'system:client:list',          'international', 103, 1, now(), null, null, '客户端管理菜单');
 
 -- springboot-admin监控
 insert into sys_menu values('117',  'Admin监控',   '2',   '5',  'Admin',            'monitor/admin/index',         '', '1', '0', 'C', '0', '0', 'monitor:admin:list',          'dashboard',     103, 1, now(), null, null, 'Admin监控菜单');
@@ -545,7 +546,12 @@ insert into sys_menu values('1612', '租户套餐新增', '122', '2', '#', '', '
 insert into sys_menu values('1613', '租户套餐修改', '122', '3', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:edit',    '#', 103, 1, now(), null, null, '');
 insert into sys_menu values('1614', '租户套餐删除', '122', '4', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:remove',  '#', 103, 1, now(), null, null, '');
 insert into sys_menu values('1615', '租户套餐导出', '122', '5', '#', '', '', '1', '0', 'F', '0', '0', 'system:tenantPackage:export',  '#', 103, 1, now(), null, null, '');
-
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', '1', '0', 'F', '0', '0', 'system:client:query',        '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', '1', '0', 'F', '0', '0', 'system:client:add',          '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', '1', '0', 'F', '0', '0', 'system:client:edit',         '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', '1', '0', 'F', '0', '0', 'system:client:remove',       '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', '1', '0', 'F', '0', '0', 'system:client:export',       '#', 103, 1, now(), null, null, '');
 
 -- ----------------------------
 -- 6、用户和角色关联表  用户N-1角色
@@ -666,6 +672,11 @@ insert into sys_role_menu values ('2', '1057');
 insert into sys_role_menu values ('2', '1058');
 insert into sys_role_menu values ('2', '1059');
 insert into sys_role_menu values ('2', '1060');
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
 
 -- ----------------------------
 -- 8、角色和部门关联表  角色1-N部门
@@ -805,8 +816,9 @@ insert into sys_dict_type values(6, '000000', '系统是否', 'sys_yes_no',
 insert into sys_dict_type values(7, '000000', '通知类型', 'sys_notice_type',     '0', 103, 1, now(), null, null, '通知类型列表');
 insert into sys_dict_type values(8, '000000', '通知状态', 'sys_notice_status',   '0', 103, 1, now(), null, null, '通知状态列表');
 insert into sys_dict_type values(9, '000000', '操作类型', 'sys_oper_type',       '0', 103, 1, now(), null, null, '操作类型列表');
-insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',   '0', 103, 1, now(), null, null, '登录状态列表');
-
+insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',  '0', 103, 1, now(), null, null, '登录状态列表');
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, now(), null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, now(), null, null, '客户端设备类型');
 
 -- ----------------------------
 -- 12、字典数据表
@@ -876,6 +888,13 @@ insert into sys_dict_data values(25, '000000', 8,  '生成代码', '8',       's
 insert into sys_dict_data values(26, '000000', 9,  '清空数据', '9',       'sys_oper_type',       '',   'danger',  'N', '0', 103, 1, now(), null, null, '清空操作');
 insert into sys_dict_data values(27, '000000', 1,  '成功',     '0',       'sys_common_status',   '',   'primary', 'N', '0', 103, 1, now(), null, null, '正常状态');
 insert into sys_dict_data values(28, '000000', 2,  '失败',     '1',       'sys_common_status',   '',   'danger',  'N', '0', 103, 1, now(), null, null, '停用状态');
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端', 'pc',            'sys_device_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端', 'app',          'sys_device_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, 'APP端');
 
 
 -- ----------------------------
@@ -1200,6 +1219,49 @@ insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX',  'X
 insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1250000000',  '', 'cos.ap-beijing.myqcloud.com',         '','N', 'ap-beijing',  '1', '1', '', 103, 1, now(), 1, now(), null);
 insert into sys_oss_config values (5, '000000', 'image',  'ruoyi',            'ruoyi123',        'ruoyi',             'image', '127.0.0.1:9000',                 '','N', '',            '1', '1', '', 103, 1, now(), 1, now(), NULL);
 
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+    id                  int8,
+    client_id           varchar(64)   ''::varchar,
+    client_key          varchar(32)   ''::varchar,
+    client_secret       varchar(255)  ''::varchar,
+    grant_type          varchar(255)  ''::varchar,
+    device_type         varchar(32)   ''::varchar,
+    active_timeout      int4          default 1800,
+    timeout             int4          default 604800,
+    status              char(1)       default '0'::bpchar,
+    del_flag            char(1)       default '0'::bpchar,
+    create_dept         int8,
+    create_by           int8,
+    create_time         timestamp,
+    update_by           int8,
+    update_time         timestamp,
+    constraint sys_client_pk primary key (id)
+)
+
+comment on table sys_client                         is '系统授权表';
+comment on column sys_client.id                     is '主建';
+comment on column sys_client.client_id              is '客户端id';
+comment on column sys_client.client_key             is '客户端key';
+comment on column sys_client.client_secret          is '客户端秘钥';
+comment on column sys_client.grant_type             is '授权类型';
+comment on column sys_client.device_type            is '设备类型';
+comment on column sys_client.active_timeout         is 'token活跃超时时间';
+comment on column sys_client.timeout                is 'token固定超时';
+comment on column sys_client.status                 is '状态(0正常 1停用)';
+comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
+comment on column sys_client.create_dept            is '创建部门';
+comment on column sys_client.create_by              is '创建者';
+comment on column sys_client.create_time            is '创建时间';
+comment on column sys_client.update_by              is '更新者';
+comment on column sys_client.update_time            is '更新时间';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+
 -- 字符串自动转时间 避免框架时间查询报错问题
 create or replace function cast_varchar_to_timestamp(varchar) returns timestamptz as $$
 select to_timestamp($1, 'yyyy-mm-dd hh24:mi:ss');

+ 50 - 4
script/sql/ry_vue_5.X.sql

@@ -283,8 +283,9 @@ insert into sys_menu values('109',  '在线用户',     '2',   '1', 'online',
 insert into sys_menu values('113',  '缓存监控',     '2',   '5', 'cache',            'monitor/cache/index',          '', 1, 0, 'C', '0', '0', 'monitor:cache:list',          'redis',         103, 1, sysdate(), null, null, '缓存监控菜单');
 insert into sys_menu values('114',  '表单构建',     '3',   '1', 'build',            'tool/build/index',             '', 1, 0, 'C', '0', '0', 'tool:build:list',             'build',         103, 1, sysdate(), null, null, '表单构建菜单');
 insert into sys_menu values('115',  '代码生成',     '3',   '2', 'gen',              'tool/gen/index',               '', 1, 0, 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, sysdate(), null, null, '代码生成菜单');
-insert into sys_menu values ('121', '租户管理',     '6',   '1', 'tenant',           'system/tenant/index',          '', 1, 0, 'C', '0', '0', 'system:tenant:list',          'list',          103, 1, sysdate(), null, null, '租户管理菜单');
-insert into sys_menu values ('122', '租户套餐管理',  '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, sysdate(), null, null, '租户套餐管理菜单');
+insert into sys_menu values('121',  '租户管理',     '6',   '1', 'tenant',           'system/tenant/index',          '', 1, 0, 'C', '0', '0', 'system:tenant:list',          'list',          103, 1, sysdate(), null, null, '租户管理菜单');
+insert into sys_menu values('122',  '租户套餐管理', '6',   '2', 'tenantPackage',    'system/tenantPackage/index',   '', 1, 0, 'C', '0', '0', 'system:tenantPackage:list',   'form',          103, 1, sysdate(), null, null, '租户套餐管理菜单');
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', 1, 0, 'C', '0', '0', 'system:client:list',          'international', 103, 1, sysdate(), null, null, '客户端管理菜单');
 
 -- springboot-admin监控
 insert into sys_menu values('117',  'Admin监控',   '2',   '5',  'Admin',            'monitor/admin/index',         '', 1, 0, 'C', '0', '0', 'monitor:admin:list',           'dashboard',     103, 1, sysdate(), null, null, 'Admin监控菜单');
@@ -382,7 +383,12 @@ insert into sys_menu values ('1612', '租户套餐新增', '122', '2', '#', '',
 insert into sys_menu values ('1613', '租户套餐修改', '122', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:edit',    '#', 103, 1, sysdate(), null, null, '');
 insert into sys_menu values ('1614', '租户套餐删除', '122', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:remove',  '#', 103, 1, sysdate(), null, null, '');
 insert into sys_menu values ('1615', '租户套餐导出', '122', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:tenantPackage:export',  '#', 103, 1, sysdate(), null, null, '');
-
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', 1, 0, 'F', '0', '0', 'system:client:query',        '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', 1, 0, 'F', '0', '0', 'system:client:add',          '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', 1, 0, 'F', '0', '0', 'system:client:edit',         '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', 1, 0, 'F', '0', '0', 'system:client:remove',       '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', 1, 0, 'F', '0', '0', 'system:client:export',       '#', 103, 1, sysdate(), null, null, '');
 
 -- ----------------------------
 -- 6、用户和角色关联表  用户N-1角色
@@ -493,6 +499,11 @@ insert into sys_role_menu values ('2', '1057');
 insert into sys_role_menu values ('2', '1058');
 insert into sys_role_menu values ('2', '1059');
 insert into sys_role_menu values ('2', '1060');
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');
 
 -- ----------------------------
 -- 8、角色和部门关联表  角色1-N部门
@@ -588,7 +599,9 @@ insert into sys_dict_type values(6, '000000', '系统是否', 'sys_yes_no',
 insert into sys_dict_type values(7, '000000', '通知类型', 'sys_notice_type',     '0', 103, 1, sysdate(), null, null, '通知类型列表');
 insert into sys_dict_type values(8, '000000', '通知状态', 'sys_notice_status',   '0', 103, 1, sysdate(), null, null, '通知状态列表');
 insert into sys_dict_type values(9, '000000', '操作类型', 'sys_oper_type',       '0', 103, 1, sysdate(), null, null, '操作类型列表');
-insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',   '0', 103, 1, sysdate(), null, null, '登录状态列表');
+insert into sys_dict_type values(10, '000000', '系统状态', 'sys_common_status',  '0', 103, 1, sysdate(), null, null, '登录状态列表');
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, sysdate(), null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, sysdate(), null, null, '客户端设备类型');
 
 
 -- ----------------------------
@@ -641,6 +654,13 @@ insert into sys_dict_data values(25, '000000', 8,  '生成代码', '8',       's
 insert into sys_dict_data values(26, '000000', 9,  '清空数据', '9',       'sys_oper_type',       '',   'danger',  'N', '0', 103, 1, sysdate(), null, null, '清空操作');
 insert into sys_dict_data values(27, '000000', 1,  '成功',     '0',       'sys_common_status',   '',   'primary', 'N', '0', 103, 1, sysdate(), null, null, '正常状态');
 insert into sys_dict_data values(28, '000000', 2,  '失败',     '1',       'sys_common_status',   '',   'danger',  'N', '0', 103, 1, sysdate(), null, null, '停用状态');
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端', 'pc',          'sys_device_type',     '',   'default', 'N', '0', 103, 1, sysdate(), null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端', 'app',        'sys_device_type',     '',   'default', 'N', '0', 103, 1, sysdate(), null, null, 'APP端');
 
 
 -- ----------------------------
@@ -833,3 +853,29 @@ insert into sys_oss_config values (2, '000000', 'qiniu',  'XXXXXXXXXXXXXXX',  'X
 insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi',             '', 'oss-cn-beijing.aliyuncs.com',   '','N', '',             '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
 insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX',  'XXXXXXXXXXXXXXX', 'ruoyi-1250000000',  '', 'cos.ap-beijing.myqcloud.com',   '','N', 'ap-beijing',   '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
 insert into sys_oss_config values (5, '000000', 'image',  'ruoyi',            'ruoyi123',        'ruoyi',             'image', '127.0.0.1:9000',           '','N', '',             '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
+
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+    id                  bigint(20)    not null            comment 'id',
+    client_id           varchar(64)   default null        comment '客户端id',
+    client_key          varchar(32)   default null        comment '客户端key',
+    client_secret       varchar(255)  default null        comment '客户端秘钥',
+    grant_type          varchar(255)  default null        comment '授权类型',
+    device_type         varchar(32)   default null        comment '设备类型',
+    active_timeout      int(11)       default 1800        comment 'token活跃超时时间',
+    timeout             int(11)       default 604800      comment 'token固定超时',
+    status              char(1)       default '0'         comment '状态(0正常 1停用)',
+    del_flag            char(1)       default '0'         comment '删除标志(0代表存在 2代表删除)',
+    create_dept         bigint(20)    default null        comment '创建部门',
+    create_by           bigint(20)    default null        comment '创建者',
+    create_time         datetime      default null        comment '创建时间',
+    update_by           bigint(20)    default null        comment '更新者',
+    update_time         datetime      default null        comment '更新时间',
+    primary key (id)
+) engine=innodb comment='系统授权表';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());

+ 167 - 0
script/sql/sqlserver/sqlserver_ry_vue_5.X.sql

@@ -1259,6 +1259,20 @@ INSERT sys_dict_data VALUES (27, N'000000', 1, N'成功', N'0', N'sys_common_sta
 GO
 INSERT sys_dict_data VALUES (28, N'000000', 2, N'失败', N'1', N'sys_common_status', N'', N'danger', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'停用状态')
 GO
+INSERT sys_dict_data VALUES (30, N'000000', 0, N'密码认证', N'password', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'密码认证')
+GO
+INSERT sys_dict_data VALUES (31, N'000000', 0, N'短信认证', N'sms', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'短信认证')
+GO
+INSERT sys_dict_data VALUES (32, N'000000', 0, N'邮件认证', N'email', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'邮件认证')
+GO
+INSERT sys_dict_data VALUES (33, N'000000', 0, N'小程序认证', N'xcx', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'小程序认证')
+GO
+INSERT sys_dict_data VALUES (34, N'000000', 0, N'三方登录认证', N'`social`', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'三方登录认证')
+GO
+INSERT sys_dict_data VALUES (35, N'000000', 0, N'PC端', N'`pc`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'PC端')
+GO
+INSERT sys_dict_data VALUES (36, N'000000', 0, N'APP端', N'`app`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'APP端')
+GO
 
 CREATE TABLE sys_dict_type
 (
@@ -1375,6 +1389,10 @@ INSERT sys_dict_type VALUES (9, N'000000', N'操作类型', N'sys_oper_type', N'
 GO
 INSERT sys_dict_type VALUES (10, N'000000', N'系统状态', N'sys_common_status', N'0', 103, 1, getdate(), NULL, NULL, N'登录状态列表')
 GO
+INSERT sys_dict_type VALUES (11, N'000000', N'授权类型', N'sys_grant_type', N'0', 103, 1, getdate(), NULL, NULL, N'认证授权类型')
+GO
+INSERT sys_dict_type VALUES (12, N'000000', N'设备类型', N'sys_device_type', N'0', 103, 1, getdate(), NULL, NULL, N'客户端设备类型')
+GO
 
 CREATE TABLE sys_logininfor
 (
@@ -1661,6 +1679,8 @@ INSERT sys_menu VALUES (121, N'租户管理', 6, 1, N'tenant', N'system/tenant/i
 GO
 INSERT sys_menu VALUES (122, N'租户套餐管理', 6, 2, N'tenantPackage', N'system/tenantPackage/index', N'', 1, 0, N'C', N'0', N'0', N'system:tenantPackage:list', N'code', 103, 1, getdate(), NULL, NULL, N'租户套餐管理菜单')
 GO
+INSERT sys_menu VALUES (123, N'客户端管理', 1, 1, N'client', N'system/client/index', N'', 1, 0, N'C', N'0', N'0', N'system:client:list', N'international', 103, 1, getdate(), NULL, NULL, N'客户端管理菜单')
+GO
 INSERT sys_menu VALUES (117, N'Admin监控', 2, 5, N'Admin', N'monitor/admin/index', N'', 1, 0, N'C', N'0', N'0', N'monitor:admin:list', N'dashboard', 103, 1, getdate(), NULL, NULL, N'Admin监控菜单');
 GO
 INSERT sys_menu VALUES (118, N'文件管理', 1, 10, N'oss', N'system/oss/index', N'', 1, 0, N'C', '0', N'0', N'system:oss:list', N'upload', 103, 1, getdate(), NULL, NULL, N'文件管理菜单');
@@ -1816,6 +1836,17 @@ INSERT sys_menu VALUES (1614, N'租户套餐删除', 122, 4, N'#', N'', N'', 1,
 GO
 INSERT sys_menu VALUES (1615, N'租户套餐导出', 122, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
 GO
+-- 客户端管理按钮
+INSERT sys_menu VALUES (1061, N'客户端管理查询', 123, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1062, N'客户端管理新增', 123, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1063, N'客户端管理修改', 123, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1064, N'客户端管理删除', 123, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1065, N'客户端管理导出', 123, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
 
 CREATE TABLE sys_notice
 (
@@ -2539,6 +2570,16 @@ INSERT sys_role_menu VALUES (2, 1059)
 GO
 INSERT sys_role_menu VALUES (2, 1060)
 GO
+INSERT sys_role_menu VALUES (2, 1061)
+GO
+INSERT sys_role_menu VALUES (2, 1062)
+GO
+INSERT sys_role_menu VALUES (2, 1063)
+GO
+INSERT sys_role_menu VALUES (2, 1064)
+GO
+INSERT sys_role_menu VALUES (2, 1065)
+GO
 
 CREATE TABLE sys_user
 (
@@ -3039,3 +3080,129 @@ INSERT INTO sys_oss_config VALUES (N'4', N'000000', N'qcloud', N'XXXXXXXXXXXXXXX
 GO
 INSERT INTO sys_oss_config VALUES (N'5', N'000000', N'image',  N'ruoyi',           N'ruoyi123',        N'ruoyi',            N'image', N'127.0.0.1:9000',               N'',N'N', N'',           N'1', N'1', N'', 103, 1, getdate(), 1, getdate(), NULL)
 GO
+
+
+CREATE TABLE sys_client
+(
+    id                  bigint                              NOT NULL,
+    client_id           nvarchar(20)  DEFAULT ''            NULL,
+    client_key          nvarchar(255) DEFAULT ''            NULL,
+    client_secret       nvarchar(255) DEFAULT ''            NULL,
+    grant_type          nvarchar(255) DEFAULT ''            NULL,
+    device_type         nvarchar(32) DEFAULT ''            NULL,
+    active_timeout      int           DEFAULT ((1800))      NULL,
+    timeout             int           DEFAULT ((604800))    NULL,
+    status              nchar(1)      DEFAULT ('0')         NULL,
+    del_flag            nchar(1)      DEFAULT ('0')         NULL,
+    create_dept         bigint                              NULL,
+    create_by           bigint                              NULL,
+    create_time         datetime2(7)                        NULL,
+    update_by           bigint                              NULL,
+    update_time         datetime2(7)                        NULL
+    CONSTRAINT PK__sys_client___BFBDE87009ED2882 PRIMARY KEY CLUSTERED (id)
+        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+        ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'主建',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'客户端id' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'客户端key',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_key'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'客户端秘钥',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_secret'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'授权类型',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'grant_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'设备类型',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'device_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token活跃超时时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'active_timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token固定超时',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'状态(0正常 1停用)',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'删除标志(0代表存在 2代表删除)',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'创建部门' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'创建者',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'创建时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'更新者',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'更新时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'系统授权表',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client'
+GO
+
+INSERT INTO sys_client VALUES (N'1', N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+INSERT INTO sys_client VALUES (N'2', N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'app', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO

+ 70 - 0
script/sql/update/oracle/update_5.0-5.1.sql

@@ -72,3 +72,73 @@ comment on column  sys_social.create_time       is '创建时间';
 comment on column  sys_social.update_by         is '更新者';
 comment on column  sys_social.update_time       is '更新时间';
 comment on column  sys_social.del_flag          is '删除标志(0代表存在 2代表删除)';
+
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+create table sys_client (
+    id                  number(20)    not null,
+    client_id           varchar(64)   default null,
+    client_key          varchar(32)   default null,
+    client_secret       varchar(255)  default null,
+    grant_type          varchar(255)  default null,
+    device_type         varchar(32)   default null,
+    active_timeout      number(11)    default 1800,
+    timeout             number(11)    default 604800,
+    status              char(1)       default '0',
+    del_flag            char(1)       default '0',
+    create_dept         number(20)    default null,
+    create_by           number(20)    default null,
+    create_time         date,
+    update_by           number(20)    default null,
+    update_time         date
+)
+
+alter table sys_client add constraint pk_sys_client primary key (id);
+
+comment on table sys_client                         is '系统授权表';
+comment on column sys_client.id                     is '主建';
+comment on column sys_client.client_id              is '客户端id';
+comment on column sys_client.client_key             is '客户端key';
+comment on column sys_client.client_secret          is '客户端秘钥';
+comment on column sys_client.grant_type             is '授权类型';
+comment on column sys_client.device_type            is '设备类型';
+comment on column sys_client.active_timeout         is 'token活跃超时时间';
+comment on column sys_client.timeout                is 'token固定超时';
+comment on column sys_client.status                 is '状态(0正常 1停用)';
+comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
+comment on column sys_client.create_dept            is '创建部门';
+comment on column sys_client.create_by              is '创建者';
+comment on column sys_client.create_time            is '创建时间';
+comment on column sys_client.update_by              is '更新者';
+comment on column sys_client.update_time            is '更新时间';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, sysdate, 1, sysdate);
+
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, sysdate, null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, sysdate, null, null, '客户端设备类型');
+
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate, null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端',      'pc',        'sys_device_type',  '',   'default', 'N', '0', 103, 1, sysdate, null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端',     'app',       'sys_device_type',  '',   'default', 'N', '0', 103, 1, sysdate, null, null, 'APP端');
+
+-- 二级菜单
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', 1, 0, 'C', '0', '0', 'system:client:list',          'international', 103, 1, sysdate, null, null, '客户端管理菜单');
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', 1, 0, 'F', '0', '0', 'system:client:query',        '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', 1, 0, 'F', '0', '0', 'system:client:add',          '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', 1, 0, 'F', '0', '0', 'system:client:edit',         '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', 1, 0, 'F', '0', '0', 'system:client:remove',       '#', 103, 1, sysdate, null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', 1, 0, 'F', '0', '0', 'system:client:export',       '#', 103, 1, sysdate, null, null, '');
+
+-- 角色菜单权限
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');

+ 70 - 0
script/sql/update/postgres/update_5.0-5.1.sql

@@ -71,3 +71,73 @@ comment on column  sys_social.create_time       is '创建时间';
 comment on column  sys_social.update_by         is '更新者';
 comment on column  sys_social.update_time       is '更新时间';
 comment on column  sys_social.del_flag          is '删除标志(0代表存在 2代表删除)';
+
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+    id                  int8,
+    client_id           varchar(64)   ''::varchar,
+    client_key          varchar(32)   ''::varchar,
+    client_secret       varchar(255)  ''::varchar,
+    grant_type          varchar(255)  ''::varchar,
+    device_type         varchar(32)   ''::varchar,
+    active_timeout      int4          default 1800,
+    timeout             int4          default 604800,
+    status              char(1)       default '0'::bpchar,
+    del_flag            char(1)       default '0'::bpchar,
+    create_dept         int8,
+    create_by           int8,
+    create_time         timestamp,
+    update_by           int8,
+    update_time         timestamp,
+    constraint sys_client_pk primary key (id)
+)
+
+comment on table sys_client                         is '系统授权表';
+comment on column sys_client.id                     is '主建';
+comment on column sys_client.client_id              is '客户端id';
+comment on column sys_client.client_key             is '客户端key';
+comment on column sys_client.client_secret          is '客户端秘钥';
+comment on column sys_client.grant_type             is '授权类型';
+comment on column sys_client.device_type            is '设备类型';
+comment on column sys_client.active_timeout         is 'token活跃超时时间';
+comment on column sys_client.timeout                is 'token固定超时';
+comment on column sys_client.status                 is '状态(0正常 1停用)';
+comment on column sys_client.del_flag               is '删除标志(0代表存在 2代表删除)';
+comment on column sys_client.create_dept            is '创建部门';
+comment on column sys_client.create_by              is '创建者';
+comment on column sys_client.create_time            is '创建时间';
+comment on column sys_client.update_by              is '更新者';
+comment on column sys_client.update_time            is '更新时间';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, now(), 1, now());
+
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, now(), null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, now(), null, null, '客户端设备类型');
+
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端', 'pc',            'sys_device_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端', 'app',          'sys_device_type',   '',   'default', 'N', '0', 103, 1, now(), null, null, 'APP端');
+
+-- 二级菜单
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', '1', '0', 'C', '0', '0', 'system:client:list',          'international', 103, 1, now(), null, null, '客户端管理菜单');
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', '1', '0', 'F', '0', '0', 'system:client:query',        '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', '1', '0', 'F', '0', '0', 'system:client:add',          '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', '1', '0', 'F', '0', '0', 'system:client:edit',         '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', '1', '0', 'F', '0', '0', 'system:client:remove',       '#', 103, 1, now(), null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', '1', '0', 'F', '0', '0', 'system:client:export',       '#', 103, 1, now(), null, null, '');
+
+-- 角色菜单权限
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');

+ 174 - 0
script/sql/update/sqlserver/update_5.0-5.1.sql

@@ -229,3 +229,177 @@ EXEC sys.sp_addextendedproperty
     'TABLE', N'sys_social',
     'COLUMN', N'update_time'
 GO
+
+
+CREATE TABLE sys_client
+(
+    id                  bigint                              NOT NULL,
+    client_id           nvarchar(20)  DEFAULT ''            NULL,
+    client_key          nvarchar(255) DEFAULT ''            NULL,
+    client_secret       nvarchar(255) DEFAULT ''            NULL,
+    grant_type          nvarchar(255) DEFAULT ''            NULL,
+    device_type         nvarchar(32) DEFAULT ''            NULL,
+    active_timeout      int           DEFAULT ((1800))      NULL,
+    timeout             int           DEFAULT ((604800))    NULL,
+    status              nchar(1)      DEFAULT ('0')         NULL,
+    del_flag            nchar(1)      DEFAULT ('0')         NULL,
+    create_dept         bigint                              NULL,
+    create_by           bigint                              NULL,
+    create_time         datetime2(7)                        NULL,
+    update_by           bigint                              NULL,
+    update_time         datetime2(7)                        NULL
+    CONSTRAINT PK__sys_client___BFBDE87009ED2882 PRIMARY KEY CLUSTERED (id)
+        WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
+        ON [PRIMARY]
+)
+ON [PRIMARY]
+GO
+
+EXEC sp_addextendedproperty
+'MS_Description', N'主建',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'id'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'客户端id' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'客户端key',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_key'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'客户端秘钥',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'client_secret'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'授权类型',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'grant_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'设备类型',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'device_type'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token活跃超时时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'active_timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'token固定超时',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'timeout'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'状态(0正常 1停用)',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'删除标志(0代表存在 2代表删除)',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'del_flag'
+GO
+EXEC sys.sp_addextendedproperty
+'MS_Description', N'创建部门' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_dept'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'创建者',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'创建时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'create_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'更新者',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_by'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'更新时间',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'update_time'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'系统授权表',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client'
+GO
+
+INSERT INTO sys_client VALUES (N'1', N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+INSERT INTO sys_client VALUES (N'2', N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'app', 1800, 604800, N'0', N'0', 103, 1, getdate(), 1, getdate())
+GO
+
+INSERT sys_dict_type VALUES (11, N'000000', N'授权类型', N'sys_grant_type', N'0', 103, 1, getdate(), NULL, NULL, N'认证授权类型')
+GO
+INSERT sys_dict_type VALUES (12, N'000000', N'设备类型', N'sys_device_type', N'0', 103, 1, getdate(), NULL, NULL, N'客户端设备类型')
+GO
+
+INSERT sys_dict_data VALUES (30, N'000000', 0, N'密码认证', N'password', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'密码认证');
+GO
+INSERT sys_dict_data VALUES (31, N'000000', 0, N'短信认证', N'sms', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'短信认证')
+GO
+INSERT sys_dict_data VALUES (32, N'000000', 0, N'邮件认证', N'email', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'邮件认证')
+GO
+INSERT sys_dict_data VALUES (33, N'000000', 0, N'小程序认证', N'xcx', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'小程序认证')
+GO
+INSERT sys_dict_data VALUES (34, N'000000', 0, N'三方登录认证', N'`social`', N'sys_grant_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'三方登录认证')
+GO
+INSERT sys_dict_data VALUES (35, N'000000', 0, N'PC端', N'`pc`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'PC端')
+GO
+INSERT sys_dict_data VALUES (36, N'000000', 0, N'APP端', N'`app`', N'sys_device_type', N'', N'default', N'N', N'0', 103, 1, getdate(), NULL, NULL, N'APP端')
+GO
+
+-- 二级菜单
+INSERT sys_menu VALUES (123, N'客户端管理', 1, 1, N'client', N'system/client/index', N'', 1, 0, N'C', N'0', N'0', N'system:client:list', N'international', 103, 1, getdate(), NULL, NULL, N'客户端管理菜单')
+GO
+-- 客户端管理按钮
+INSERT sys_menu VALUES (1061, N'客户端管理查询', 123, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:query', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1062, N'客户端管理新增', 123, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:add', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1063, N'客户端管理修改', 123, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:edit', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1064, N'客户端管理删除', 123, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+INSERT sys_menu VALUES (1065, N'客户端管理导出', 123, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:client:export', N'#', 103, 1, getdate(), NULL, NULL, N'');
+GO
+
+
+-- 角色菜单权限
+INSERT sys_role_menu VALUES (2, 1061)
+GO
+INSERT sys_role_menu VALUES (2, 1062)
+GO
+INSERT sys_role_menu VALUES (2, 1063)
+GO
+INSERT sys_role_menu VALUES (2, 1064)
+GO
+INSERT sys_role_menu VALUES (2, 1065)
+GO

+ 53 - 0
script/sql/update/update_5.0-5.1.sql

@@ -39,3 +39,56 @@ create table sys_social
     del_flag           char(1)          default '0'     comment '删除标志(0代表存在 2代表删除)',
     PRIMARY KEY (id)
 ) engine=innodb comment = '社会化关系表';
+
+-- ----------------------------
+-- 系统授权表
+-- ----------------------------
+drop table if exists sys_client;
+create table sys_client (
+    id                  bigint(20)    not null            comment 'id',
+    client_id           varchar(64)   default null        comment '客户端id',
+    client_key          varchar(32)   default null        comment '客户端key',
+    client_secret       varchar(255)  default null        comment '客户端秘钥',
+    grant_type          varchar(255)  default null        comment '授权类型',
+    device_type         varchar(32)   default null        comment '设备类型',
+    active_timeout      int(11)       default 1800        comment 'token活跃超时时间',
+    timeout             int(11)       default 604800      comment 'token固定超时',
+    status              char(1)       default '0'         comment '状态(0正常 1停用)',
+    del_flag            char(1)       default '0'         comment '删除标志(0代表存在 2代表删除)',
+    create_dept         bigint(20)    default null        comment '创建部门',
+    create_by           bigint(20)    default null        comment '创建者',
+    create_time         datetime      default null        comment '创建时间',
+    update_by           bigint(20)    default null        comment '更新者',
+    update_time         datetime      default null        comment '更新时间',
+    primary key (id)
+) engine=innodb comment='系统授权表';
+
+insert into sys_client values (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+insert into sys_client values (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'app', 1800, 604800, 0, 0, 103, 1, sysdate(), 1, sysdate());
+
+insert into sys_dict_type values(11, '000000', '授权类型', 'sys_grant_type',     '0', 103, 1, sysdate(), null, null, '认证授权类型');
+insert into sys_dict_type values(12, '000000', '设备类型', 'sys_device_type',    '0', 103, 1, sysdate(), null, null, '客户端设备类型');
+
+insert into sys_dict_data values(30, '000000', 0,  '密码认证', 'password',   'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '密码认证');
+insert into sys_dict_data values(31, '000000', 0,  '短信认证', 'sms',        'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '短信认证');
+insert into sys_dict_data values(32, '000000', 0,  '邮件认证', 'email',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '邮件认证');
+insert into sys_dict_data values(33, '000000', 0,  '小程序认证', 'xcx',      'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '小程序认证');
+insert into sys_dict_data values(34, '000000', 0,  '三方登录认证', 'social', 'sys_grant_type',   '',   'default', 'N', '0', 103, 1, sysdate(), null, null, '三方登录认证');
+insert into sys_dict_data values(35, '000000', 0,  'PC端', 'pc',          'sys_device_type',     '',   'default', 'N', '0', 103, 1, sysdate(), null, null, 'PC端');
+insert into sys_dict_data values(36, '000000', 0,  'APP端', 'app',        'sys_device_type',     '',   'default', 'N', '0', 103, 1, sysdate(), null, null, 'APP端');
+
+-- 二级菜单
+insert into sys_menu values('123',  '客户端管理',   '1',   '1', 'client',           'system/client/index',          '', 1, 0, 'C', '0', '0', 'system:client:list',          'international', 103, 1, sysdate(), null, null, '客户端管理菜单');
+-- 客户端管理按钮
+insert into sys_menu values('1061', '客户端管理查询', '123', '1',  '#', '', 1, 0, 'F', '0', '0', 'system:client:query',        '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1062', '客户端管理新增', '123', '2',  '#', '', 1, 0, 'F', '0', '0', 'system:client:add',          '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1063', '客户端管理修改', '123', '3',  '#', '', 1, 0, 'F', '0', '0', 'system:client:edit',         '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1064', '客户端管理删除', '123', '4',  '#', '', 1, 0, 'F', '0', '0', 'system:client:remove',       '#', 103, 1, sysdate(), null, null, '');
+insert into sys_menu values('1065', '客户端管理导出', '123', '5',  '#', '', 1, 0, 'F', '0', '0', 'system:client:export',       '#', 103, 1, sysdate(), null, null, '');
+
+-- 角色菜单权限
+insert into sys_role_menu values ('2', '1061');
+insert into sys_role_menu values ('2', '1062');
+insert into sys_role_menu values ('2', '1063');
+insert into sys_role_menu values ('2', '1064');
+insert into sys_role_menu values ('2', '1065');