Merge branch 'master' into feature_user-session-timeout

# Conflicts:
#	sql/ruoyi-vue-pro.sql
This commit is contained in:
Lyon
2021-03-09 10:07:08 +08:00
40 changed files with 631 additions and 258 deletions

View File

@ -13,7 +13,8 @@ import org.springframework.context.annotation.Configuration;
* @author 芋道源码
*/
@Configuration
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class)
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
public class MybatisConfiguration {
@Bean

View File

@ -21,13 +21,13 @@ public class BaseDO implements Serializable {
*/
private Date updateTime;
/**
* 创建者 TODO 芋艿:迁移成编号
* 创建者
*/
private String createBy;
private String creator;
/**
* 更新者 TODO 芋艿:迁移成编号
* 更新者
*/
private String updateBy;
private String updater;
/**
* 是否删除
*/

View File

@ -0,0 +1,65 @@
package cn.iocoder.dashboard.framework.mybatis.core.handle;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Objects;
/**
* 通用参数填充实现类
*
* 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
*
* @author hexiaowu
*/
@Component
public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
Date current = new Date();
// 创建时间为空,则以当前时间为插入时间
if (Objects.isNull(baseDO.getCreateTime())) {
baseDO.setCreateTime(current);
}
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(baseDO.getUpdateTime())) {
baseDO.setUpdateTime(current);
}
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(loginUser.getId().toString());
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getUpdater())) {
baseDO.setUpdater(loginUser.getId().toString());
}
}
}
@Override
public void updateFill(MetaObject metaObject) {
Object modifyTime = getFieldValByName("updateTime", metaObject);
Object modifier = getFieldValByName("updater", metaObject);
// 获取登录用户信息
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", new Date(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(modifier)) {
setFieldValByName("updater", loginUser.getId(), metaObject);
}
}
}

View File

@ -28,6 +28,10 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
return selectOne(new QueryWrapper<T>().eq(field, value));
}
default Integer selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value));
}
default List<T> selectList() {
return selectList(new QueryWrapper<>());
}

View File

@ -1 +0,0 @@
<http://www.iocoder.cn/Spring-Boot/Spring-Security/?github>

View File

@ -0,0 +1 @@
<https://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao>

View File

@ -152,7 +152,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
httpSecurity.logout().logoutUrl(webProperties.getApiPrefix() + "/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.dashboard.framework.security.core.handler;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
@ -36,6 +37,6 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
securityFrameworkService.logout(token);
}
// 返回成功
ServletUtils.writeJSON(response, null);
ServletUtils.writeJSON(response, CommonResult.success(null));
}
}

View File

@ -2,7 +2,10 @@ package cn.iocoder.dashboard.framework.security.core.util;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
@ -40,9 +43,20 @@ public class SecurityFrameworkUtils {
/**
* 获取当前用户
*
* @return 当前用户
*/
@Nullable
public static LoginUser getLoginUser() {
return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) {
return null;
}
Authentication authentication = context.getAuthentication();
if (authentication == null) {
return null;
}
return (LoginUser) authentication.getPrincipal();
}
/**
@ -50,8 +64,10 @@ public class SecurityFrameworkUtils {
*
* @return 用户编号
*/
@Nullable
public static Long getLoginUserId() {
return getLoginUser().getId();
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
}
/**
@ -59,8 +75,10 @@ public class SecurityFrameworkUtils {
*
* @return 角色编号数组
*/
@Nullable
public static Set<Long> getLoginUserRoleIds() {
return getLoginUser().getRoleIds();
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getRoleIds() : null;
}
/**

View File

@ -10,15 +10,17 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.service.ApiKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

View File

@ -27,9 +27,10 @@ public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 设置 API 前缀,仅仅匹配 controller 包下的
configurer.addPathPrefix(webProperties.getApiPrefix(), clazz ->
clazz.isAnnotationPresent(RestController.class)
&& clazz.getPackage().getName().startsWith(webProperties.getControllerPackage()));
&& clazz.getPackage().getName().startsWith(webProperties.getControllerPackage())); // 仅仅匹配 controller 包
}
// ========== Filter 相关 ==========

View File

@ -12,6 +12,7 @@ import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum;
import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer;
import cn.iocoder.dashboard.modules.infra.service.config.InfConfigService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -99,7 +100,8 @@ public class InfConfigServiceImpl implements InfConfigService {
checkConfigKeyUnique(id, key);
}
private InfConfigDO checkConfigExists(Long id) {
@VisibleForTesting
public InfConfigDO checkConfigExists(Long id) {
if (id == null) {
return null;
}
@ -110,7 +112,8 @@ public class InfConfigServiceImpl implements InfConfigService {
return config;
}
private void checkConfigKeyUnique(Long id, String key) {
@VisibleForTesting
public void checkConfigKeyUnique(Long id, String key) {
InfConfigDO config = configMapper.selectByKey(key);
if (config == null) {
return;

View File

@ -26,7 +26,7 @@ public class SysDeptBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
private Integer sort;
@ApiModelProperty(value = "负责人", example = "芋道")
private String leader;

View File

@ -25,7 +25,7 @@ public class SysPostBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
private Integer sort;
@ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
private Integer status;

View File

@ -23,7 +23,7 @@ public class SysPostExcelVO {
private String name;
@ExcelProperty("岗位排序")
private String sort;
private Integer sort;
@ExcelProperty(value = "状态", converter = DictConvert.class)
@DictFormat(SYS_COMMON_STATUS)

View File

@ -29,7 +29,7 @@ public class SysMenuBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
private Integer sort;
@ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
@NotNull(message = "父菜单 ID 不能为空")

View File

@ -25,7 +25,7 @@ public class SysRoleBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
private Integer sort;
@ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 SysRoleTypeEnum 枚举")
private Integer type;

View File

@ -35,7 +35,7 @@ public class SysDeptDO extends BaseDO {
/**
* 显示顺序
*/
private String sort;
private Integer sort;
/**
* 负责人
*/

