完善:完善用户登录 API,优化部分包结构(引入 MyBatis Plus、多数据源、P6Spy、Liquibase 等依赖,详情可见 README 介绍)

This commit is contained in:
2022-12-25 12:35:35 +08:00
parent 00e2b44d0e
commit 78e84e8941
28 changed files with 954 additions and 106 deletions

View File

@@ -0,0 +1,130 @@
/*
* 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.mybatis;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import cn.hutool.core.util.ObjectUtil;
import top.charles7c.cnadmin.common.exception.ServiceException;
import top.charles7c.cnadmin.common.model.entity.BaseEntity;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
/**
* MyBatis Plus 元对象处理器配置(插入或修改时自动填充)
*
* @author Charles7c
* @since 2022/12/22 19:52
*/
public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
/** 创建人 */
private static final String CREATE_USER = "createUser";
/** 创建时间 */
private static final String CREATE_TIME = "createTime";
/** 修改人 */
private static final String UPDATE_USER = "updateUser";
/** 修改时间 */
private static final String UPDATE_TIME = "updateTime";
/**
* 插入数据时填充
*
* @param metaObject
* 元对象
*/
@Override
public void insertFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNull(metaObject)) {
return;
}
Long createUser = LoginHelper.getUserId();
Date createTime = new Date();
if (metaObject.getOriginalObject() instanceof BaseEntity) {
// 继承了 BaseEntity 的类,填充创建信息
BaseEntity baseEntity = (BaseEntity)metaObject.getOriginalObject();
baseEntity.setCreateUser(baseEntity.getCreateUser() != null ? baseEntity.getCreateUser() : createUser);
baseEntity.setCreateTime(baseEntity.getCreateTime() != null ? baseEntity.getCreateTime() : createTime);
baseEntity.setUpdateUser(baseEntity.getUpdateUser() != null ? baseEntity.getUpdateUser() : createUser);
baseEntity.setUpdateTime(baseEntity.getUpdateTime() != null ? baseEntity.getUpdateTime() : createTime);
} else {
// 未继承 BaseEntity 的类,根据类中拥有的创建信息进行填充,不存在创建信息不进行填充
this.fillFieldValue(metaObject, CREATE_USER, createUser, false);
this.fillFieldValue(metaObject, CREATE_TIME, createTime, false);
this.fillFieldValue(metaObject, UPDATE_USER, createUser, false);
this.fillFieldValue(metaObject, UPDATE_TIME, createTime, false);
}
} catch (Exception e) {
throw new ServiceException("插入数据时自动填充异常:" + e.getMessage());
}
}
/**
* 修改数据时填充
*
* @param metaObject
* 元对象
*/
@Override
public void updateFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNull(metaObject)) {
return;
}
Long updateUser = LoginHelper.getUserId();
Date updateTime = new Date();
if (metaObject.getOriginalObject() instanceof BaseEntity) {
// 继承了 BaseEntity 的类,填充修改信息
BaseEntity baseEntity = (BaseEntity)metaObject.getOriginalObject();
baseEntity.setUpdateUser(updateUser);
baseEntity.setUpdateTime(updateTime);
} else {
// 未继承 BaseEntity 的类,根据类中拥有的修改信息进行填充,不存在修改信息不进行填充
this.fillFieldValue(metaObject, UPDATE_USER, updateUser, true);
this.fillFieldValue(metaObject, UPDATE_TIME, updateTime, true);
}
} catch (Exception e) {
throw new ServiceException("修改数据时自动填充异常:" + e.getMessage());
}
}
/**
* 填充属性值
*
* @param metaObject
* 元数据对象
* @param fieldName
* 要填充的属性名
* @param fillFieldValue
* 要填充的属性值
* @param isOverride
* 如果属性值不为空是否覆盖true 覆盖、false 不覆盖)
*/
private void fillFieldValue(MetaObject metaObject, String fieldName, Object fillFieldValue, boolean isOverride) {
if (metaObject.hasSetter(fieldName)) {
Object fieldValue = metaObject.getValue(fieldName);
setFieldValByName(fieldName, fieldValue != null && !isOverride ? fieldValue : fillFieldValue, metaObject);
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.mybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import cn.hutool.core.net.NetUtil;
/**
* MyBatis Plus 配置
*
* @author Charles7c
* @since 2022/12/22 19:51
*/
@Configuration
@MapperScan("${mybatis-plus.mapper-package}")
public class MybatisPlusConfiguration {
/**
* 插件配置
*
* @return /
*/
@Bean
MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
return interceptor;
}
/**
* 分页插件配置(<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">...</a>
*/
private PaginationInnerInterceptor paginationInnerInterceptor() {
// 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
// PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 溢出总页数后是否进行处理
paginationInnerInterceptor.setOverflow(false);
// 单页分页条数限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 元对象处理器配置(插入或修改时自动填充)
*/
@Bean
MetaObjectHandler metaObjectHandler() {
return new MyBatisPlusMetaObjectHandler();
}
/**
* ID 生成器配置仅在主键类型idType配置为 ASSIGN_ID 或 ASSIGN_UUID 时有效(使用网卡信息绑定雪花生成器,防止集群雪花 ID 重复)
*/
@Bean
IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
}

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/23 22:55
*/
@NoArgsConstructor
public class ServiceException extends RuntimeException {
public ServiceException(String message) {
super(message);
}
}

View File

@@ -19,10 +19,12 @@ package top.charles7c.cnadmin.common.handler;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -37,6 +39,8 @@ import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.exception.BadRequestException;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
import top.charles7c.cnadmin.common.util.StreamUtils;
/**
* 全局异常处理器
@@ -85,7 +89,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public R handleBindException(BindException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, "");
return R.fail(HttpStatus.BAD_REQUEST.value(), message);
}
/**
@@ -95,7 +100,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, "");
return R.fail(HttpStatus.BAD_REQUEST.value(), message);
}
/**
@@ -105,7 +111,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return R.fail(HttpStatus.BAD_REQUEST.value(), ExceptionUtils
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()));
}
/**

View File

@@ -0,0 +1,78 @@
/*
* 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.dto;
import java.io.Serializable;
import lombok.Data;
/**
* 登录用户信息
*
* @author Charles7c
* @since 2022/12/24 13:01
*/
@Data
public class LoginUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户 ID
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 性别0未知 1男 2女
*/
private Integer gender;
/**
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 头像地址
*/
private String avatar;
/**
* 备注
*/
private String notes;
/**
* 状态1启用 2禁用
*/
private Integer status;
}

View File

@@ -21,6 +21,9 @@ import java.util.Date;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
/**
* 实体类基类
*
@@ -35,20 +38,24 @@ public class BaseEntity implements Serializable {
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -0,0 +1,59 @@
/*
* 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.Collection;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import cn.hutool.core.collection.CollUtil;
/**
* Stream 工具类
*
* @author Charles7c
* @since 2022/12/22 19:51
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StreamUtils {
/**
* 将集合中的指定字段使用分隔符拼接成字符串
*
* @param collection
* 集合
* @param function
* 字段方法
* @param delimiter
* 分隔符
* @param <E>
* /
* @return 拼接结果
*/
public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
if (CollUtil.isEmpty(collection)) {
return StringUtils.EMPTY;
}
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
/**
* 登录助手
*
* @author Charles7c
* @since 2022/12/24 12:58
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginHelper {
private static final String LOGIN_USER_KEY = "LOGIN_USER";
/**
* 用户登录并缓存用户信息
*
* @param loginUser
* 登录用户信息
*/
public static void login(LoginUser loginUser) {
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getUserId());
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}
/**
* 获取登录用户信息
*
* @return /
*/
public static LoginUser getLoginUser() {
LoginUser loginUser = (LoginUser)SaHolder.getStorage().get(LOGIN_USER_KEY);
if (loginUser != null) {
return loginUser;
}
try {
loginUser = (LoginUser)StpUtil.getTokenSession().get(LOGIN_USER_KEY);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
} catch (Exception ignored) {
}
return loginUser;
}
/**
* 获取登录用户 ID
*
* @return /
*/
public static Long getUserId() {
return ExceptionUtils.exToNull(() -> getLoginUser().getUserId());
}
/**
* 获取登录用户名
*
* @return /
*/
public static String getUsername() {
return ExceptionUtils.exToNull(() -> getLoginUser().getUsername());
}
/**
* 获取登录用户昵称
*
* @return /
*/
public static String getNickname() {
return ExceptionUtils.exToNull(() -> getLoginUser().getNickname());
}
}