新增:新增用户登录和退出 API(引入 Sa-Token 依赖,详情可见 README 介绍)

This commit is contained in:
2022-12-22 19:39:27 +08:00
parent d54c93aebc
commit 00e2b44d0e
27 changed files with 1352 additions and 49 deletions

View File

@@ -59,6 +59,31 @@ limitations under the License.
</exclusions>
</dependency>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- ################ Sa-Token 相关 ################ -->
<!-- Sa-Token轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<!-- Sa-Token 整合 JWT -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
</dependency>
<!-- Sa-Token 整合 Redis使用 Jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Knife4j前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
<dependency>

View File

@@ -0,0 +1,41 @@
/*
* 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.charles7c.cnadmin.common.config.properties;
import lombok.Data;
import org.springframework.stereotype.Component;
import cn.hutool.extra.spring.SpringUtil;
/**
* RSA 配置属性
*
* @author Charles7c
* @since 2022/12/21 20:21
*/
@Data
@Component
public class RsaProperties {
/** 私钥 */
public static final String PRIVATE_KEY;
static {
PRIVATE_KEY = SpringUtil.getProperty("rsa.privateKey");
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.charles7c.cnadmin.common.consts;
/**
* 公共常量
*
* @author Charles7c
* @since 2022/12/22 19:30
*/
public interface CommonConstants {
/**
* 状态-启用
*/
Integer STATUS_ENABLE = 1;
/**
* 状态-禁用
*/
Integer STATUS_DISABLE = 2;
}

View File

@@ -0,0 +1,34 @@
/*
* 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.charles7c.cnadmin.common.exception;
import lombok.NoArgsConstructor;
/**
* 自定义验证异常-错误请求
*
* @author Charles7c
* @since 2022/12/21 20:59
*/
@NoArgsConstructor
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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.charles7c.cnadmin.common.handler;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.exception.BadRequestException;
import top.charles7c.cnadmin.common.model.vo.R;
/**
* 全局异常处理器
*
* @author Charles7c
* @since 2022/12/21 21:01
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截未知的系统异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public R handleException(Exception e, HttpServletRequest request) {
log.error("请求地址'{}',发生未知异常", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("请求地址'{}',发生系统异常", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
/**
* 拦截自定义验证异常-错误请求
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BadRequestException.class)
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.error("请求地址'{}',自定义验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-绑定异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public R handleBindException(BindException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-违反约束异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-方法参数无效异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-方法参数类型不匹配异常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
String subMsg = StrUtil.format("参数名:'{}',期望参数类型:'{}'", e.getName(), e.getParameter().getParameterType());
log.error("请求地址'{}',参数转换失败。方法:'{}'{}", request.getRequestURI(),
Objects.requireNonNull(e.getParameter().getMethod()).getName(), subMsg, e);
return R.fail(HttpStatus.BAD_REQUEST.value(), subMsg);
}
/**
* 拦截校验异常-请求方式不支持异常
*/
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod());
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
}
/**
* 拦截认证异常-未登录异常
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(NotLoginException.class)
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", request.getRequestURI(), e.getMessage());
return R.fail(HttpStatus.UNAUTHORIZED.value(), "认证失败,无法访问系统资源");
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.charles7c.cnadmin.common.model.entity;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 实体类基类
*
* @author Charles7c
* @since 2022/12/12 23:02
*/
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建人
*/
private Long createUser;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改人
*/
private Long updateUser;
/**
* 修改时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,116 @@
/*
* 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.charles7c.cnadmin.common.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.exception.BadRequestException;
/**
* 检查工具类
*
* @author Charles7c
* @since 2022/12/21 20:56
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CheckUtils {
/**
* 如果为空,抛出异常
*
* @param obj
* 被检测的对象
* @param message
* 错误信息
*/
public static void exIfNull(Object obj, String message) {
if (obj == null) {
log.error(message);
throw new BadRequestException(message);
}
}
/**
* 如果为空,抛出异常
*
* @param str
* 被检测的字符串
* @param message
* 错误信息
*/
public static void exIfBlank(CharSequence str, String message) {
if (StrUtil.isBlank(str)) {
log.error(message);
throw new BadRequestException(message);
}
}
/**
* 如果相同,抛出异常
*
* @param obj1
* 要比较的对象1
* @param obj2
* 要比较的对象2
* @param message
* 错误信息
*/
public static void exIfEqual(Object obj1, Object obj2, String message) {
if (ObjectUtil.equals(obj1, obj2)) {
log.error(message);
throw new BadRequestException(message);
}
}
/**
* 如果不相同,抛出异常
*
* @param obj1
* 要比较的对象1
* @param obj2
* 要比较的对象2
* @param message
* 错误信息
*/
public static void exIfNotEqual(Object obj1, Object obj2, String message) {
if (ObjectUtil.notEqual(obj1, obj2)) {
log.error(message);
throw new BadRequestException(message);
}
}
/**
* 如果条件成立,抛出异常
*
* @param conditionSupplier
* 条件
* @param message
* 错误信息
*/
public static void exIfCondition(java.util.function.BooleanSupplier conditionSupplier, String message) {
if (conditionSupplier != null && conditionSupplier.getAsBoolean()) {
log.error(message);
throw new BadRequestException(message);
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.charles7c.cnadmin.common.util;
import java.util.function.Consumer;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* 异常工具类
*
* @author Charles7c
* @since 2022/12/21 20:56
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExceptionUtils {
/**
* 如果有异常,返回 null
*
* @param exSupplier
* 可能会出现异常的方法执行
* @param <T>
* /
* @return /
*/
public static <T> T exToNull(ExSupplier<T> exSupplier) {
return exToDefault(exSupplier, null);
}
/**
* 如果有异常,执行异常处理
*
* @param supplier
* 可能会出现异常的方法执行
* @param exConsumer
* 异常处理
* @param <T>
* /
* @return /
*/
public static <T> T exToNull(ExSupplier<T> supplier, Consumer<Exception> exConsumer) {
return exToDefault(supplier, null, exConsumer);
}
/**
* 如果有异常,返回默认值
*
* @param exSupplier
* 可能会出现异常的方法执行
* @param defaultValue
* 默认值
* @param <T>
* /
* @return /
*/
public static <T> T exToDefault(ExSupplier<T> exSupplier, T defaultValue) {
return exToDefault(exSupplier, defaultValue, null);
}
/**
* 如果有异常,执行异常处理,返回默认值
*
* @param exSupplier
* 可能会出现异常的方法执行
* @param defaultValue
* 默认值
* @param exConsumer
* 异常处理
* @param <T>
* /
* @return /
*/
public static <T> T exToDefault(ExSupplier<T> exSupplier, T defaultValue, Consumer<Exception> exConsumer) {
try {
return exSupplier.get();
} catch (Exception ex) {
if (exConsumer != null) {
exConsumer.accept(ex);
}
return defaultValue;
}
}
/**
* 异常提供者
*
* @param <T>
* /
*/
public interface ExSupplier<T> {
/**
* 获取返回值
*
* @return /
* @throws Exception
* /
*/
T get() throws Exception;
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.charles7c.cnadmin.common.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
/**
* 加密/解密工具类
*
* @author Charles7c
* @since 2022/12/21 21:41
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SecureUtils {
/**
* 公钥加密
*
* @param data
* 要加密的内容
* @param publicKey
* 公钥
* @return 公钥加密并 Base64 加密后的内容
*/
public static String encryptByRsaPublicKey(String data, String publicKey) {
return Base64.encode(SecureUtil.rsa(null, publicKey).encrypt(data, KeyType.PublicKey));
}
/**
* 私钥解密
*
* @param data
* 要解密的内容Base64 加密过)
* @param privateKey
* 私钥
* @return 解密后的内容
*/
public static String decryptByRsaPrivateKey(String data, String privateKey) {
return new String(SecureUtil.rsa(privateKey, null).decrypt(Base64.decode(data), KeyType.PrivateKey));
}
/**
* MD5 加密
*
* @param data
* 要加密的内容
* @param salt
* 盐
* @return 加密后的内容
*/
public static String md5Salt(String data, String salt) {
return SecureUtil.md5(SecureUtil.md5(data) + salt);
}
}