View File

@ -34,7 +34,7 @@ public class SysPostDO extends BaseDO {
/**
* 岗位排序
*/
private String sort;
private Integer sort;
/**
* 状态
*

View File

@ -49,7 +49,7 @@ public class SysMenuDO extends BaseDO {
/**
* 显示顺序
*/
private String sort;
private Integer sort;
/**
* 父菜单ID
*/

View File

@ -15,13 +15,13 @@ import java.util.List;
@Mapper
public interface SysDictDataMapper extends BaseMapperX<SysDictDataDO> {
default SysDictDataDO selectByDictTypeAndLabel(String dictType, String label) {
default SysDictDataDO selectByDictTypeAndValue(String dictType, String value) {
return selectOne(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType)
.eq("label", label));
.eq("value", value));
}
default int selectCountByDictType(String dictType) {
return selectCount(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType));
return selectCount("dict_type", dictType);
}
default PageResult<SysDictDataDO> selectPage(SysDictDataPageReqVO reqVO) {

View File

@ -160,7 +160,26 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override
public void logout(String token) {
// AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); TODO 需要搞一搞
// 查询用户信息
LoginUser loginUser = userSessionService.getLoginUser(token);
if (loginUser == null) {
return;
}
// 删除 session
userSessionService.deleteUserSession(token);
// 记录登出日子和
this.createLogoutLog(loginUser.getUsername());
}
private void createLogoutLog(String username) {
SysLoginLogCreateReqVO reqVO = new SysLoginLogCreateReqVO();
reqVO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqVO.setTraceId(TracerUtils.getTraceId());
reqVO.setUsername(username);
reqVO.setUserAgent(ServletUtils.getUserAgent());
reqVO.setUserIp(ServletUtils.getClientIP());
reqVO.setResult(SysLoginResultEnum.SUCCESS.getResult());
loginLogService.createLoginLog(reqVO);
}
@Override

View File

@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.dict.impl;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO;
@ -10,12 +9,13 @@ import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataEx
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataUpdateReqVO;
import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictDataDO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictDataMapper;
import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableTable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
@ -28,6 +28,7 @@ import java.util.Comparator;
import java.util.Date;
import java.util.List;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
/**
@ -156,7 +157,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Override
public Long createDictData(SysDictDataCreateReqVO reqVO) {
// 校验正确性
this.checkCreateOrUpdate(null, reqVO.getLabel(), reqVO.getDictType());
this.checkCreateOrUpdate(null, reqVO.getValue(), reqVO.getDictType());
// 插入字典类型
SysDictDataDO dictData = SysDictDataConvert.INSTANCE.convert(reqVO);
dictDataMapper.insert(dictData);
@ -168,7 +169,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Override
public void updateDictData(SysDictDataUpdateReqVO reqVO) {
// 校验正确性
this.checkCreateOrUpdate(reqVO.getId(), reqVO.getLabel(), reqVO.getDictType());
this.checkCreateOrUpdate(reqVO.getId(), reqVO.getValue(), reqVO.getDictType());
// 更新字典类型
SysDictDataDO updateObj = SysDictDataConvert.INSTANCE.convert(reqVO);
dictDataMapper.updateById(updateObj);
@ -191,46 +192,49 @@ public class SysDictDataServiceImpl implements SysDictDataService {
return dictDataMapper.selectCountByDictType(dictType);
}
private void checkCreateOrUpdate(Long id, String label, String dictType) {
private void checkCreateOrUpdate(Long id, String value, String dictType) {
// 校验自己存在
checkDictDataExists(id);
// 校验字典类型有效
checkDictTypeValid(dictType);
// 校验字典数据的值的唯一性
checkDictDataValueUnique(id, dictType, label);
checkDictDataValueUnique(id, dictType, value);
}
private void checkDictDataValueUnique(Long id, String dictType, String label) {
SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndLabel(dictType, label);
@VisibleForTesting
public void checkDictDataValueUnique(Long id, String dictType, String value) {
SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndValue(dictType, value);
if (dictData == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的字典数据
if (id == null) {
throw ServiceExceptionUtil.exception(DICT_DATA_VALUE_DUPLICATE);
throw exception(DICT_DATA_VALUE_DUPLICATE);
}
if (!dictData.getId().equals(id)) {
throw ServiceExceptionUtil.exception(DICT_DATA_VALUE_DUPLICATE);
throw exception(DICT_DATA_VALUE_DUPLICATE);
}
}
private void checkDictDataExists(Long id) {
@VisibleForTesting
public void checkDictDataExists(Long id) {
if (id == null) {
return;
}
SysDictDataDO dictData = dictDataMapper.selectById(id);
if (dictData == null) {
throw ServiceExceptionUtil.exception(DICT_DATA_NOT_EXISTS);
throw exception(DICT_DATA_NOT_EXISTS);
}
}
private void checkDictTypeValid(String type) {
@VisibleForTesting
public void checkDictTypeValid(String type) {
SysDictTypeDO dictType = dictTypeService.getDictType(type);
if (dictType == null) {
throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_EXISTS);
throw exception(DICT_TYPE_NOT_EXISTS);
}
if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) {
throw ServiceExceptionUtil.exception(DICT_TYPE_NOT_ENABLE);
throw exception(DICT_TYPE_NOT_ENABLE);
}
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.dashboard.modules.system.dal.dataobject.dict.SysDictTypeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dict.SysDictTypeMapper;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -97,8 +98,9 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
checkDictTypeUnique(id, type);
}
private void checkDictTypeNameUnique(Long id, String type) {
SysDictTypeDO dictType = dictTypeMapper.selectByName(type);
@VisibleForTesting
public void checkDictTypeNameUnique(Long id, String name) {
SysDictTypeDO dictType = dictTypeMapper.selectByName(name);
if (dictType == null) {
return;
}
@ -111,7 +113,8 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
}
}
private void checkDictTypeUnique(Long id, String type) {
@VisibleForTesting
public void checkDictTypeUnique(Long id, String type) {
SysDictTypeDO dictType = dictTypeMapper.selectByType(type);
if (dictType == null) {
return;
@ -125,7 +128,8 @@ public class SysDictTypeServiceImpl implements SysDictTypeService {
}
}
private SysDictTypeDO checkDictTypeExists(Long id) {
@VisibleForTesting
public SysDictTypeDO checkDictTypeExists(Long id) {
if (id == null) {
return null;
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.dashboard.util.collection;
import cn.hutool.core.util.ArrayUtil;
import java.util.function.Consumer;
/**
* Array 工具类
*
@ -18,11 +20,11 @@ public class ArrayUtils {
* @return 结果数组
*/
@SafeVarargs
public static <T> T[] append(T object, T... newElements) {
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
if (object == null) {
return newElements;
}
T[] result = ArrayUtil.newArray(object.getClass(), 1 + newElements.length);
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
result[0] = object;
System.arraycopy(newElements, 0, result, 1, newElements.length);
return result;

View File

@ -19,4 +19,14 @@ public class ObjectUtils {
return result;
}
public static <T extends Comparable<T>> T max(T obj1, T obj2) {
if (obj1 == null) {
return obj2;
}
if (obj2 == null) {
return obj1;
}
return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
}
}