feat(system/client): 新增客户端管理

This commit is contained in:
KAI
2024-12-26 02:18:34 +00:00
committed by Charles7c
parent 6bcff7244f
commit 95f2617a4c
30 changed files with 1651 additions and 114 deletions

View File

@@ -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 信息

View File

@@ -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<RouteResp> listRoute() {
return loginService.buildRouteTree(UserContextHolder.getUserId());
}
}
}

View File

@@ -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<ClientService, ClientResp, ClientDetailResp, ClientQuery, ClientReq> {
}

View File

@@ -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');

View File

@@ -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='客户端管理';