From b0909dbe3ddd7fb06180704c241b4cebabff6665 Mon Sep 17 00:00:00 2001
From: 疯狂的狮子Li <15040126243@163.com>
Date: 星期三, 28 六月 2023 14:27:13 +0800
Subject: [PATCH] !379 合并 客户端授权功能

---
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java                |   15 
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java                 |   90 ++
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java       |   66 +
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java     |    7 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java    |    7 
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java     |  144 +++
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java       |    7 
 ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties                                          |    3 
 script/sql/update/update_5.0-5.1.sql                                                                   |   53 +
 ruoyi-admin/src/main/resources/i18n/messages_en_US.properties                                          |    3 
 script/sql/update/oracle/update_5.0-5.1.sql                                                            |   70 +
 ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml                        |    7 
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java |  115 ++
 script/sql/oracle/oracle_ry_vue_5.X.sql                                                                |   69 +
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java   |   22 
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java                      |   77 +
 script/sql/update/sqlserver/update_5.0-5.1.sql                                                         |  174 ++++
 ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java                               |   89 -
 ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java                                       |   41 +
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java                 |   80 ++
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java             |   60 +
 ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java      |   34 
 script/sql/ry_vue_5.X.sql                                                                              |   54 +
 script/sql/update/postgres/update_5.0-5.1.sql                                                          |   70 +
 ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java                                 |  226 -----
 ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java                          |  109 ++
 script/sql/sqlserver/sqlserver_ry_vue_5.X.sql                                                          |  167 ++++
 ruoyi-admin/src/main/resources/application.yml                                                         |    1 
 ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java                                   |   45 +
 script/sql/postgres/postgres_ry_vue_5.X.sql                                                            |   70 +
 ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java                            |   88 ++
 ruoyi-admin/src/main/resources/i18n/messages.properties                                                |    3 
 ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java                       |  130 +++
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java       |    5 
 ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java                            |  109 ++
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java  |    7 
 36 files changed, 1,999 insertions(+), 318 deletions(-)

diff --git a/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
index 51160aa..9032d27 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
@@ -4,33 +4,35 @@
 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 @@
  *
  * @author Lion Li
  */
+@Slf4j
 @SaIgnore
 @Validated
 @RequiredArgsConstructor
@@ -57,77 +60,31 @@
     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);
+    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("瀹㈡埛绔痠d: {} 璁よ瘉绫诲瀷锛歿} 寮傚父!.", clientId, grantType);
+            return R.fail(MessageUtils.message("auth.grant.type.error"));
+        }
+        // 鏍¢獙绉熸埛
+        loginService.checkTenant(loginBody.getTenantId());
+        // 鐧诲綍
+        return R.ok(IAuthStrategy.login(loginBody, client));
     }
-
-    /**
-     * 鐭俊鐧诲綍
-     *
-     * @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 灏忕▼搴廲ode
-     * @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);
-    }
-
 
     /**
      * 璁よ瘉鎺堟潈
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java b/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java
index ef5dac0..834afe5 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/LoginVo.java
+++ b/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 @@
 @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;
 
 }
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/IAuthStrategy.java
new file mode 100644
index 0000000..690f740
--- /dev/null
+++ b/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);
+
+}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java b/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
index 52bf56c..88500b2 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
+++ b/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.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.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 @@
 @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 @@
     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 @@
      * @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 @@
         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 @@
         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 @@
     /**
      * 鐧诲綍鏍¢獙
      */
-    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 @@
         RedisUtils.deleteObject(errorKey);
     }
 
-    private void checkTenant(String tenantId) {
+    /**
+     * 鏍¢獙绉熸埛
+     *
+     * @param tenantId 绉熸埛ID
+     */
+    public void checkTenant(String tenantId) {
         if (!TenantHelper.isEnable()) {
             return;
         }
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/EmailAuthStrategy.java
new file mode 100644
index 0000000..58ac0a9
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
new file mode 100644
index 0000000..7076d80
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SmsAuthStrategy.java
new file mode 100644
index 0000000..1c810fd
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/XcxAuthStrategy.java
new file mode 100644
index 0000000..f02f752
--- /dev/null
+++ b/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;
+    }
+
+}
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 91429f3..fcc1865 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -140,6 +140,7 @@
     - sys_role_menu
     - sys_user_post
     - sys_user_role
