From 95f2617a4c086e3e5113799fe213c24c42bfbd17 Mon Sep 17 00:00:00 2001 From: KAI <1373639299@qq.com> Date: Thu, 26 Dec 2024 02:18:34 +0000 Subject: [PATCH] =?UTF-8?q?feat(system/client):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/common/constant/SysConstants.java | 2 +- .../admin/common/context/UserContext.java | 10 + .../top/continew/admin/auth/AuthHandler.java | 55 ++++++ .../admin/auth/config/AuthHandlerContext.java | 53 ++++++ .../admin/auth/enums/AuthTypeEnum.java | 57 ++++++ .../auth/handler/AbstractAuthHandler.java | 137 ++++++++++++++ .../auth/handler/AccountAuthHandler.java | 120 ++++++++++++ .../admin/auth/handler/EmailAuthHandler.java | 107 +++++++++++ .../admin/auth/handler/PhoneAuthHandler.java | 109 +++++++++++ .../admin/auth/handler/SocialAuthHandler.java | 172 ++++++++++++++++++ ...countLoginReq.java => AccountAuthReq.java} | 2 +- .../admin/auth/model/req/AuthReq.java | 46 +++++ .../{EmailLoginReq.java => EmailAuthReq.java} | 2 +- .../{PhoneLoginReq.java => PhoneAuthReq.java} | 2 +- .../admin/auth/model/req/SocialAuthReq.java | 49 +++++ .../admin/auth/service/LoginService.java | 38 ++-- .../auth/service/impl/LoginServiceImpl.java | 38 +--- .../admin/system/mapper/ClientMapper.java | 28 +++ .../admin/system/model/entity/ClientDO.java | 82 +++++++++ .../admin/system/model/query/ClientQuery.java | 75 ++++++++ .../admin/system/model/req/ClientReq.java | 97 ++++++++++ .../system/model/resp/ClientDetailResp.java | 98 ++++++++++ .../admin/system/model/resp/ClientResp.java | 87 +++++++++ .../admin/system/service/ClientService.java | 35 ++++ .../service/impl/ClientServiceImpl.java | 76 ++++++++ .../admin/config/log/LogDaoLocalImpl.java | 12 +- .../admin/controller/auth/AuthController.java | 92 ++++------ .../controller/system/ClientController.java | 40 ++++ .../db/changelog/mysql/main_data.sql | 27 +++ .../db/changelog/mysql/main_table.sql | 17 ++ 30 files changed, 1651 insertions(+), 114 deletions(-) create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/AuthHandler.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/config/AuthHandlerContext.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/enums/AuthTypeEnum.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/handler/AbstractAuthHandler.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/handler/AccountAuthHandler.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/handler/EmailAuthHandler.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/handler/PhoneAuthHandler.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/handler/SocialAuthHandler.java rename continew-module-system/src/main/java/top/continew/admin/auth/model/req/{AccountLoginReq.java => AccountAuthReq.java} (96%) create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/model/req/AuthReq.java rename continew-module-system/src/main/java/top/continew/admin/auth/model/req/{EmailLoginReq.java => EmailAuthReq.java} (96%) rename continew-module-system/src/main/java/top/continew/admin/auth/model/req/{PhoneLoginReq.java => PhoneAuthReq.java} (96%) create mode 100644 continew-module-system/src/main/java/top/continew/admin/auth/model/req/SocialAuthReq.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/mapper/ClientMapper.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/query/ClientQuery.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientDetailResp.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/service/ClientService.java create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java create mode 100644 continew-webapi/src/main/java/top/continew/admin/controller/system/ClientController.java diff --git a/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java b/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java index 8d75579c..2e9ad5c3 100644 --- a/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java +++ b/continew-common/src/main/java/top/continew/admin/common/constant/SysConstants.java @@ -67,7 +67,7 @@ public class SysConstants { /** * 账号登录 URI */ - public static final String LOGIN_URI = "/auth/account"; + public static final String LOGIN_URI = "/auth/login"; /** * 退出 URI diff --git a/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java b/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java index 6646201c..4495a8c3 100644 --- a/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java +++ b/continew-common/src/main/java/top/continew/admin/common/context/UserContext.java @@ -80,6 +80,16 @@ public class UserContext implements Serializable { */ private Set roles; + /** + * 设备类型 + */ + private String clientType; + + /** + * 客户端ID + */ + private String clientId; + public UserContext(Set permissions, Set roles, Integer passwordExpirationDays) { this.permissions = permissions; this.setRoles(roles); diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/AuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/AuthHandler.java new file mode 100644 index 00000000..e46e3a50 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/AuthHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth; + +import jakarta.servlet.http.HttpServletRequest; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.AuthReq; +import top.continew.admin.system.model.resp.ClientResp; + +/** + * 认证接口 + * + * @author KAI + * @since 2024/12/22 14:52:23 + */ +public interface AuthHandler { + + /** + * 执行登录 + * + * @param authReq 登录请求参数 + * @param request HTTP请求对象 + * @return 登录响应 + */ + R login(T authReq, ClientResp clientResp, HttpServletRequest request); + + /** + * 获取登录类型 + * + * @return 登录类型Enum + */ + AuthTypeEnum getAuthType(); + + /** + * 校验参数 + * + * @param authReq 登录请求参数 + */ + void validate(T authReq); + +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/config/AuthHandlerContext.java b/continew-module-system/src/main/java/top/continew/admin/auth/config/AuthHandlerContext.java new file mode 100644 index 00000000..f958de5f --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/config/AuthHandlerContext.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import top.continew.admin.auth.model.req.AuthReq; +import top.continew.admin.auth.AuthHandler; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 登录类型策略上文 + * + * @author KAI + * @since 2024/12/20 15:16:55 + */ +@Component +public class AuthHandlerContext { + private final Map> handlerMap = new HashMap<>(); + + @Autowired + public AuthHandlerContext(List> strategies) { + for (AuthHandler strategy : strategies) { + handlerMap.put(strategy.getAuthType().getValue(), strategy); + } + } + + @SuppressWarnings("unchecked") + public AuthHandler getHandler(String type) { + AuthHandler strategy = handlerMap.get(type); + if (strategy == null) { + throw new IllegalArgumentException("No handler found for type: " + type); + } + return (AuthHandler)strategy; + } +} diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/enums/AuthTypeEnum.java b/continew-module-system/src/main/java/top/continew/admin/auth/enums/AuthTypeEnum.java new file mode 100644 index 00000000..92edad69 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/enums/AuthTypeEnum.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import top.continew.admin.common.constant.UiConstants; +import top.continew.starter.core.enums.BaseEnum; + +/** + * 认证类型 + * + * @author Charles7c + * @since 2023/12/23 13:38 + */ +@Getter +@RequiredArgsConstructor +public enum AuthTypeEnum implements BaseEnum { + + /** + * 账号 + */ + ACCOUNT("account", "账号", UiConstants.COLOR_ERROR), + + /** + * 邮箱 + */ + EMAIL("email", "邮箱", UiConstants.COLOR_PRIMARY), + + /** + * 手机号 + */ + PHONE("phone", "手机号", UiConstants.COLOR_SUCCESS), + + /** + * 第三方授权 + */ + SOCIAL_AUTH("socialAuth", "第三方授权", UiConstants.COLOR_DEFAULT); + + private final String value; + private final String description; + private final String color; +} diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/handler/AbstractAuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/handler/AbstractAuthHandler.java new file mode 100644 index 00000000..cd7c46e9 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/handler/AbstractAuthHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.handler; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; +import top.continew.admin.common.context.RoleContext; +import top.continew.admin.common.context.UserContext; +import top.continew.admin.common.context.UserContextHolder; +import top.continew.admin.common.context.UserExtraContext; +import top.continew.admin.system.model.entity.UserDO; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.OptionService; +import top.continew.admin.system.service.RoleService; +import top.continew.starter.web.util.SpringWebUtils; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS; + +/** + * 认证处理器抽象类 + * + * @author KAI + * @since 2024/12/22 14:52 + */ +@Component +@RequiredArgsConstructor +public abstract class AbstractAuthHandler { + @Resource + private RoleService roleService; + @Resource + private OptionService optionService; + @Resource + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + public static final String CAPTCHA_EXPIRED = "验证码已失效"; + public static final String CAPTCHA_ERROR = "验证码错误"; + public static final String CLIENT_ID = "clientId"; + + /** + * 获取登录凭证 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + * @return token 认证信息 + */ + protected String authCertificate(UserDO user, ClientResp clientResp) { + preLogin(user, clientResp); + // 核心登录逻辑 + Long userId = user.getId(); + CompletableFuture> permissionFuture = CompletableFuture.supplyAsync(() -> roleService + .listPermissionByUserId(userId), threadPoolTaskExecutor); + CompletableFuture> roleFuture = CompletableFuture.supplyAsync(() -> roleService + .listByUserId(userId), threadPoolTaskExecutor); + CompletableFuture passwordExpirationDaysFuture = CompletableFuture.supplyAsync(() -> optionService + .getValueByCode2Int(PASSWORD_EXPIRATION_DAYS.name())); + CompletableFuture.allOf(permissionFuture, roleFuture, passwordExpirationDaysFuture); + + UserContext userContext = new UserContext(permissionFuture.join(), roleFuture + .join(), passwordExpirationDaysFuture.join()); + + BeanUtil.copyProperties(user, userContext); + SaLoginModel model = new SaLoginModel(); + // 设置登录 token 最低活跃频率 如未指定,则使用全局配置的 activeTimeout 值 + model.setActiveTimeout(clientResp.getActiveTimeout()); + // 设置登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值 + model.setTimeout(clientResp.getTimeout()); + // 设置设备类型 + model.setDevice(clientResp.getClientType()); + userContext.setClientType(clientResp.getClientType()); + // 设置客户端id + userContext.setClientId(clientResp.getClientId()); + model.setExtra(CLIENT_ID, clientResp.getClientId()); + // 自定义用户上下文处理 + customizeUserContext(userContext, user, clientResp); + + // 登录并缓存用户信息 + StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils + .getRequest())))); + UserContextHolder.setContext(userContext); + + // 后置处理 + String token = StpUtil.getTokenValue(); + postLogin(token, user, clientResp); + return token; + } + + /** + * 登录前置处理 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + */ + private void preLogin(UserDO user, ClientResp clientResp) { + } + + /** + * 自定义用户上下文处理 + * + * @param userContext 用户上下文 + * @param user 用户信息 + * @param clientResp 客户端信息 + */ + protected void customizeUserContext(UserContext userContext, UserDO user, ClientResp clientResp) { + } + + /** + * 登录后置处理 + * + * @param token 登录令牌 + * @param user 用户信息 + * @param clientResp 客户端信息 + */ + protected void postLogin(String token, UserDO user, ClientResp clientResp) { + } +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/handler/AccountAuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/handler/AccountAuthHandler.java new file mode 100644 index 00000000..ebfefc2b --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/handler/AccountAuthHandler.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.handler; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.AccountAuthReq; +import top.continew.admin.auth.model.resp.LoginResp; +import top.continew.admin.auth.service.LoginService; +import top.continew.admin.auth.AuthHandler; +import top.continew.admin.common.constant.SysConstants; +import top.continew.admin.common.util.SecureUtils; +import top.continew.admin.system.model.entity.UserDO; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.OptionService; +import top.continew.admin.system.service.UserService; +import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.validation.ValidationUtils; + +/** + * 账号认证处理器 + * + * @author KAI + * @since 2024/12/22 14:58:32 + */ +@Component +@RequiredArgsConstructor +public class AccountAuthHandler extends AbstractAuthHandler implements AuthHandler { + + private final UserService userService; + private final LoginService loginService; + private final PasswordEncoder passwordEncoder; + private final OptionService optionService; + + /** + * 获取认证类型 + * + * @return 账号认证类型 + */ + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.ACCOUNT; + } + + /** + * 校验账号登录请求对象 + * + * @param authReq 登录请求参数 + */ + @Override + public void validate(AccountAuthReq authReq) { + // 获取验证码开关 + int enableCaptcha = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED"); + + ValidationUtils.validate(authReq); + if (SysConstants.YES.equals(enableCaptcha)) { + ValidationUtils.throwIfEmpty(authReq.getCaptcha(), "验证码不能为空"); + ValidationUtils.throwIfEmpty(authReq.getUuid(), "验证码标识不能为空"); + } + } + + /** + * 账号登录 + * + * @param authReq 账号登录请求对象 + * @param request HTTP请求对象 + * @return 登录响应 + */ + @Override + public LoginResp login(AccountAuthReq authReq, ClientResp clientResp, HttpServletRequest request) { + this.validate(authReq); + // 解密密码 + String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(authReq.getPassword())); + ValidationUtils.throwIfBlank(rawPassword, "密码解密失败"); + + // 验证用户名密码 + UserDO user = userService.getByUsername(authReq.getUsername()); + boolean isError = user == null || !passwordEncoder.matches(rawPassword, user.getPassword()); + + // 检查账号锁定状态 + loginService.checkUserLocked(authReq.getUsername(), request, isError); + ValidationUtils.throwIf(isError, "用户名或密码错误"); + + // 检查用户状态 + loginService.checkUserStatus(user); + + // 执行登录 + String token = this.authCertificate(user, clientResp); + return LoginResp.builder().token(token).build(); + } + + /** + * 获取认证信息 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + * @return 认证信息 + */ + @Override + public String authCertificate(UserDO user, ClientResp clientResp) { + return super.authCertificate(user, clientResp); + } +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/handler/EmailAuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/handler/EmailAuthHandler.java new file mode 100644 index 00000000..0c6e840d --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/handler/EmailAuthHandler.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.handler; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.EmailAuthReq; +import top.continew.admin.auth.model.resp.LoginResp; +import top.continew.admin.auth.service.LoginService; +import top.continew.admin.auth.AuthHandler; +import top.continew.admin.common.constant.CacheConstants; +import top.continew.admin.system.model.entity.UserDO; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.UserService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.validation.ValidationUtils; + +/** + * 邮箱认证处理器 + * + * @author KAI + * @since 2024/12/22 14:58 + */ +@Component +@RequiredArgsConstructor +public class EmailAuthHandler extends AbstractAuthHandler implements AuthHandler { + + private final UserService userService; + private final LoginService loginService; + + /** + * 获取认证类型 + * + * @return 邮箱认证类型 + */ + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.EMAIL; + } + + /** + * 校验邮箱登录请求对象 + * + * @param authReq 邮箱登录请求参数 + */ + @Override + public void validate(EmailAuthReq authReq) { + ValidationUtils.validate(authReq); + } + + /** + * 邮箱登录 + * + * @param authReq 邮箱登录请求对象 + * @param request HTTP请求对象 + * @return 登录响应 + */ + @Override + public LoginResp login(EmailAuthReq authReq, ClientResp clientResp, HttpServletRequest request) { + this.validate(authReq); + + String email = authReq.getEmail(); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email; + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, AbstractAuthHandler.CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(authReq.getCaptcha(), captcha, CAPTCHA_ERROR); + RedisUtils.delete(captchaKey); + // 验证邮箱 + UserDO user = userService.getByEmail(authReq.getEmail()); + ValidationUtils.throwIfNull(user, "此邮箱未绑定本系统账号"); + + // 检查用户状态 + loginService.checkUserStatus(user); + + // 执行登录 + String token = this.authCertificate(user, clientResp); + return LoginResp.builder().token(token).build(); + } + + /** + * 获取登录凭证 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + * @return token 认证信息 + */ + @Override + public String authCertificate(UserDO user, ClientResp clientResp) { + return super.authCertificate(user, clientResp); + } +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/handler/PhoneAuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/handler/PhoneAuthHandler.java new file mode 100644 index 00000000..72189669 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/handler/PhoneAuthHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.handler; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.PhoneAuthReq; +import top.continew.admin.auth.model.resp.LoginResp; +import top.continew.admin.auth.service.LoginService; +import top.continew.admin.auth.AuthHandler; +import top.continew.admin.common.constant.CacheConstants; +import top.continew.admin.system.model.entity.UserDO; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.UserService; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.core.validation.ValidationUtils; + +/** + * 手机号认证处理器 + * + * @author KAI + * @since 2024/12/22 14:59 + */ +@Component +@RequiredArgsConstructor +public class PhoneAuthHandler extends AbstractAuthHandler implements AuthHandler { + + private final UserService userService; + private final LoginService loginService; + + /** + * 获取认证类型 + * + * @return 手机号认证类型 + */ + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.PHONE; + } + + /** + * 校验手机号登录请求对象 + * + * @param authReq 手机号登录请求参数 + */ + @Override + public void validate(PhoneAuthReq authReq) { + ValidationUtils.validate(authReq); + } + + /** + * 手机号登录 + * + * @param authReq 手机号登录请求对象 + * @param request HTTP请求对象 + * @return 登录响应 + */ + @Override + public LoginResp login(PhoneAuthReq authReq, ClientResp clientResp, HttpServletRequest request) { + //校验参数 + this.validate(authReq); + + String phone = authReq.getPhone(); + String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone; + String captcha = RedisUtils.get(captchaKey); + ValidationUtils.throwIfBlank(captcha, AbstractAuthHandler.CAPTCHA_EXPIRED); + ValidationUtils.throwIfNotEqualIgnoreCase(authReq.getCaptcha(), captcha, AbstractAuthHandler.CAPTCHA_ERROR); + RedisUtils.delete(captchaKey); + + // 验证手机号 + UserDO user = userService.getByPhone(authReq.getPhone()); + ValidationUtils.throwIfNull(user, "此手机号未绑定本系统账号"); + + // 检查用户状态 + loginService.checkUserStatus(user); + + // 执行登录 + String token = this.authCertificate(user, clientResp); + return LoginResp.builder().token(token).build(); + } + + /** + * 获取登录凭证 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + * @return token 认证信息 + */ + @Override + public String authCertificate(UserDO user, ClientResp clientResp) { + return super.authCertificate(user, clientResp); + } +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/handler/SocialAuthHandler.java b/continew-module-system/src/main/java/top/continew/admin/auth/handler/SocialAuthHandler.java new file mode 100644 index 00000000..1bd9a17f --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/handler/SocialAuthHandler.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.handler; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.json.JSONUtil; +import com.xkcoding.justauth.AuthRequestFactory; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +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 org.springframework.stereotype.Component; +import top.continew.admin.auth.AuthHandler; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.SocialAuthReq; +import top.continew.admin.auth.model.resp.LoginResp; +import top.continew.admin.auth.service.LoginService; +import top.continew.admin.common.constant.RegexConstants; +import top.continew.admin.common.constant.SysConstants; +import top.continew.admin.common.enums.GenderEnum; +import top.continew.admin.system.model.entity.RoleDO; +import top.continew.admin.system.model.entity.UserDO; +import top.continew.admin.system.model.entity.UserSocialDO; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.RoleService; +import top.continew.admin.system.service.UserRoleService; +import top.continew.admin.system.service.UserService; +import top.continew.admin.system.service.UserSocialService; +import top.continew.starter.core.exception.BadRequestException; +import top.continew.starter.core.validation.ValidationUtils; + +import java.time.LocalDateTime; +import java.util.Collections; + +/** + * 手机号认证处理器 + * + * @author KAI + * @since 2024/12/25 14:21 + */ +@Component +@RequiredArgsConstructor +public class SocialAuthHandler extends AbstractAuthHandler implements AuthHandler { + private final AuthRequestFactory authRequestFactory; + private final UserSocialService userSocialService; + private final UserService userService; + private final RoleService roleService; + private final UserRoleService userRoleService; + private final LoginService loginService; + + /** + * 获取认证类型 + * + * @return 第三方认证类型 + */ + @Override + public AuthTypeEnum getAuthType() { + return AuthTypeEnum.SOCIAL_AUTH; + } + + /** + * 校验第三方登录请求对象 + * + * @param authReq 登录请求参数 + */ + @Override + public void validate(SocialAuthReq authReq) { + ValidationUtils.validate(authReq); + } + + /** + * 第三方登录 + * + * @param authReq 第三方登录请求对象 + * @param request HTTP请求对象 + * @return 登录响应 + */ + @Override + public LoginResp login(SocialAuthReq authReq, ClientResp clientResp, HttpServletRequest request) { + this.validate(authReq); + if (StpUtil.isLogin()) { + StpUtil.logout(); + } + AuthRequest authRequest = this.getAuthRequest(authReq.getSource()); + AuthCallback callback = new AuthCallback(); + callback.setCode(authReq.getCode()); + callback.setState(authReq.getState()); + AuthResponse response = authRequest.login(callback); + ValidationUtils.throwIf(!response.ok(), response.getMsg()); + AuthUser authUser = response.getData(); + String source = authUser.getSource(); + String openId = authUser.getUuid(); + UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId); + UserDO user; + if (null == userSocial) { + String username = authUser.getUsername(); + String nickname = authUser.getNickname(); + UserDO existsUser = userService.getByUsername(username); + String randomStr = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5); + if (null != existsUser || !ReUtil.isMatch(RegexConstants.USERNAME, username)) { + username = randomStr + IdUtil.fastSimpleUUID(); + } + if (!ReUtil.isMatch(RegexConstants.GENERAL_NAME, nickname)) { + nickname = source.toLowerCase() + randomStr; + } + user = new UserDO(); + user.setUsername(username); + user.setNickname(nickname); + user.setGender(GenderEnum.valueOf(authUser.getGender().name())); + user.setAvatar(authUser.getAvatar()); + user.setDeptId(SysConstants.SUPER_DEPT_ID); + Long userId = userService.add(user); + RoleDO role = roleService.getByCode(SysConstants.SUPER_ROLE_CODE); + userRoleService.assignRolesToUser(Collections.singletonList(role.getId()), userId); + userSocial = new UserSocialDO(); + userSocial.setUserId(userId); + userSocial.setSource(source); + userSocial.setOpenId(openId); + loginService.sendSecurityMsg(user); + } else { + user = BeanUtil.copyProperties(userService.getById(userSocial.getUserId()), UserDO.class); + } + loginService.checkUserStatus(user); + userSocial.setMetaJson(JSONUtil.toJsonStr(authUser)); + userSocial.setLastLoginTime(LocalDateTime.now()); + userSocialService.saveOrUpdate(userSocial); + // 执行登录 + String token = this.authCertificate(user, clientResp); + return LoginResp.builder().token(token).build(); + } + + private AuthRequest getAuthRequest(String source) { + try { + return authRequestFactory.get(source); + } catch (Exception e) { + throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source)); + } + } + + /** + * 获取认证信息 + * + * @param user 用户信息 + * @param clientResp 客户端信息 + * @return 认证信息 + */ + @Override + protected String authCertificate(UserDO user, ClientResp clientResp) { + return super.authCertificate(user, clientResp); + } + +} diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountAuthReq.java similarity index 96% rename from continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java rename to continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountAuthReq.java index 0ed4148c..d56d1414 100644 --- a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java +++ b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AccountAuthReq.java @@ -31,7 +31,7 @@ import java.io.Serializable; */ @Data @Schema(description = "账号登录参数") -public class AccountLoginReq implements Serializable { +public class AccountAuthReq extends AuthReq implements Serializable { @Serial private static final long serialVersionUID = 1L; diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AuthReq.java b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AuthReq.java new file mode 100644 index 00000000..7820faca --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/AuthReq.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.model.req; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 登录参数基础类 + * + * @author KAI + * @since 2024/12/22 15:16 + */ +@Data +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "authType", visible = true) +@JsonSubTypes({@JsonSubTypes.Type(value = AccountAuthReq.class, name = "account"), + @JsonSubTypes.Type(value = EmailAuthReq.class, name = "email"), + @JsonSubTypes.Type(value = PhoneAuthReq.class, name = "phone"), + @JsonSubTypes.Type(value = SocialAuthReq.class, name = "socialAuth")}) +public abstract class AuthReq { + + @Schema(description = "客户端id") + @NotBlank(message = "客户端id不能为空") + private String clientId; + + @Schema(description = "认证类型") + @NotBlank(message = "认证类型不能为空") + private String authType; +} diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailAuthReq.java similarity index 96% rename from continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java rename to continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailAuthReq.java index 5959a898..f84d0e3d 100644 --- a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java +++ b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/EmailAuthReq.java @@ -34,7 +34,7 @@ import java.io.Serializable; */ @Data @Schema(description = "邮箱登录参数") -public class EmailLoginReq implements Serializable { +public class EmailAuthReq extends AuthReq implements Serializable { @Serial private static final long serialVersionUID = 1L; diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneAuthReq.java similarity index 96% rename from continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java rename to continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneAuthReq.java index f81cc74f..5072b624 100644 --- a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java +++ b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/PhoneAuthReq.java @@ -34,7 +34,7 @@ import java.io.Serializable; */ @Data @Schema(description = "手机号登录参数") -public class PhoneLoginReq implements Serializable { +public class PhoneAuthReq extends AuthReq implements Serializable { @Serial private static final long serialVersionUID = 1L; diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/model/req/SocialAuthReq.java b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/SocialAuthReq.java new file mode 100644 index 00000000..2eb749f6 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/auth/model/req/SocialAuthReq.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 第三方登录参数 + * + * @author KAI + * @since 2024/12/25 15:43 + */ +@Data +@Schema(description = "第三方登录参数") +public class SocialAuthReq extends AuthReq { + /** + * 第三方登录平台 + */ + @NotBlank(message = "第三方登录平台不能为空") + private String source; + + /** + * 第三方登录code + */ + @NotBlank(message = "第三方登录code不能为空") + private String code; + + /** + * 第三方登录state + */ + @NotBlank(message = "第三方登录state不能为空") + private String state; +} diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/service/LoginService.java b/continew-module-system/src/main/java/top/continew/admin/auth/service/LoginService.java index 34a81359..529ec973 100644 --- a/continew-module-system/src/main/java/top/continew/admin/auth/service/LoginService.java +++ b/continew-module-system/src/main/java/top/continew/admin/auth/service/LoginService.java @@ -19,6 +19,7 @@ package top.continew.admin.auth.service; import jakarta.servlet.http.HttpServletRequest; import me.zhyd.oauth.model.AuthUser; import top.continew.admin.auth.model.resp.RouteResp; +import top.continew.admin.system.model.entity.UserDO; import java.util.List; @@ -31,30 +32,28 @@ import java.util.List; public interface LoginService { /** - * 账号登录 + * 检查用户状态 + * + * @param user 用户信息 + */ + void checkUserStatus(UserDO user); + + /** + * 检查用户是否被锁定 * * @param username 用户名 - * @param password 密码 * @param request 请求对象 - * @return 令牌 + * @param isError 是否登录错误 */ - String accountLogin(String username, String password, HttpServletRequest request); + void checkUserLocked(String username, HttpServletRequest request, boolean isError); /** - * 手机号登录 + * 执行登录操作 * - * @param phone 手机号 - * @return 令牌 + * @param user 用户信息 + * @return token */ - String phoneLogin(String phone); - - /** - * 邮箱登录 - * - * @param email 邮箱 - * @return 令牌 - */ - String emailLogin(String email); + String login(UserDO user); /** * 三方账号登录 @@ -71,4 +70,11 @@ public interface LoginService { * @return 路由树 */ List buildRouteTree(Long userId); + + /** + * 发送安全消息 + * + * @param user 用户信息 + */ + void sendSecurityMsg(UserDO user); } diff --git a/continew-module-system/src/main/java/top/continew/admin/auth/service/impl/LoginServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/auth/service/impl/LoginServiceImpl.java index 48d0ebbe..e021bd2d 100644 --- a/continew-module-system/src/main/java/top/continew/admin/auth/service/impl/LoginServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/auth/service/impl/LoginServiceImpl.java @@ -96,32 +96,6 @@ public class LoginServiceImpl implements LoginService { private final OptionService optionService; private final MessageService messageService; - @Override - public String accountLogin(String username, String password, HttpServletRequest request) { - UserDO user = userService.getByUsername(username); - boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(password, user.getPassword()); - this.checkUserLocked(username, request, isError); - CheckUtils.throwIf(isError, "用户名或密码错误"); - this.checkUserStatus(user); - return this.login(user); - } - - @Override - public String phoneLogin(String phone) { - UserDO user = userService.getByPhone(phone); - CheckUtils.throwIfNull(user, "此手机号未绑定本系统账号"); - this.checkUserStatus(user); - return this.login(user); - } - - @Override - public String emailLogin(String email) { - UserDO user = userService.getByEmail(email); - CheckUtils.throwIfNull(user, "此邮箱未绑定本系统账号"); - this.checkUserStatus(user); - return this.login(user); - } - @Override @Transactional(rollbackFor = Exception.class) public String socialLogin(AuthUser authUser) { @@ -209,7 +183,8 @@ public class LoginServiceImpl implements LoginService { * @param user 用户信息 * @return 令牌 */ - private String login(UserDO user) { + @Override + public String login(UserDO user) { Long userId = user.getId(); CompletableFuture> permissionFuture = CompletableFuture.supplyAsync(() -> roleService .listPermissionByUserId(userId), threadPoolTaskExecutor); @@ -233,7 +208,8 @@ public class LoginServiceImpl implements LoginService { * * @param user 用户信息 */ - private void checkUserStatus(UserDO user) { + @Override + public void checkUserStatus(UserDO user) { CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员"); DeptDO dept = deptService.getById(user.getDeptId()); CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, dept.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员"); @@ -246,7 +222,8 @@ public class LoginServiceImpl implements LoginService { * @param request 请求对象 * @param isError 是否登录错误 */ - private void checkUserLocked(String username, HttpServletRequest request, boolean isError) { + @Override + public void checkUserLocked(String username, HttpServletRequest request, boolean isError) { // 不锁定 int maxErrorCount = optionService.getValueByCode2Int(PasswordPolicyEnum.PASSWORD_ERROR_LOCK_COUNT.name()); if (maxErrorCount <= SysConstants.NO) { @@ -274,7 +251,8 @@ public class LoginServiceImpl implements LoginService { * * @param user 用户信息 */ - private void sendSecurityMsg(UserDO user) { + @Override + public void sendSecurityMsg(UserDO user) { MessageReq req = new MessageReq(); MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER; req.setTitle(socialRegister.getTitle().formatted(projectProperties.getName())); diff --git a/continew-module-system/src/main/java/top/continew/admin/system/mapper/ClientMapper.java b/continew-module-system/src/main/java/top/continew/admin/system/mapper/ClientMapper.java new file mode 100644 index 00000000..5bc6dee2 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/mapper/ClientMapper.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.mapper; + +import top.continew.starter.data.mp.base.BaseMapper; +import top.continew.admin.system.model.entity.ClientDO; + +/** + * 客户端管理 Mapper + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +public interface ClientMapper extends BaseMapper {} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java new file mode 100644 index 00000000..80ed66a7 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.extension.crud.model.entity.BaseDO; + +import java.io.Serial; +import java.util.List; + +/** + * 客户端管理实体 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Data +@TableName(value = "sys_client", autoResultMap = true) +public class ClientDO extends BaseDO { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 客户端Key + */ + private String clientKey; + + /** + * 客户端秘钥 + */ + private String clientSecret; + + /** + * 登录类型 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authType; + + /** + * 客户端类型 + */ + private String clientType; + + /** + * Token最低活跃频率(-1为不限制) + */ + private Integer activeTimeout; + + /** + * Token有效期(默认30天,单位:秒) + */ + private Integer timeout; + + /** + * 状态(1:启用;2:禁用) + */ + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/query/ClientQuery.java b/continew-module-system/src/main/java/top/continew/admin/system/model/query/ClientQuery.java new file mode 100644 index 00000000..96cd2a3a --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/query/ClientQuery.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.model.query; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.data.core.annotation.Query; +import top.continew.starter.data.core.enums.QueryType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 客户端管理查询条件 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "客户端管理查询条件") +public class ClientQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 客户端Key + */ + @Schema(description = "客户端Key") + @Query(type = QueryType.EQ) + private String clientKey; + + /** + * 客户端秘钥 + */ + @Schema(description = "客户端秘钥") + @Query(type = QueryType.EQ) + private String clientSecret; + + /** + * 登录类型 + */ + @Schema(description = "登录类型") + @Query(type = QueryType.IN) + private List authType; + /** + * 客户端类型 + */ + @Schema(description = "客户端类型") + @Query(type = QueryType.EQ) + private String clientType; + + /** + * 状态(1:启用;2:禁用) + */ + @Schema(description = "状态") + @Query(type = QueryType.EQ) + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java b/continew-module-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java new file mode 100644 index 00000000..09b5eb0d --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.extension.crud.model.req.BaseReq; + +import java.io.Serial; +import java.util.List; + +/** + * 创建或修改客户端管理参数 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "创建或修改客户端管理参数") +public class ClientReq extends BaseReq { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 客户端ID + */ + @Schema(description = "客户端ID") + private String clientId; + + /** + * 客户端Key + */ + @Schema(description = "客户端Key") + @NotBlank(message = "客户端Key不能为空") + @Length(max = 32, message = "客户端Key长度不能超过 {max} 个字符") + private String clientKey; + + /** + * 客户端秘钥 + */ + @Schema(description = "客户端秘钥") + @NotBlank(message = "客户端秘钥不能为空") + @Length(max = 255, message = "客户端秘钥长度不能超过 {max} 个字符") + private String clientSecret; + + /** + * 登录类型 + */ + @Schema(description = "登录类型") + @NotNull(message = "登录类型不能为空") + private List authType; + /** + * 客户端类型 + */ + @Schema(description = "客户端类型") + @NotBlank(message = "客户端类型不能为空") + @Length(max = 32, message = "客户端类型长度不能超过 {max} 个字符") + private String clientType; + + /** + * Token最低活跃频率(-1为不限制) + */ + @Schema(description = "Token最低活跃频率(-1为不限制)") + private Integer activeTimeout; + + /** + * Token有效期(默认30天,单位:秒) + */ + @Schema(description = "Token有效期(默认30天,单位:秒)") + private Integer timeout; + + /** + * 状态(1:启用;2:禁用) + */ + @Schema(description = "状态(1:启用;2:禁用)") + @NotNull(message = "状态(1:启用;2:禁用)不能为空") + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientDetailResp.java b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientDetailResp.java new file mode 100644 index 00000000..8afc05f5 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientDetailResp.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.model.resp; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.extension.crud.model.resp.BaseDetailResp; + +import java.io.Serial; +import java.util.List; + +/** + * 客户端管理详情信息 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Data +@ExcelIgnoreUnannotated +@Schema(description = "客户端管理详情信息") +public class ClientDetailResp extends BaseDetailResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 客户端ID + */ + @Schema(description = "客户端ID") + @ExcelProperty(value = "客户端ID") + private String clientId; + + /** + * 客户端Key + */ + @Schema(description = "客户端Key") + @ExcelProperty(value = "客户端Key") + private String clientKey; + + /** + * 客户端秘钥 + */ + @Schema(description = "客户端秘钥") + @ExcelProperty(value = "客户端秘钥") + private String clientSecret; + + /** + * 登录类型 + */ + @Schema(description = "登录类型") + @ExcelProperty(value = "登录类型") + private List authType; + + /** + * 客户端类型 + */ + @Schema(description = "客户端类型") + @ExcelProperty(value = "客户端类型") + private String clientType; + + /** + * Token最低活跃频率(-1为不限制) + */ + @Schema(description = "Token最低活跃频率(-1为不限制)") + @ExcelProperty(value = "Token最低活跃频率(-1为不限制)") + private Integer activeTimeout; + + /** + * Token有效期(默认30天,单位:秒) + */ + @Schema(description = "Token有效期(默认30天,单位:秒)") + @ExcelProperty(value = "Token有效期(默认30天,单位:秒)") + private Integer timeout; + + /** + * 状态 + */ + @Schema(description = "状态") + @ExcelProperty(value = "状态") + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java new file mode 100644 index 00000000..e45f5f92 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.model.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.starter.extension.crud.model.resp.BaseResp; + +import java.io.Serial; +import java.util.List; + +/** + * 客户端管理信息 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Data +@Schema(description = "客户端管理信息") +public class ClientResp extends BaseResp { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 客户端ID + */ + @Schema(description = "客户端ID") + private String clientId; + + /** + * 客户端Key + */ + @Schema(description = "客户端Key") + private String clientKey; + + /** + * 客户端秘钥 + */ + @Schema(description = "客户端秘钥") + private String clientSecret; + + /** + * 认证类型 + */ + @Schema(description = "认证类型") + private List authType; + + /** + * 客户端类型 + */ + @Schema(description = "客户端类型") + private String clientType; + + /** + * Token最低活跃频率(-1为不限制) + */ + @Schema(description = "Token最低活跃频率(-1为不限制)") + private Integer activeTimeout; + + /** + * Token有效期(默认30天,单位:秒) + */ + @Schema(description = "Token有效期(默认30天,单位:秒)") + private Integer timeout; + + /** + * 状态(1:启用;2:禁用) + */ + @Schema(description = "状态") + private DisEnableStatusEnum status; +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/ClientService.java b/continew-module-system/src/main/java/top/continew/admin/system/service/ClientService.java new file mode 100644 index 00000000..4ea45cd8 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/ClientService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.service; + +import top.continew.admin.system.model.query.ClientQuery; +import top.continew.admin.system.model.req.ClientReq; +import top.continew.admin.system.model.resp.ClientDetailResp; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.starter.extension.crud.service.BaseService; + +/** + * 客户端管理业务接口 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +public interface ClientService extends BaseService { + + ClientResp getClientByClientId(String clientId); + +} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java new file mode 100644 index 00000000..b813c0ee --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.system.mapper.ClientMapper; +import top.continew.admin.system.model.entity.ClientDO; +import top.continew.admin.system.model.query.ClientQuery; +import top.continew.admin.system.model.req.ClientReq; +import top.continew.admin.system.model.resp.ClientDetailResp; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.ClientService; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.validation.ValidationUtils; +import top.continew.starter.extension.crud.service.BaseServiceImpl; + +import java.util.List; + +/** + * 客户端管理业务实现 + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Service +@RequiredArgsConstructor +public class ClientServiceImpl extends BaseServiceImpl implements ClientService { + @Override + protected void beforeAdd(ClientReq req) { + String clientId = DigestUtil.md5Hex(req.getClientKey() + StringConstants.COLON + req.getClientSecret()); + req.setClientId(clientId); + } + + /** + * 通过ClientId获取客户端实例 + * + * @param clientId 客户端id + * @return 客户端响应对象 + */ + @Override + public ClientResp getClientByClientId(String clientId) { + ClientDO clientDO = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(ClientDO::getClientId, clientId)); + return BeanUtil.copyProperties(clientDO, ClientResp.class); + } + + @Override + protected void beforeDelete(List ids) { + // 查询如果删除客户端记录以后是否还存在账号认证的方式,不存在则不允许删除 + List clientDOS = baseMapper.selectList(new LambdaQueryWrapper().notIn(ClientDO::getId, ids) + .like(ClientDO::getAuthType, AuthTypeEnum.ACCOUNT.getValue())); + ValidationUtils.throwIfEmpty(clientDOS, StrUtil.format("请至少保留一条{}认证的方式", AuthTypeEnum.ACCOUNT + .getDescription())); + super.beforeDelete(ids); + } +} \ No newline at end of file diff --git a/continew-webapi/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java b/continew-webapi/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java index 44e47162..6022d1f2 100644 --- a/continew-webapi/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java +++ b/continew-webapi/src/main/java/top/continew/admin/config/log/LogDaoLocalImpl.java @@ -28,7 +28,8 @@ import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.scheduling.annotation.Async; -import top.continew.admin.auth.model.req.AccountLoginReq; +import top.continew.admin.auth.enums.AuthTypeEnum; +import top.continew.admin.auth.model.req.AccountAuthReq; import top.continew.admin.common.constant.SysConstants; import top.continew.admin.system.enums.LogStatusEnum; import top.continew.admin.system.mapper.LogMapper; @@ -145,9 +146,12 @@ public class LogDaoLocalImpl implements LogDao { // 解析登录接口信息 if (requestUri.startsWith(SysConstants.LOGIN_URI) && LogStatusEnum.SUCCESS.equals(logDO.getStatus())) { String requestBody = logRequest.getBody(); - AccountLoginReq loginReq = JSONUtil.toBean(requestBody, AccountLoginReq.class); - logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername()) - .getId())); + //账号登录设置操作人 + if (requestBody.contains(AuthTypeEnum.ACCOUNT.getValue())) { + AccountAuthReq loginReq = JSONUtil.toBean(requestBody, AccountAuthReq.class); + logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername()) + .getId())); + } return; } // 解析 Token 信息 diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/auth/AuthController.java b/continew-webapi/src/main/java/top/continew/admin/controller/auth/AuthController.java index cb75da04..93165427 100644 --- a/continew-webapi/src/main/java/top/continew/admin/controller/auth/AuthController.java +++ b/continew-webapi/src/main/java/top/continew/admin/controller/auth/AuthController.java @@ -19,31 +19,29 @@ package top.continew.admin.controller.auth; import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import top.continew.admin.auth.model.req.AccountLoginReq; -import top.continew.admin.auth.model.req.EmailLoginReq; -import top.continew.admin.auth.model.req.PhoneLoginReq; +import top.continew.admin.auth.config.AuthHandlerContext; +import top.continew.admin.auth.model.req.AuthReq; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.model.resp.RouteResp; import top.continew.admin.auth.model.resp.UserInfoResp; import top.continew.admin.auth.service.LoginService; -import top.continew.admin.common.constant.CacheConstants; -import top.continew.admin.common.constant.SysConstants; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; -import top.continew.admin.common.util.SecureUtils; +import top.continew.admin.system.model.resp.ClientResp; import top.continew.admin.system.model.resp.user.UserDetailResp; -import top.continew.admin.system.service.OptionService; +import top.continew.admin.system.service.ClientService; import top.continew.admin.system.service.UserService; -import top.continew.starter.cache.redisson.util.RedisUtils; -import top.continew.starter.core.util.ExceptionUtils; +import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.validation.ValidationUtils; import top.continew.starter.log.annotation.Log; @@ -55,67 +53,41 @@ import java.util.List; * @author Charles7c * @since 2022/12/21 20:37 */ +@Slf4j @Log(module = "登录") @Tag(name = "认证 API") @RestController @RequiredArgsConstructor @RequestMapping("/auth") public class AuthController { + private final ClientService clientService; - private static final String CAPTCHA_EXPIRED = "验证码已失效"; - private static final String CAPTCHA_ERROR = "验证码错误"; - private final OptionService optionService; - private final LoginService loginService; private final UserService userService; + private final LoginService loginService; + + private final AuthHandlerContext authHandlerContext; + @SaIgnore - @Operation(summary = "账号登录", description = "根据账号和密码进行登录认证") - @PostMapping("/account") - public LoginResp accountLogin(@Validated @RequestBody AccountLoginReq loginReq, HttpServletRequest request) { - // 校验验证码 - int loginCaptchaEnabled = optionService.getValueByCode2Int("LOGIN_CAPTCHA_ENABLED"); - if (SysConstants.YES.equals(loginCaptchaEnabled)) { - ValidationUtils.throwIfBlank(loginReq.getCaptcha(), "验证码不能为空"); - ValidationUtils.throwIfBlank(loginReq.getUuid(), "验证码标识不能为空"); - String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + loginReq.getUuid(); - String captcha = RedisUtils.get(captchaKey); - ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); - RedisUtils.delete(captchaKey); - ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR); + @Operation(summary = "登录", description = "统一登录入口") + @PostMapping("/login") + public LoginResp login(@Validated @RequestBody AuthReq loginReq, HttpServletRequest request) { + // 认证类型 + String authType = loginReq.getAuthType(); + + // 获取并验证客户端信息 + ClientResp clientResp = clientService.getClientByClientId(loginReq.getClientId()); + ValidationUtils.throwIfNull(clientResp, "客户端信息不存在,请检查客户端id是否正确!"); + + // 验证认证类型 + ValidationUtils.throwIf(!clientResp.getAuthType().contains(authType), StrUtil.format("暂未授权此类型:{}", authType)); + try { + // 执行登录策略 + return (LoginResp)authHandlerContext.getHandler(authType).login(loginReq, clientResp, request); + } catch (Exception e) { + log.error("登录失败: {}", e.getMessage(), e); + throw new BusinessException("登录失败: " + e.getMessage()); } - // 用户登录 - String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(loginReq.getPassword())); - ValidationUtils.throwIfBlank(rawPassword, "密码解密失败"); - String token = loginService.accountLogin(loginReq.getUsername(), rawPassword, request); - return LoginResp.builder().token(token).build(); - } - - @SaIgnore - @Operation(summary = "手机号登录", description = "根据手机号和验证码进行登录认证") - @PostMapping("/phone") - public LoginResp phoneLogin(@Validated @RequestBody PhoneLoginReq loginReq) { - String phone = loginReq.getPhone(); - String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone; - String captcha = RedisUtils.get(captchaKey); - ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); - ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR); - RedisUtils.delete(captchaKey); - String token = loginService.phoneLogin(phone); - return LoginResp.builder().token(token).build(); - } - - @SaIgnore - @Operation(summary = "邮箱登录", description = "根据邮箱和验证码进行登录认证") - @PostMapping("/email") - public LoginResp emailLogin(@Validated @RequestBody EmailLoginReq loginReq) { - String email = loginReq.getEmail(); - String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + email; - String captcha = RedisUtils.get(captchaKey); - ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); - ValidationUtils.throwIfNotEqualIgnoreCase(loginReq.getCaptcha(), captcha, CAPTCHA_ERROR); - RedisUtils.delete(captchaKey); - String token = loginService.emailLogin(email); - return LoginResp.builder().token(token).build(); } @Operation(summary = "用户退出", description = "注销用户的当前登录") @@ -146,4 +118,4 @@ public class AuthController { public List listRoute() { return loginService.buildRouteTree(UserContextHolder.getUserId()); } -} \ No newline at end of file +} diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/system/ClientController.java b/continew-webapi/src/main/java/top/continew/admin/controller/system/ClientController.java new file mode 100644 index 00000000..de468297 --- /dev/null +++ b/continew-webapi/src/main/java/top/continew/admin/controller/system/ClientController.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.controller.system; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RestController; +import top.continew.admin.common.base.BaseController; +import top.continew.admin.system.model.query.ClientQuery; +import top.continew.admin.system.model.req.ClientReq; +import top.continew.admin.system.model.resp.ClientDetailResp; +import top.continew.admin.system.model.resp.ClientResp; +import top.continew.admin.system.service.ClientService; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +/** + * 客户端管理管理 API + * + * @author MoChou + * @since 2024/12/03 16:04 + */ +@Tag(name = "客户端管理管理 API") +@RestController +@CrudRequestMapping(value = "/system/client", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}) +public class ClientController extends BaseController { +} \ No newline at end of file diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql index 09cd2ecb..6f767215 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql @@ -188,3 +188,30 @@ VALUES (1, '开发环境', 'local_dev', 2, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储', b'1', 1, 1, 1, NOW()), (2, '生产环境', 'local_prod', 2, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file', '本地存储', b'0', 2, 2, 1, NOW()); +-- 客户端管理管理菜单 +INSERT INTO `sys_menu` +(`title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`, `update_user`, `update_time`) +VALUES +( '客户端管理', 1000, 2, '/system/client', 'SystemClient', 'system/client/index', NULL, 'mobile', b'0', b'0', b'0', NULL, 9, 1, 1, NOW(), NULL, NULL); + +SET @parentId = LAST_INSERT_ID(); + +-- 客户端管理管理按钮 +INSERT INTO `sys_menu` +(`title`, `parent_id`, `type`, `path`, `name`, `component`, `redirect`, `icon`, `is_external`, `is_cache`, `is_hidden`, `permission`, `sort`, `status`, `create_user`, `create_time`, `update_user`, `update_time`) +VALUES + ('列表', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:list', 1, 1, 1, NOW(), NULL, NULL), + ('详情', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:detail', 2, 1, 1, NOW(), NULL, NULL), + ('新增', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:add', 3, 1, 1, NOW(), NULL, NULL), + ('修改', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:update', 4, 1, 1, NOW(), NULL, NULL), + ('删除', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:delete', 5, 1, 1, NOW(), NULL, NULL), + ('导出', @parentId, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:client:export', 6, 1, 1, NOW(), NULL, NULL); +-- 插入客户端字典数据 +INSERT INTO `sys_dict` (`id`, `name`, `code`, `description`, `is_system`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (4, '客户端类型', 'client_type', NULL, b'0', 1, '2024-12-17 18:09:37', NULL, NULL); +INSERT INTO `sys_dict_item` (`id`, `label`, `value`, `color`, `sort`, `description`, `status`, `dict_id`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (659457796923719711, '桌面端', 'pc', 'blue', 999, NULL, 1, 4, 1, '2024-12-17 18:09:51', NULL, NULL); +INSERT INTO `sys_dict_item` (`id`, `label`, `value`, `color`, `sort`, `description`, `status`, `dict_id`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (659457877756346402, '安卓', 'android', '#148628', 999, NULL, 1, 4, 1, '2024-12-17 18:10:10', NULL, NULL); +INSERT INTO `sys_dict_item` (`id`, `label`, `value`, `color`, `sort`, `description`, `status`, `dict_id`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (659457929665052709, '小程序', 'xcx', '#7930AD', 999, NULL, 1, 4, 1, '2024-12-17 18:10:23', NULL, NULL); +-- 插入客户端数据 +INSERT INTO `sys_client` (`id`, `client_id`, `client_key`, `client_secret`, `auth_type`, `client_type`, `active_timeout`, `timeout`, `status`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (1, 'ef51c9a3e9046c4f2ea45142c8a8344a', 'pc', 'dd77ab1e353a027e0d60ce3b151e8642', '[\"account\", \"socialAuth\", \"email\", \"phone\"]', 'pc', 1800, 86400, 1, 1, now(), 1, '2024-12-25 17:41:36'); + + diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql index 95e77b8f..69fac008 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql @@ -294,3 +294,20 @@ CREATE TABLE IF NOT EXISTS `sys_file` ( INDEX `idx_create_user`(`create_user`), INDEX `idx_update_user`(`update_user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; + +CREATE TABLE IF NOT EXISTS `sys_client` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `client_id` varchar(50) NOT NULL COMMENT '客户端ID', + `client_key` varchar(255) NOT NULL COMMENT '客户端Key', + `client_secret` varchar(255) NOT NULL COMMENT '客户端秘钥', + `auth_type` json DEFAULT NULL COMMENT '授权类型', + `client_type` varchar(50) DEFAULT NULL COMMENT '客户端类型', + `active_timeout` int DEFAULT '-1' COMMENT 'Token最低活跃频率(-1为不限制)', + `timeout` int DEFAULT '2592000' COMMENT 'Token有效期(默认30天,单位:秒)', + `status` tinyint(1) UNSIGNED NOT NULL COMMENT '状态(1:启用;2:禁用)', + `create_user` bigint(20) NOT NULL COMMENT '创建人', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户端管理';