+    - sys_client
 
 # MyBatisPlus閰嶇疆
 # https://baomidou.com/config/
diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties
index 4f2da28..5c9f422 100644
--- a/ruoyi-admin/src/main/resources/i18n/messages.properties
+++ b/ruoyi-admin/src/main/resources/i18n/messages.properties
@@ -28,6 +28,9 @@
 user.notfound=璇烽噸鏂扮櫥褰�
 user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
 user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
 ##鏂囦欢涓婁紶娑堟伅
 upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
 upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
diff --git a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
index 0c738a3..2ab82a6 100644
--- a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
+++ b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
@@ -28,6 +28,9 @@
 user.notfound=Please login again
 user.forcelogout=The administrator is forced to exit锛宲lease 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
diff --git a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
index 4f2da28..5c9f422 100644
--- a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
+++ b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
@@ -28,6 +28,9 @@
 user.notfound=璇烽噸鏂扮櫥褰�
 user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
 user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
 ##鏂囦欢涓婁紶娑堟伅
 upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
 upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
index 73f678c..09842a9 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
+++ b/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;
@@ -16,6 +21,28 @@
 public class LoginBody {
 
     /**
+     * 瀹㈡埛绔痠d
+     */
+    @NotBlank(message = "{auth.clientid.not.blank}")
+    private String clientId;
+
+    /**
+     * 瀹㈡埛绔痥ey
+     */
+    private String clientKey;
+
+    /**
+     * 瀹㈡埛绔閽�
+     */
+    private String clientSecret;
+
+    /**
+     * 鎺堟潈绫诲瀷
+     */
+    @NotBlank(message = "{auth.grant.type.not.blank}")
+    private String grantType;
+
+    /**
      * 绉熸埛ID
      */
     @NotBlank(message = "{tenant.number.not.blank}")
@@ -24,15 +51,15 @@
     /**
      * 鐢ㄦ埛鍚�
      */
-    @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 @@
      */
     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;
+
+    /**
+     * 灏忕▼搴廲ode
+     */
+    @NotBlank(message = "{xcx.code.not.blank}", groups = {WechatGroup.class})
+    private String xcxCode;
+
 }
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java
new file mode 100644
index 0000000..130932f
--- /dev/null
+++ b/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 {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java
new file mode 100644
index 0000000..1516172
--- /dev/null
+++ b/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 {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java
new file mode 100644
index 0000000..d562860
--- /dev/null
+++ b/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 {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java
new file mode 100644
index 0000000..b397f18
--- /dev/null
+++ b/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 {
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
index 3b52695..b948ea7 100644
--- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
@@ -37,14 +37,26 @@
 
     @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);
+        // 浠嶫ava杈撳嚭鑷矱xcel鏄痗ode杞瑃ext
+        // 鍥犳浠嶦xcel杞琂ava搴旇灏唗ext涓巆ode瀵硅皟
+        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+        // 搴旇浠巘ext -> code涓煡鎵�
+        Object codeValue = enumTextToCodeMap.get(textValue);
+        return Convert.convert(contentProperty.getField().getType(), codeValue);
     }
 
     @Override
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
index 929d124..9ee4216 100644
--- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -7,13 +7,12 @@
 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;
 
@@ -37,40 +36,21 @@
     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);
     }
 
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java
new file mode 100644
index 0000000..61d30c7
--- /dev/null
+++ b/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));
+    }
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysClient.java
new file mode 100644
index 0000000..0f681be
--- /dev/null
+++ b/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;
+
+    /**
+     * 瀹㈡埛绔痠d
+     */
+    private String clientId;
+
+    /**
+     * 瀹㈡埛绔痥ey
+     */
+    private String clientKey;
+
+    /**
+     * 瀹㈡埛绔閽�
+     */
+    private String clientSecret;
+
+    /**
+     * 鎺堟潈绫诲瀷
+     */
+    private String grantType;
+
+    /**
+     * 璁惧绫诲瀷
+     */
+    private String deviceType;
+
+    /**
+     * token娲昏穬瓒呮椂鏃堕棿
+     */
+    private Long activeTimeout;
+
+    /**
+     * token鍥哄畾瓒呮椂鏃堕棿
+     */
+    private Long timeout;
+
+    /**
+     * 鐘舵�侊紙0姝e父 1鍋滅敤锛�
+     */
+    private String status;
+
+    /**
+     * 鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysClientBo.java
new file mode 100644
index 0000000..e5f5ffa
--- /dev/null
+++ b/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;
+
+    /**
+     * 瀹㈡埛绔痠d
+     */
+    private String clientId;
+
+    /**
+     * 瀹㈡埛绔痥ey
+     */
+    @NotBlank(message = "瀹㈡埛绔痥ey涓嶈兘涓虹┖", 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姝e父 1鍋滅敤锛�
+     */
+    private String status;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysClientVo.java
new file mode 100644
index 0000000..34f24eb
--- /dev/null
+++ b/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;
+
+    /**
+     * 瀹㈡埛绔痠d
+     */
+    @ExcelProperty(value = "瀹㈡埛绔痠d")
+    private String clientId;
+
+    /**
+     * 瀹㈡埛绔痥ey
+     */
+    @ExcelProperty(value = "瀹㈡埛绔痥ey")
+    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姝e父 1鍋滅敤锛�
+     */
+    @ExcelProperty(value = "鐘舵��", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=姝e父,1=鍋滅敤")
+    private String status;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysClientMapper.java
new file mode 100644
index 0000000..15bcfb4
--- /dev/null
+++ b/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> {
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysClientService.java
new file mode 100644
index 0000000..db48d4b
--- /dev/null
+++ b/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;
+
+/**
+ * 瀹㈡埛绔鐞哠ervice鎺ュ彛
+ *
+ * @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);
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysClientServiceImpl.java
new file mode 100644
index 0000000..c635675
--- /dev/null
+++ b/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;
+
+/**
+ * 瀹㈡埛绔鐞哠ervice涓氬姟灞傚鐞�
+ *
+ * @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;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
index 859c941..7b8e8f9 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
@@ -453,6 +453,11 @@
 
     @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;
diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysClientMapper.xml
new file mode 100644
index 0000000..fd150ad
--- /dev/null
+++ b/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>
diff --git a/script/sql/oracle/oracle_ry_vue_5.X.sql b/script/sql/oracle/oracle_ry_vue_5.X.sql
index bbc6d96..69efffc 100644
--- a/script/sql/oracle/oracle_ry_vue_5.X.sql
+++ b/script/sql/oracle/oracle_ry_vue_5.X.sql
@@ -439,7 +439,8 @@
 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',  '浠g爜鐢熸垚',     '3',   '2', 'gen',              'tool/gen/index',               '', 1, 0, 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, sysdate, null, null, '浠g爜鐢熸垚鑿滃崟');
 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('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', '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(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(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, '姝e父鐘舵��');
 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 (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 '瀹㈡埛绔痠d';
+comment on column sys_client.client_key             is '瀹㈡埛绔痥ey';
+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姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag               is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+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);
+
 
 -- ----------------------------
 -- 閽╁瓙 锛岀敤浜巗ession杩炴帴涔嬪悗 鑷姩璁剧疆榛樿鐨刣ate绫诲瀷鏍煎紡鍖� 绠�鍖栨椂闂存煡璇�
diff --git a/script/sql/postgres/postgres_ry_vue_5.X.sql b/script/sql/postgres/postgres_ry_vue_5.X.sql
index a5123ce..e632f99 100644
--- a/script/sql/postgres/postgres_ry_vue_5.X.sql
+++ b/script/sql/postgres/postgres_ry_vue_5.X.sql
@@ -447,7 +447,8 @@
 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',  '浠g爜鐢熸垚',     '3',   '2', 'gen',              'tool/gen/index',               '', '1', '0', 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, now(), null, null, '浠g爜鐢熸垚鑿滃崟');
 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('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', '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(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(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, '姝e父鐘舵��');
 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 (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 '瀹㈡埛绔痠d';
+comment on column sys_client.client_key             is '瀹㈡埛绔痥ey';
+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姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag               is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+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');
diff --git a/script/sql/ry_vue_5.X.sql b/script/sql/ry_vue_5.X.sql
index 0fd115a..ff7b428 100644
--- a/script/sql/ry_vue_5.X.sql
+++ b/script/sql/ry_vue_5.X.sql
@@ -283,8 +283,9 @@
 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',  '浠g爜鐢熸垚',     '3',   '2', 'gen',              'tool/gen/index',               '', 1, 0, 'C', '0', '0', 'tool:gen:list',               'code',          103, 1, sysdate(), null, null, '浠g爜鐢熸垚鑿滃崟');
-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 ('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', '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(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(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, '姝e父鐘舵��');
 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 (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 '瀹㈡埛绔痠d',
+    client_key          varchar(32)   default null        comment '瀹㈡埛绔痥ey',
+    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姝e父 1鍋滅敤锛�',
+    del_flag            char(1)       default '0'         comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+    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());
diff --git a/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql b/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
index 015e926..f6c87b4 100644
--- a/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
+++ b/script/sql/sqlserver/sqlserver_ry_vue_5.X.sql
@@ -1259,6 +1259,20 @@
 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
 (
@@ -1374,6 +1388,10 @@
 INSERT sys_dict_type VALUES (9, N'000000', N'鎿嶄綔绫诲瀷', N'sys_oper_type', N'0', 103, 1, getdate(), NULL, NULL, 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 @@
 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'鏂囦欢绠$悊鑿滃崟');
@@ -1815,6 +1835,17 @@
 INSERT sys_menu VALUES (1614, N'绉熸埛濂楅鍒犻櫎', 122, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'system:tenantPackage:remove', N'#', 103, 1, getdate(), NULL, NULL, N'');
 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 @@
 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 @@
 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'瀹㈡埛绔痠d' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹㈡埛绔痥ey',
+'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姝e父 1鍋滅敤锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+'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
diff --git a/script/sql/update/oracle/update_5.0-5.1.sql b/script/sql/update/oracle/update_5.0-5.1.sql
index 9aa6d97..c3e7178 100644
--- a/script/sql/update/oracle/update_5.0-5.1.sql
+++ b/script/sql/update/oracle/update_5.0-5.1.sql
@@ -72,3 +72,73 @@
 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浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+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 '瀹㈡埛绔痠d';
+comment on column sys_client.client_key             is '瀹㈡埛绔痥ey';
+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姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag               is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+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');
diff --git a/script/sql/update/postgres/update_5.0-5.1.sql b/script/sql/update/postgres/update_5.0-5.1.sql
index 9d94230..c834b98 100644
--- a/script/sql/update/postgres/update_5.0-5.1.sql
+++ b/script/sql/update/postgres/update_5.0-5.1.sql
@@ -71,3 +71,73 @@
 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浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+
+-- ----------------------------
+-- 绯荤粺鎺堟潈琛�
+-- ----------------------------
+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 '瀹㈡埛绔痠d';
+comment on column sys_client.client_key             is '瀹㈡埛绔痥ey';
+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姝e父 1鍋滅敤锛�';
+comment on column sys_client.del_flag               is '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�';
+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');
diff --git a/script/sql/update/sqlserver/update_5.0-5.1.sql b/script/sql/update/sqlserver/update_5.0-5.1.sql
index 3421a9b..f240688 100644
--- a/script/sql/update/sqlserver/update_5.0-5.1.sql
+++ b/script/sql/update/sqlserver/update_5.0-5.1.sql
@@ -229,3 +229,177 @@
     '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'瀹㈡埛绔痠d' ,
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'tenant_id'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'瀹㈡埛绔痥ey',
+'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姝e父 1鍋滅敤锛�',
+'SCHEMA', N'dbo',
+'TABLE', N'sys_client',
+'COLUMN', N'status'
+GO
+EXEC sp_addextendedproperty
+'MS_Description', N'鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+'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
diff --git a/script/sql/update/update_5.0-5.1.sql b/script/sql/update/update_5.0-5.1.sql
index 353ca21..6654ed4 100644
--- a/script/sql/update/update_5.0-5.1.sql
+++ b/script/sql/update/update_5.0-5.1.sql
@@ -39,3 +39,56 @@
     del_flag           char(1)          default '0'     comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
     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 '瀹㈡埛绔痠d',
+    client_key          varchar(32)   default null        comment '瀹㈡埛绔痥ey',
+    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姝e父 1鍋滅敤锛�',
+    del_flag            char(1)       default '0'         comment '鍒犻櫎鏍囧織锛�0浠h〃瀛樺湪 2浠h〃鍒犻櫎锛�',
+    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');

--
Gitblit v1.9.3