Merge branch 'master' into unit_logger_system

# Conflicts:
#	src/test/resources/sql/clean.sql
#	src/test/resources/sql/create_tables.sql
This commit is contained in:
wangkai
2021-03-10 18:29:03 +08:00
59 changed files with 1958 additions and 641 deletions

View File

@@ -1,5 +1,7 @@
package cn.iocoder.dashboard.framework.mybatis.config;
import cn.iocoder.dashboard.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.annotations.Mapper;
@@ -13,7 +15,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
@@ -23,4 +26,9 @@ public class MybatisConfiguration {
return mybatisPlusInterceptor;
}
@Bean
public MetaObjectHandler defaultMetaObjectHandler(){
return new DefaultDBFieldHandler(); // 自动填充参数类
}
}

View File

@@ -1,5 +1,7 @@
package cn.iocoder.dashboard.framework.mybatis.core.dataobject;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
@@ -15,19 +17,27 @@ public class BaseDO implements Serializable {
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 最后更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 创建者 TODO 芋艿:迁移成编号
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
private String createBy;
@TableField(fill = FieldFill.INSERT)
private String creator;
/**
* 更新者 TODO 芋艿:迁移成编号
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
private String updateBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updater;
/**
* 是否删除
*/

View File

@@ -0,0 +1,63 @@
package cn.iocoder.dashboard.framework.mybatis.core.handler;
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 java.util.Date;
import java.util.Objects;
/**
* 通用参数填充实现类
*
* 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
*
* @author hexiaowu
*/
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().toString(), 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 authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
@@ -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

@@ -1,11 +1,9 @@
package cn.iocoder.dashboard.modules.infra.controller.config;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.*;
import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
@@ -13,95 +11,95 @@ import cn.iocoder.dashboard.modules.infra.service.config.InfConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_SENSITIVE;
@Api(tags = "参数配置")
@RestController
@RequestMapping("/infra/config")
@Validated
public class InfConfigController {
@Resource
private InfConfigService configService;
@ApiOperation("获取参数配置分页")
@GetMapping("/page")
// @PreAuthorize("@ss.hasPermi('infra:config:list')")
public CommonResult<PageResult<InfConfigRespVO>> getConfigPage(@Validated InfConfigPageReqVO reqVO) {
PageResult<InfConfigDO> page = configService.getConfigPage(reqVO);
return success(InfConfigConvert.INSTANCE.convertPage(page));
@PostMapping("/create")
@ApiOperation("创建参数配置")
@PreAuthorize("@ss.hasPermission('infra:config:create')")
public CommonResult<Long> createConfig(@Valid @RequestBody InfConfigCreateReqVO reqVO) {
return success(configService.createConfig(reqVO));
}
@ApiOperation("导出参数配置")
@GetMapping("/export")
// @Log(title = "参数管理", businessType = BusinessType.EXPORT)
// @PreAuthorize("@ss.hasPermi('infra:config:export')")
public void exportSysConfig(HttpServletResponse response, @Validated InfConfigExportReqVO reqVO) throws IOException {
List<InfConfigDO> list = configService.getConfigList(reqVO);
// 拼接数据
List<InfConfigExcelVO> excelDataList = InfConfigConvert.INSTANCE.convertList(list);
// 输出
ExcelUtils.write(response, "参数配置.xls", "配置列表",
InfConfigExcelVO.class, excelDataList);
@PutMapping("/update")
@ApiOperation("修改参数配置")
@PreAuthorize("@ss.hasPermission('infra:config:update')")
public CommonResult<Boolean> updateConfig(@Valid @RequestBody InfConfigUpdateReqVO reqVO) {
configService.updateConfig(reqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除参数配置")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:config:delete')")
public CommonResult<Boolean> deleteConfig(@RequestParam("id") Long id) {
configService.deleteConfig(id);
return success(true);
}
@GetMapping(value = "/get")
@ApiOperation("获得参数配置")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@GetMapping(value = "/get")
// @PreAuthorize("@ss.hasPermi('infra:config:query')")
@PreAuthorize("@ss.hasPermission('infra:config:query')")
public CommonResult<InfConfigRespVO> getConfig(@RequestParam("id") Long id) {
return success(InfConfigConvert.INSTANCE.convert(configService.getConfig(id)));
}
@GetMapping(value = "/get-value-by-key")
@ApiOperation(value = "根据参数键名查询参数值", notes = "敏感配置,不允许返回给前端")
@ApiImplicitParam(name = "key", value = "参数键", required = true, example = "yunai.biz.username", dataTypeClass = String.class)
@GetMapping(value = "/get-value-by-key")
public CommonResult<String> getConfigKey(@RequestParam("key") String key) {
InfConfigDO config = configService.getConfigByKey(key);
if (config == null) {
return null;
}
if (config.getSensitive()) {
throw ServiceExceptionUtil.exception(CONFIG_GET_VALUE_ERROR_IF_SENSITIVE);
throw exception(CONFIG_GET_VALUE_ERROR_IF_SENSITIVE);
}
return success(config.getValue());
}
@ApiOperation("新增参数配置")
@PostMapping("/create")
// @PreAuthorize("@ss.hasPermi('infra:config:add')")
// @Log(title = "参数管理", businessType = BusinessType.INSERT)
@Idempotent(timeout = 60, keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#reqVO.key")
public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) {
return success(configService.createConfig(reqVO));
@GetMapping("/page")
@ApiOperation("获取参数配置分页")
@PreAuthorize("@ss.hasPermission('infra:config:query')")
public CommonResult<PageResult<InfConfigRespVO>> getConfigPage(@Valid InfConfigPageReqVO reqVO) {
PageResult<InfConfigDO> page = configService.getConfigPage(reqVO);
return success(InfConfigConvert.INSTANCE.convertPage(page));
}
@ApiOperation("修改参数配置")
@PutMapping("/update")
// @PreAuthorize("@ss.hasPermi('infra:config:edit')")
// @Log(title = "参数管理", businessType = BusinessType.UPDATE)
@Idempotent(timeout = 60)
public CommonResult<Boolean> updateConfig(@Validated @RequestBody InfConfigUpdateReqVO reqVO) {
configService.updateConfig(reqVO);
return success(true);
}
@ApiOperation("删除参数配置")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@DeleteMapping("/delete")
// @PreAuthorize("@ss.hasPermi('infra:config:remove')")
// @Log(title = "参数管理", businessType = BusinessType.DELETE)
public CommonResult<Boolean> deleteConfig(@RequestParam("id") Long id) {
configService.deleteConfig(id);
return success(true);
@GetMapping("/export")
@ApiOperation("导出参数配置")
@PreAuthorize("@ss.hasPermission('infra:config:export')")
@OperateLog(type = EXPORT)
public void exportSysConfig(@Valid InfConfigExportReqVO reqVO,
HttpServletResponse response) throws IOException {
List<InfConfigDO> list = configService.getConfigList(reqVO);
// 拼接数据
List<InfConfigExcelVO> datas = InfConfigConvert.INSTANCE.convertList(list);
// 输出
ExcelUtils.write(response, "参数配置.xls", "数据", InfConfigExcelVO.class, datas);
}
}

View File

@@ -75,7 +75,7 @@ public class InfJobLogController {
List<InfJobLogDO> list = jobLogService.getJobLogList(exportReqVO);
// 导出 Excel
List<InfJobLogExcelVO> datas = InfJobLogConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "定时任务.xls", "数据", InfJobLogExcelVO.class, datas);
ExcelUtils.write(response, "任务日志.xls", "数据", InfJobLogExcelVO.class, datas);
}
}

View File

@@ -14,20 +14,21 @@ import java.util.List;
@Mapper
public interface InfConfigMapper extends BaseMapperX<InfConfigDO> {
default PageResult<InfConfigDO> selectPage(InfConfigPageReqVO reqVO) {
return selectPage(reqVO,
new QueryWrapperX<InfConfigDO>().likeIfPresent("name", reqVO.getName())
.likeIfPresent("`key`", reqVO.getKey())
.eqIfPresent("`type`", reqVO.getType())
.betweenIfPresent("create_time", reqVO.getBeginTime(), reqVO.getEndTime()));
}
default InfConfigDO selectByKey(String key) {
return selectOne(new QueryWrapper<InfConfigDO>().eq("`key`", key));
}
default PageResult<InfConfigDO> selectPage(InfConfigPageReqVO reqVO) {
return selectPage(reqVO, new QueryWrapperX<InfConfigDO>()
.likeIfPresent("name", reqVO.getName())
.likeIfPresent("`key`", reqVO.getKey())
.eqIfPresent("`type`", reqVO.getType())
.betweenIfPresent("create_time", reqVO.getBeginTime(), reqVO.getEndTime()));
}
default List<InfConfigDO> selectList(InfConfigExportReqVO reqVO) {
return selectList(new QueryWrapperX<InfConfigDO>().likeIfPresent("name", reqVO.getName())
return selectList(new QueryWrapperX<InfConfigDO>()
.likeIfPresent("name", reqVO.getName())
.likeIfPresent("`key`", reqVO.getKey())
.eqIfPresent("`type`", reqVO.getType())
.betweenIfPresent("create_time", reqVO.getBeginTime(), reqVO.getEndTime()));

View File

@@ -7,28 +7,37 @@ import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigPageReqV
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
import javax.validation.Valid;
import java.util.List;
/**
* 参数配置 Service 接口
*
* @author 芋道源码
*/
public interface InfConfigService {
/**
* 获得参数配置分页列表
* 创建参数配置
*
* @param reqVO 分页条件
* @return 分页列表
* @param reqVO 创建信息
* @return 配置编号
*/
PageResult<InfConfigDO> getConfigPage(InfConfigPageReqVO reqVO);
Long createConfig(@Valid InfConfigCreateReqVO reqVO);
/**
* 获得参数配置列表
* 更新参数配置
*
* @param reqVO 列表
* @return 列表
* @param reqVO 更新信息
*/
List<InfConfigDO> getConfigList(InfConfigExportReqVO reqVO);
void updateConfig(@Valid InfConfigUpdateReqVO reqVO);
/**
* 删除参数配置
*
* @param id 配置编号
*/
void deleteConfig(Long id);
/**
* 获得参数配置
@@ -47,25 +56,20 @@ public interface InfConfigService {
InfConfigDO getConfigByKey(String key);
/**
* 创建参数配置
* 获得参数配置分页列表
*
* @param reqVO 创建信息
* @return 配置编号
* @param reqVO 分页条件
* @return 分页列表
*/
Long createConfig(InfConfigCreateReqVO reqVO);
PageResult<InfConfigDO> getConfigPage(@Valid InfConfigPageReqVO reqVO);
/**
* 更新参数配置
* 获得参数配置列表
*
* @param reqVO 更新信息
* @param reqVO 列表
* @return 列表
*/
void updateConfig(InfConfigUpdateReqVO reqVO);
List<InfConfigDO> getConfigList(@Valid InfConfigExportReqVO reqVO);
/**
* 删除参数配置
*
* @param id 配置编号
*/
void deleteConfig(Long id);
}

View File

@@ -12,8 +12,10 @@ 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;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@@ -26,6 +28,7 @@ import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
*/
@Service
@Slf4j
@Validated
public class InfConfigServiceImpl implements InfConfigService {
@Resource
@@ -34,26 +37,6 @@ public class InfConfigServiceImpl implements InfConfigService {
@Resource
private InfConfigProducer configProducer;
@Override
public PageResult<InfConfigDO> getConfigPage(InfConfigPageReqVO reqVO) {
return configMapper.selectPage(reqVO);
}
@Override
public List<InfConfigDO> getConfigList(InfConfigExportReqVO reqVO) {
return configMapper.selectList(reqVO);
}
@Override
public InfConfigDO getConfig(Long id) {
return configMapper.selectById(id);
}
@Override
public InfConfigDO getConfigByKey(String key) {
return configMapper.selectByKey(key);
}
@Override
public Long createConfig(InfConfigCreateReqVO reqVO) {
// 校验正确性
@@ -92,6 +75,26 @@ public class InfConfigServiceImpl implements InfConfigService {
configProducer.sendConfigRefreshMessage();
}
@Override
public InfConfigDO getConfig(Long id) {
return configMapper.selectById(id);
}
@Override
public InfConfigDO getConfigByKey(String key) {
return configMapper.selectByKey(key);
}
@Override
public PageResult<InfConfigDO> getConfigPage(InfConfigPageReqVO reqVO) {
return configMapper.selectPage(reqVO);
}
@Override
public List<InfConfigDO> getConfigList(InfConfigExportReqVO reqVO) {
return configMapper.selectList(reqVO);
}
private void checkCreateOrUpdate(Long id, String key) {
// 校验自己存在
checkConfigExists(id);
@@ -99,7 +102,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 +114,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

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

View File

@@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
@@ -24,8 +25,8 @@ public class SysPostBaseVO {
private String code;
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
@NotNull(message = "显示顺序不能为空")
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

@@ -15,7 +15,7 @@ import javax.validation.constraints.Size;
public class SysDictDataBaseVO {
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@ApiModelProperty(value = "字典标签", required = true, example = "芋道")

View File

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

View File

@@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
@@ -24,8 +25,8 @@ public class SysRoleBaseVO {
private String code;
@ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
@NotBlank(message = "显示顺序不能为空")
private String sort;
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 SysRoleTypeEnum 枚举")
private Integer type;

View File

@@ -10,6 +10,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 在线用户表
*
@@ -36,6 +38,14 @@ public class SysUserSessionDO extends BaseDO {
* 关联 {@link SysUserDO#getId()}
*/
private Long userId;
/**
* 用户账号
*
* 冗余,因为账号可以变更
*/
private String username;
/**
* 用户 IP
*/
@@ -44,5 +54,9 @@ public class SysUserSessionDO extends BaseDO {
* 浏览器 UA
*/
private String userAgent;
/**
* 会话超时时间
*/
private Date sessionTimeout;
}

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

@@ -8,6 +8,8 @@ import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@Mapper
public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
@@ -18,4 +20,7 @@ public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
.likeIfPresent("user_ip", reqVO.getUserIp()));
}
default List<SysUserSessionDO> selectListBySessionTimoutLt() {
return selectList(new QueryWrapperX<SysUserSessionDO>().lt("session_timeout",new Date()));
}
}

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

@@ -16,7 +16,7 @@ public interface SysRedisKeyConstants {
RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存",
"login_user:%s", // 参数为 sessionId
STRING, LoginUser.class, Duration.ofMinutes(30));
STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
"captcha_code:%s", // 参数为 uuid

View File

@@ -1,11 +1,13 @@
package cn.iocoder.dashboard.modules.system.dal.redis.auth;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.dashboard.util.json.JsonUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.time.Duration;
import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
@@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private SysUserSessionService sysUserSessionService;
public LoginUser get(String sessionId) {
String redisKey = formatKey(sessionId);
@@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO {
public void set(String sessionId, LoginUser loginUser) {
String redisKey = formatKey(sessionId);
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), LOGIN_USER.getTimeout());
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
Duration.ofMillis(sysUserSessionService.getSessionTimeoutMillis()));
}
public void delete(String sessionId) {

View File

@@ -1,23 +1,30 @@
package cn.iocoder.dashboard.modules.system.job.auth;
import cn.iocoder.dashboard.framework.quartz.core.handler.JobHandler;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 用户 Session 超时 Job
*
* @author 芋道源码
* @author
*/
@Component
@Slf4j
public class SysUserSessionTimeoutJob implements JobHandler {
@Resource
private SysUserSessionService sysUserSessionService;
@Override
public String execute(String param) throws Exception {
// System.out.println("执行了一次任务");
log.info("[execute][执行任务:{}]", param);
return null;
// 执行过期
Long timeoutCount = sysUserSessionService.clearSessionTimeout();
// 返回结果,记录每次的超时数量
return String.format("移除在线会话数量为 %s 个", timeoutCount);
}
}

View File

@@ -60,4 +60,10 @@ public interface SysUserSessionService {
*/
PageResult<SysUserSessionDO> getUserSessionPage(SysUserSessionPageReqVO reqVO);
/**
* 移除超时的在线用户
*
* @return {@link Long } 移出的超时用户数量
**/
long clearSessionTimeout();
}

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

@@ -6,20 +6,28 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO;
import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginLogTypeEnum;
import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginResultEnum;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.dashboard.util.date.DateUtils.addTime;
/**
* 在线用户 Session Service 实现类
@@ -31,14 +39,14 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
@Resource
private SecurityProperties securityProperties;
@Resource
private SysLoginUserRedisDAO loginUserRedisDAO;
@Resource
private SysUserSessionMapper userSessionMapper;
@Resource
private SysUserService userService;
@Resource
private SysLoginLogService loginLogService;
@Override
public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
@@ -49,7 +57,9 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
loginUserRedisDAO.set(sessionId, loginUser);
// 写入 DB 中
SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
.userId(loginUser.getId()).userIp(userIp).userAgent(userAgent).build();
.userId(loginUser.getId()).userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
.sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
.build();
userSessionMapper.insert(userSession);
// 返回 Session 编号
return sessionId;
@@ -62,7 +72,9 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
loginUserRedisDAO.set(sessionId, loginUser);
// 更新 DB 中
SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build();
updateObj.setUsername(loginUser.getUsername());
updateObj.setUpdateTime(new Date());
updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
userSessionMapper.updateById(updateObj);
}
@@ -97,6 +109,36 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
return userSessionMapper.selectPage(reqVO, userIds);
}
@Override
public long clearSessionTimeout() {
// 获取db里已经超时的用户列表
List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();
Map<String, SysUserSessionDO> timeoutSessionDOMap = sessionTimeoutDOS
.stream()
.filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null)
.collect(Collectors.toMap(SysUserSessionDO::getId, o -> o));
// 确认已经超时,按批次移出在线用户列表
if (CollUtil.isNotEmpty(timeoutSessionDOMap)) {
Lists.partition(new ArrayList<>(timeoutSessionDOMap.keySet()), 100).forEach(userSessionMapper::deleteBatchIds);
//记录用户超时退出日志
createTimeoutLogoutLog(timeoutSessionDOMap.values());
}
return timeoutSessionDOMap.size();
}
private void createTimeoutLogoutLog(Collection<SysUserSessionDO> timeoutSessionDOS) {
for (SysUserSessionDO timeoutSessionDO : timeoutSessionDOS) {
SysLoginLogCreateReqVO reqVO = new SysLoginLogCreateReqVO();
reqVO.setLogType(SysLoginLogTypeEnum.LOGOUT_TIMEOUT.getType());
reqVO.setTraceId(TracerUtils.getTraceId());
reqVO.setUsername(timeoutSessionDO.getUsername());
reqVO.setUserAgent(timeoutSessionDO.getUserAgent());
reqVO.setUserIp(timeoutSessionDO.getUserIp());
reqVO.setResult(SysLoginResultEnum.SUCCESS.getResult());
loginLogService.createLoginLog(reqVO);
}
}
/**
* 生成 Session 编号,目前采用 UUID 算法
*

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

@@ -9,6 +9,7 @@ import cn.iocoder.dashboard.modules.system.convert.notice.SysNoticeConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.notice.SysNoticeMapper;
import cn.iocoder.dashboard.modules.system.dal.dataobject.notice.SysNoticeDO;
import cn.iocoder.dashboard.modules.system.service.notice.SysNoticeService;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -60,7 +61,8 @@ public class SysNoticeServiceImpl implements SysNoticeService {
noticeMapper.deleteById(id);
}
private void checkNoticeExists(Long id) {
@VisibleForTesting
public void checkNoticeExists(Long id) {
if (id == null) {
return;
}

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;
}
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.dashboard;
import cn.iocoder.dashboard.config.RedisTestConfiguration;
import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
import cn.iocoder.dashboard.framework.redis.config.RedisConfig;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* 依赖内存 DB 的单元测试
*
* 注意Service 层同样适用。对于 Service 层的单元测试,我们针对自己模块的 Mapper 走的是 H2 内存数据库,针对别的模块的 Service 走的是 Mock 方法
*
* @author 芋道源码
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
public class BaseDbAndRedisUnitTest {
@Import({
// DB 配置类
DataSourceConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
// MyBatis 配置类
MybatisConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
RedisAutoConfiguration.class, // Spring Redis 自动配置类
RedisConfig.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})
public static class Application {
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.dashboard;
import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* 依赖内存 DB 的单元测试
*
* 注意Service 层同样适用。对于 Service 层的单元测试,我们针对自己模块的 Mapper 走的是 H2 内存数据库,针对别的模块的 Service 走的是 Mock 方法
*
* @author 芋道源码
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
public class BaseDbUnitTest {
@Import({
// DB 配置类
DataSourceConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
// MyBatis 配置类
MybatisConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
})
public static class Application {
}
}

View File

@@ -12,6 +12,7 @@ import javax.annotation.Resource;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
@Deprecated
public class BaseSpringBootUnitTest {
@Resource

View File

@@ -8,12 +8,10 @@ import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.io.IOException;
@Configuration(proxyBeanMethods = false)
@Lazy(false) // 禁用懒加载,因为需要保证 Redis Server 必须先启动
@EnableConfigurationProperties(RedisProperties.class)
@AutoConfigureBefore({RedisAutoConfiguration.class, RedissonAutoConfiguration.class}) // 在 Redis 自动配置前,进行初始化
public class RedisTestConfiguration {

View File

@@ -1,16 +0,0 @@
package cn.iocoder.dashboard.config;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@Configuration
public class SecurityTestConfiguration {
@Bean
public AuthenticationManager authenticationManager() {
return Mockito.mock(AuthenticationManager.class);
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.infra.service.config;
import cn.iocoder.dashboard.BaseSpringBootUnitTest;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigExportReqVO;
@@ -15,6 +15,7 @@ import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
@@ -24,8 +25,7 @@ import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.RandomUtils.*;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
@@ -36,7 +36,8 @@ import static org.mockito.Mockito.verify;
*
* @author 芋道源码
*/
public class InfConfigServiceTest extends BaseSpringBootUnitTest {
@Import(InfConfigServiceImpl.class)
public class InfConfigServiceTest extends BaseDbUnitTest {
@Resource
private InfConfigServiceImpl configService;
@@ -46,6 +47,120 @@ public class InfConfigServiceTest extends BaseSpringBootUnitTest {
@MockBean
private InfConfigProducer configProducer;
@Test
public void testCreateConfig_success() {
// 准备参数
InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
// 调用
Long configId = configService.createConfig(reqVO);
// 断言
assertNotNull(configId);
// 校验记录的属性是否正确
InfConfigDO config = configMapper.selectById(configId);
assertPojoEquals(reqVO, config);
assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testUpdateConfig_success() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class, o -> {
o.setId(dbConfig.getId()); // 设置更新的 ID
});
// 调用
configService.updateConfig(reqVO);
// 校验是否更新正确
InfConfigDO config = configMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, config);
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testDeleteConfig_success() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用
configService.deleteConfig(id);
// 校验数据不存在了
assertNull(configMapper.selectById(id));
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testDeleteConfig_canNotDeleteSystemType() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用, 并断言异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
}
@Test
public void testCheckConfigExists_success() {
// mock 数据
InfConfigDO dbConfigDO = randomInfConfigDO();
configMapper.insert(dbConfigDO);// @Sql: 先插入出一条存在的数据
// 调用成功
configService.checkConfigExists(dbConfigDO.getId());
}
@Test
public void testCheckConfigExist_notExists() {
assertServiceException(() -> configService.checkConfigExists(randomLongId()), CONFIG_NOT_EXISTS);
}
@Test
public void testCheckConfigKeyUnique_success() {
// 调用,成功
configService.checkConfigKeyUnique(randomLongId(), randomString());
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForCreate() {
// 准备参数
String key = randomString();
// mock 数据
configMapper.insert(randomInfConfigDO(o -> o.setKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.checkConfigKeyUnique(null, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String key = randomString();
// mock 数据
configMapper.insert(randomInfConfigDO(o -> o.setKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.checkConfigKeyUnique(id, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testGetConfigPage() {
// mock 数据
@@ -128,105 +243,6 @@ public class InfConfigServiceTest extends BaseSpringBootUnitTest {
assertPojoEquals(dbConfig, config);
}
@Test
public void testCreateConfig_success() {
// 准备参数
InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
// 调用
Long configId = configService.createConfig(reqVO);
// 断言
assertNotNull(configId);
// 校验记录的属性是否正确
InfConfigDO config = configMapper.selectById(configId);
assertPojoEquals(reqVO, config);
assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testCreateConfig_keyDuplicate() {
// 准备参数
InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
// mock 数据
configMapper.insert(randomInfConfigDO(o -> { // @Sql
o.setKey(reqVO.getKey()); // 模拟 key 重复
}));
// 调用, 并断言异常
assertServiceException(() -> configService.createConfig(reqVO), CONFIG_KEY_DUPLICATE);
}
@Test
public void testUpdateConfig_success() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class, o -> {
o.setId(dbConfig.getId()); // 设置更新的 ID
});
// 调用
configService.updateConfig(reqVO);
// 校验是否更新正确
InfConfigDO config = configMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, config);
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testUpdateConfig_notExists() {
// 准备参数
InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> configService.updateConfig(reqVO), CONFIG_NOT_EXISTS);
}
@Test
public void testDeleteConfig_success() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用
configService.deleteConfig(id);
// 校验数据不存在了
assertNull(configMapper.selectById(id));
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testDeleteConfig_canNotDeleteSystemType() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用, 并断言异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
}
@Test
public void testDeleteConfig_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_NOT_EXISTS);
}
// ========== 随机对象 ==========
@SafeVarargs

View File

@@ -1,15 +1,19 @@
package cn.iocoder.dashboard.modules.system.service.auth;
import cn.iocoder.dashboard.BaseSpringBootUnitTest;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.dashboard.modules.system.service.auth.impl.SysAuthServiceImpl;
import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService;
import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService;
import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
import cn.iocoder.dashboard.util.AssertUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.annotation.Resource;
@@ -21,7 +25,13 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
public class SysAuthServiceImplTest extends BaseSpringBootUnitTest {
/**
* {@link SysAuthServiceImpl} 的单元测试
*
* @author 芋道源码
*/
@Import(SysAuthServiceImpl.class)
public class SysAuthServiceImplTest extends BaseDbUnitTest {
@Resource
private SysAuthServiceImpl authService;
@@ -30,6 +40,14 @@ public class SysAuthServiceImplTest extends BaseSpringBootUnitTest {
private SysUserService userService;
@MockBean
private SysPermissionService permissionService;
@MockBean
private AuthenticationManager authenticationManager;
@MockBean
private SysCaptchaService captchaService;
@MockBean
private SysLoginLogService loginLogService;
@MockBean
private SysUserSessionService userSessionService;
@Test
public void testLoadUserByUsername_success() {

View File

@@ -0,0 +1,78 @@
package cn.iocoder.dashboard.modules.system.service.auth;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.dashboard.BaseDbAndRedisUnitTest;
import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO;
import cn.iocoder.dashboard.modules.system.service.auth.impl.SysUserSessionServiceImpl;
import cn.iocoder.dashboard.modules.system.service.dept.impl.SysDeptServiceImpl;
import cn.iocoder.dashboard.modules.system.service.logger.impl.SysLoginLogServiceImpl;
import cn.iocoder.dashboard.modules.system.service.user.SysUserServiceImpl;
import cn.iocoder.dashboard.util.AssertUtils;
import cn.iocoder.dashboard.util.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* SysUserSessionServiceImpl Tester.
*
* @author Lyon
* @version 1.0
* @since <pre>3月 8, 2021</pre>
*/
@Import(
SysUserSessionServiceImpl.class)
public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
@Resource
SysUserSessionServiceImpl sysUserSessionService;
@Resource
SysUserSessionMapper sysUserSessionMapper;
@MockBean
SecurityProperties securityProperties;
@MockBean
SysDeptServiceImpl sysDeptService;
@MockBean
SysUserServiceImpl sysUserService;
@MockBean
SysLoginLogServiceImpl sysLoginLogService;
@MockBean
SysLoginUserRedisDAO sysLoginUserRedisDAO;
@Test
public void testClearSessionTimeout_success() throws Exception {
// 准备超时数据 120 条, 在线用户 1 条
int expectedTimeoutCount = 120, expectedTotal = 1;
// 准备数据
List<SysUserSessionDO> prepareData = Stream
.iterate(0, i -> i)
.limit(expectedTimeoutCount)
.map(i -> RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetSecond(new Date(), -1))))
.collect(Collectors.toList());
SysUserSessionDO sessionDO = RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetMinute(new Date(), 30)));
prepareData.add(sessionDO);
prepareData.forEach(sysUserSessionMapper::insert);
//清空超时数据
long actualTimeoutCount = sysUserSessionService.clearSessionTimeout();
//校验
assertEquals(expectedTimeoutCount, actualTimeoutCount);
List<SysUserSessionDO> userSessionDOS = sysUserSessionMapper.selectList();
assertEquals(expectedTotal, userSessionDOS.size());
AssertUtils.assertPojoEquals(sessionDO, userSessionDOS.get(0), "updateTime");
}
}

View File

@@ -0,0 +1,274 @@
package cn.iocoder.dashboard.modules.system.service.dept;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptListReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.dept.SysDeptUpdateReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dept.SysDeptMapper;
import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum;
import cn.iocoder.dashboard.modules.system.mq.producer.dept.SysDeptProducer;
import cn.iocoder.dashboard.modules.system.service.dept.impl.SysDeptServiceImpl;
import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import com.google.common.collect.Multimap;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static cn.hutool.core.bean.BeanUtil.getFieldValue;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* {@link SysDeptServiceImpl} 的单元测试类
*
* @author niudehua
*/
@Import(SysDeptServiceImpl.class)
class SysDeptServiceTest extends BaseDbUnitTest {
@Resource
private SysDeptServiceImpl deptService;
@Resource
private SysDeptMapper deptMapper;
@MockBean
private SysDeptProducer deptProducer;
@Test
@SuppressWarnings("unchecked")
void testInitLocalCache() {
// mock 数据
SysDeptDO deptDO1 = randomDeptDO();
deptMapper.insert(deptDO1);
SysDeptDO deptDO2 = randomDeptDO();
deptMapper.insert(deptDO2);
// 调用
deptService.initLocalCache();
// 断言 deptCache 缓存
Map<Long, SysDeptDO> deptCache = (Map<Long, SysDeptDO>) getFieldValue(deptService, "deptCache");
assertEquals(2, deptCache.size());
assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
// 断言 parentDeptCache 缓存
Multimap<Long, SysDeptDO> parentDeptCache = (Multimap<Long, SysDeptDO>) getFieldValue(deptService, "parentDeptCache");
assertEquals(2, parentDeptCache.size());
assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
// 断言 maxUpdateTime 缓存
Date maxUpdateTime = (Date) getFieldValue(deptService, "maxUpdateTime");
assertEquals(ObjectUtils.max(deptDO1.getUpdateTime(), deptDO2.getUpdateTime()), maxUpdateTime);
}
@Test
void testListDepts() {
// mock 数据
SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { // 等会查询到
o.setName("开发部");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
deptMapper.insert(dept);
// 测试 name 不匹配
deptMapper.insert(ObjectUtils.clone(dept, o -> o.setName("")));
// 测试 status 不匹配
deptMapper.insert(ObjectUtils.clone(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 准备参数
SysDeptListReqVO reqVO = new SysDeptListReqVO();
reqVO.setName("");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用
List<SysDeptDO> sysDeptDOS = deptService.listDepts(reqVO);
// 断言
assertEquals(1, sysDeptDOS.size());
assertPojoEquals(dept, sysDeptDOS.get(0));
}
@Test
void testCreateDept_success() {
// 准备参数
SysDeptCreateReqVO reqVO = randomPojo(SysDeptCreateReqVO.class,
o -> {
o.setParentId(DeptIdEnum.ROOT.getId());
o.setStatus(randomCommonStatus());
});
// 调用
Long deptId = deptService.createDept(reqVO);
// 断言
assertNotNull(deptId);
// 校验记录的属性是否正确
SysDeptDO deptDO = deptMapper.selectById(deptId);
assertPojoEquals(reqVO, deptDO);
// 校验调用
verify(deptProducer, times(1)).sendDeptRefreshMessage();
}
@Test
void testUpdateDept_success() {
// mock 数据
SysDeptDO dbDeptDO = randomPojo(SysDeptDO.class, o -> o.setStatus(randomCommonStatus()));
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDeptUpdateReqVO reqVO = randomPojo(SysDeptUpdateReqVO.class, o -> {
// 设置更新的 ID
o.setParentId(DeptIdEnum.ROOT.getId());
o.setId(dbDeptDO.getId());
o.setStatus(randomCommonStatus());
});
// 调用
deptService.updateDept(reqVO);
// 校验是否更新正确
SysDeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, deptDO);
}
@Test
void testDeleteDept_success() {
// mock 数据
SysDeptDO dbDeptDO = randomPojo(SysDeptDO.class, o -> o.setStatus(randomCommonStatus()));
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbDeptDO.getId();
// 调用
deptService.deleteDept(id);
// 校验数据不存在了
assertNull(deptMapper.selectById(id));
}
@Test
void testCheckDept_nameDuplicateForUpdate() {
// mock 数据
SysDeptDO deptDO = randomDeptDO();
// 设置根节点部门
deptDO.setParentId(DeptIdEnum.ROOT.getId());
deptMapper.insert(deptDO);
// mock 数据 稍后模拟重复它的 name
SysDeptDO nameDeptDO = randomDeptDO();
// 设置根节点部门
nameDeptDO.setParentId(DeptIdEnum.ROOT.getId());
deptMapper.insert(nameDeptDO);
// 准备参数
SysDeptUpdateReqVO reqVO = randomPojo(SysDeptUpdateReqVO.class,
o -> {
// 设置根节点部门
o.setParentId(DeptIdEnum.ROOT.getId());
// 设置更新的 ID
o.setId(deptDO.getId());
// 模拟 name 重复
o.setName(nameDeptDO.getName());
});
// 调用, 并断言异常
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_NAME_DUPLICATE);
}
@Test
void testCheckDept_parentNotExitsForCreate() {
SysDeptCreateReqVO reqVO = randomPojo(SysDeptCreateReqVO.class,
o -> o.setStatus(randomCommonStatus()));
// 调用,并断言异常
assertServiceException(() -> deptService.createDept(reqVO), DEPT_PARENT_NOT_EXITS);
}
@Test
void testCheckDept_notFoundForDelete() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> deptService.deleteDept(id), DEPT_NOT_FOUND);
}
@Test
void testCheckDept_exitsChildrenForDelete() {
// mock 数据
SysDeptDO parentDept = randomPojo(SysDeptDO.class, o -> o.setStatus(randomCommonStatus()));
deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDeptDO childrenDeptDO = randomPojo(SysDeptDO.class, o -> {
o.setParentId(parentDept.getId());
o.setStatus(randomCommonStatus());
});
// 插入子部门
deptMapper.insert(childrenDeptDO);
// 调用, 并断言异常
assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
}
@Test
void testCheckDept_parentErrorForUpdate() {
// mock 数据
SysDeptDO dbDeptDO = randomPojo(SysDeptDO.class, o -> o.setStatus(randomCommonStatus()));
deptMapper.insert(dbDeptDO);
// 准备参数
SysDeptUpdateReqVO reqVO = randomPojo(SysDeptUpdateReqVO.class,
o -> {
// 设置自己为父部门
o.setParentId(dbDeptDO.getId());
// 设置更新的 ID
o.setId(dbDeptDO.getId());
});
// 调用, 并断言异常
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_ERROR);
}
@Test
void testCheckDept_notEnableForCreate() {
// mock 数据
SysDeptDO deptDO = randomPojo(SysDeptDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
deptMapper.insert(deptDO);
// 准备参数
SysDeptCreateReqVO reqVO = randomPojo(SysDeptCreateReqVO.class,
o -> {
// 设置未启用的部门为副部门
o.setParentId(deptDO.getId());
});
// 调用, 并断言异常
assertServiceException(() -> deptService.createDept(reqVO), DEPT_NOT_ENABLE);
}
@Test
void testCheckDept_parentIsChildForUpdate() {
// mock 数据
SysDeptDO parentDept = randomPojo(SysDeptDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
deptMapper.insert(parentDept);
SysDeptDO childDept = randomPojo(SysDeptDO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setParentId(parentDept.getId());
});
deptMapper.insert(childDept);
// 初始化本地缓存
deptService.initLocalCache();
// 准备参数
SysDeptUpdateReqVO reqVO = randomPojo(SysDeptUpdateReqVO.class,
o -> {
// 设置自己的子部门为父部门
o.setParentId(childDept.getId());
// 设置更新的 ID
o.setId(parentDept.getId());
});
// 调用, 并断言异常
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_IS_CHILD);
}
@SafeVarargs
private static SysDeptDO randomDeptDO(Consumer<SysDeptDO>... consumers) {
Consumer<SysDeptDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
};
return randomPojo(SysDeptDO.class, ArrayUtils.append(consumer, consumers));
}
}

View File

@@ -0,0 +1,200 @@
package cn.iocoder.dashboard.modules.system.service.dept;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.post.SysPostCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.post.SysPostExportReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.post.SysPostPageReqVO;
import cn.iocoder.dashboard.modules.system.controller.dept.vo.post.SysPostUpdateReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.dept.SysPostMapper;
import cn.iocoder.dashboard.modules.system.service.dept.impl.SysPostServiceImpl;
import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link SysPostServiceImpl} 的单元测试类
*
* @author niudehua
*/
@Import(SysPostServiceImpl.class)
class SysPostServiceTest extends BaseDbUnitTest {
@Resource
private SysPostServiceImpl postService;
@Resource
private SysPostMapper postMapper;
@Test
void testPagePosts() {
// mock 数据
SysPostDO postDO = randomPojo(SysPostDO.class, o -> {
o.setName("码仔");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
postMapper.insert(postDO);
// 测试 name 不匹配
postMapper.insert(ObjectUtils.clone(postDO, o -> o.setName("程序员")));
// 测试 status 不匹配
postMapper.insert(ObjectUtils.clone(postDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 准备参数
SysPostPageReqVO reqVO = new SysPostPageReqVO();
reqVO.setName("");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用
PageResult<SysPostDO> pageResult = postService.pagePosts(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(postDO, pageResult.getList().get(0));
}
@Test
void testListPosts() {
// mock 数据
SysPostDO postDO = randomPojo(SysPostDO.class, o -> {
o.setName("码仔");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
postMapper.insert(postDO);
// 测试 name 不匹配
postMapper.insert(ObjectUtils.clone(postDO, o -> o.setName("程序员")));
// 测试 status 不匹配
postMapper.insert(ObjectUtils.clone(postDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 准备参数
SysPostExportReqVO reqVO = new SysPostExportReqVO();
reqVO.setName("");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用
List<SysPostDO> list = postService.listPosts(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(postDO, list.get(0));
}
@Test
void testGetPost() {
// mock 数据
SysPostDO dbPostDO = randomPostDO();
postMapper.insert(dbPostDO);
// 准备参数
Long id = dbPostDO.getId();
// 调用
SysPostDO post = postService.getPost(id);
// 断言
assertNotNull(post);
assertPojoEquals(dbPostDO, post);
}
@Test
void testCreatePost_success() {
// 准备参数
SysPostCreateReqVO reqVO = randomPojo(SysPostCreateReqVO.class,
o -> o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()));
// 调用
Long postId = postService.createPost(reqVO);
// 断言
assertNotNull(postId);
// 校验记录的属性是否正确
SysPostDO post = postMapper.selectById(postId);
assertPojoEquals(reqVO, post);
}
@Test
void testUpdatePost_success() {
// mock 数据
SysPostDO postDO = randomPostDO();
postMapper.insert(postDO);// @Sql: 先插入出一条存在的数据
// 准备参数
SysPostUpdateReqVO reqVO = randomPojo(SysPostUpdateReqVO.class,
o -> {
// 设置更新的 ID
o.setId(postDO.getId());
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus());
});
// 调用
postService.updatePost(reqVO);
// 校验是否更新正确
SysPostDO post = postMapper.selectById(reqVO.getId());// 获取最新的
assertPojoEquals(reqVO, post);
}
@Test
void testDeletePost_success() {
// mock 数据
SysPostDO postDO = randomPostDO();
postMapper.insert(postDO);
// 准备参数
Long id = postDO.getId();
// 调用
postService.deletePost(id);
assertNull(postMapper.selectById(id));
}
@Test
void testCheckPost_notFoundForDelete() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> postService.deletePost(id), POST_NOT_FOUND);
}
@Test
void testCheckPost_nameDuplicateForCreate() {
// mock 数据
SysPostDO postDO = randomPostDO();
postMapper.insert(postDO);// @Sql: 先插入出一条存在的数据
// 准备参数
SysPostCreateReqVO reqVO = randomPojo(SysPostCreateReqVO.class,
// 模拟 name 重复
o -> o.setName(postDO.getName()));
assertServiceException(() -> postService.createPost(reqVO), POST_NAME_DUPLICATE);
}
@Test
void testCheckPost_codeDuplicateForUpdate() {
// mock 数据
SysPostDO postDO = randomPostDO();
postMapper.insert(postDO);
// mock 数据 稍后模拟重复它的 code
SysPostDO codePostDO = randomPostDO();
postMapper.insert(codePostDO);
// 准备参数
SysPostUpdateReqVO reqVO = randomPojo(SysPostUpdateReqVO.class,
o -> {
// 设置更新的 ID
o.setId(postDO.getId());
// 模拟 code 重复
o.setCode(codePostDO.getCode());
});
// 调用, 并断言异常
assertServiceException(() -> postService.updatePost(reqVO), POST_CODE_DUPLICATE);
}
@SafeVarargs
private static SysPostDO randomPostDO(Consumer<SysPostDO>... consumers) {
Consumer<SysPostDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
};
return randomPojo(SysPostDO.class, ArrayUtils.append(consumer, consumers));
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.system.service.dict;
import cn.iocoder.dashboard.BaseSpringBootUnitTest;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO;
@@ -12,14 +12,20 @@ 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.impl.SysDictDataServiceImpl;
import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import com.google.common.collect.ImmutableTable;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.DICT_DATA_NOT_EXISTS;
import static cn.hutool.core.bean.BeanUtil.getFieldValue;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.*;
@@ -32,7 +38,8 @@ import static org.mockito.Mockito.*;
*
* @author 芋道源码
*/
public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
@Import(SysDictDataServiceImpl.class)
public class SysDictDataServiceTest extends BaseDbUnitTest {
@Resource
private SysDictDataServiceImpl dictDataService;
@@ -44,6 +51,37 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
@MockBean
private SysDictDataProducer dictDataProducer;
/**
* 测试加载到新的字典数据的情况
*/
@Test
@SuppressWarnings("unchecked")
public void testInitLocalCache() {
// mock 数据
SysDictDataDO dictData01 = randomDictDataDO();
dictDataMapper.insert(dictData01);
SysDictDataDO dictData02 = randomDictDataDO();
dictDataMapper.insert(dictData02);
// 调用
dictDataService.initLocalCache();
// 断言 labelDictDataCache 缓存
ImmutableTable<String, String, SysDictDataDO> labelDictDataCache =
(ImmutableTable<String, String, SysDictDataDO>) getFieldValue(dictDataService, "labelDictDataCache");
assertEquals(2, labelDictDataCache.size());
assertPojoEquals(dictData01, labelDictDataCache.get(dictData01.getDictType(), dictData01.getLabel()));
assertPojoEquals(dictData02, labelDictDataCache.get(dictData02.getDictType(), dictData02.getLabel()));
// 断言 valueDictDataCache 缓存
ImmutableTable<String, String, SysDictDataDO> valueDictDataCache =
(ImmutableTable<String, String, SysDictDataDO>) getFieldValue(dictDataService, "valueDictDataCache");
assertEquals(2, valueDictDataCache.size());
assertPojoEquals(dictData01, valueDictDataCache.get(dictData01.getDictType(), dictData01.getValue()));
assertPojoEquals(dictData02, valueDictDataCache.get(dictData02.getDictType(), dictData02.getValue()));
// 断言 maxUpdateTime 缓存
Date maxUpdateTime = (Date) getFieldValue(dictDataService, "maxUpdateTime");
assertEquals(ObjectUtils.max(dictData01.getUpdateTime(), dictData02.getUpdateTime()), maxUpdateTime);
}
@Test
public void testGetDictDataPage() {
// mock 数据
@@ -107,8 +145,7 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
SysDictDataCreateReqVO reqVO = randomPojo(SysDictDataCreateReqVO.class,
o -> o.setStatus(randomCommonStatus()));
// mock 方法
when(dictTypeService.getDictType(eq(reqVO.getDictType())))
.thenReturn(randomPojo(SysDictTypeDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
// 调用
Long dictDataId = dictDataService.createDictData(reqVO);
@@ -124,50 +161,142 @@ public class SysDictDataServiceTest extends BaseSpringBootUnitTest {
@Test
public void testUpdateDictData_success() {
// mock 数据
SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class);
SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class, o -> {
o.setId(dbDictData.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
});
// mock 方法,字典类型
when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
// 调用
dictDataService.updateDictData(reqVO);
// 校验是否更新正确
SysDictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, dictData);
}
@Test
public void testUpdateDictData_notExists() {
// 准备参数
SysDictDataUpdateReqVO reqVO = randomPojo(SysDictDataUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> dictDataService.updateDictData(reqVO), DICT_DATA_NOT_EXISTS);
// 校验调用
verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
}
@Test
public void testDeleteDictData_success() {
// mock 数据
SysDictDataDO dbDictData = randomPojo(SysDictDataDO.class);
SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbDictData.getId();
// 调用
dictDataService.deleteDictData(id);
// 校验数据不存在了
assertNull(dictDataMapper.selectById(id));
// 校验数据不存在了
assertNull(dictDataMapper.selectById(id));
// 校验调用
verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
}
@Test
public void testDeleteDictData_notExists() {
// 准备参数
Long id = randomLongId();
public void testCheckDictDataExists_success() {
// mock 数据
SysDictDataDO dbDictData = randomDictDataDO();
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
// 调用成功
dictDataService.checkDictDataExists(dbDictData.getId());
}
@Test
public void testCheckDictDataExists_notExists() {
assertServiceException(() -> dictDataService.checkDictDataExists(randomLongId()), DICT_DATA_NOT_EXISTS);
}
@Test
public void testCheckDictTypeValid_success() {
// mock 方法,数据类型被禁用
String type = randomString();
when(dictTypeService.getDictType(eq(type))).thenReturn(randomDictTypeDO(type));
// 调用, 成功
dictDataService.checkDictTypeValid(type);
}
@Test
public void testCheckDictTypeValid_notExists() {
assertServiceException(() -> dictDataService.checkDictTypeValid(randomString()), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testCheckDictTypeValid_notEnable() {
// mock 方法,数据类型被禁用
String dictType = randomString();
when(dictTypeService.getDictType(eq(dictType))).thenReturn(
randomPojo(SysDictTypeDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 调用, 并断言异常
assertServiceException(() -> dictDataService.deleteDictData(id), DICT_DATA_NOT_EXISTS);
assertServiceException(() -> dictDataService.checkDictTypeValid(dictType), DICT_TYPE_NOT_ENABLE);
}
@Test
public void testCheckDictDataValueUnique_success() {
// 调用,成功
dictDataService.checkDictDataValueUnique(randomLongId(), randomString(), randomString());
}
@Test
public void testCheckDictDataValueUnique_valueDuplicateForCreate() {
// 准备参数
String dictType = randomString();
String value = randomString();
// mock 数据
dictDataMapper.insert(randomDictDataDO(o -> {
o.setDictType(dictType);
o.setValue(value);
}));
// 调用,校验异常
assertServiceException(() -> dictDataService.checkDictDataValueUnique(null, dictType, value),
DICT_DATA_VALUE_DUPLICATE);
}
@Test
public void testCheckDictDataValueUnique_valueDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String dictType = randomString();
String value = randomString();
// mock 数据
dictDataMapper.insert(randomDictDataDO(o -> {
o.setDictType(dictType);
o.setValue(value);
}));
// 调用,校验异常
assertServiceException(() -> dictDataService.checkDictDataValueUnique(id, dictType, value),
DICT_DATA_VALUE_DUPLICATE);
}
// ========== 随机对象 ==========
@SafeVarargs
private static SysDictDataDO randomDictDataDO(Consumer<SysDictDataDO>... consumers) {
Consumer<SysDictDataDO> consumer = (o) -> {
o.setStatus(randomCommonStatus()); // 保证 status 的范围
};
return randomPojo(SysDictDataDO.class, ArrayUtils.append(consumer, consumers));
}
/**
* 生成一个有效的字典类型
*
* @param type 字典类型
* @return SysDictTypeDO 对象
*/
private static SysDictTypeDO randomDictTypeDO(String type) {
return randomPojo(SysDictTypeDO.class, o -> {
o.setType(type);
o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 是开启
});
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.dashboard.modules.system.service.dict;
import cn.iocoder.dashboard.BaseSpringBootUnitTest;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.type.SysDictTypeCreateReqVO;
@@ -14,6 +14,7 @@ import cn.iocoder.dashboard.util.collection.ArrayUtils;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
@@ -23,8 +24,7 @@ import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.RandomUtils.*;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
@@ -35,7 +35,8 @@ import static org.mockito.Mockito.when;
*
* @author 芋道源码
*/
public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
@Import(SysDictTypeServiceImpl.class)
public class SysDictTypeServiceTest extends BaseDbUnitTest {
@Resource
private SysDictTypeServiceImpl dictTypeService;
@@ -142,32 +143,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertPojoEquals(reqVO, dictType);
}
@Test
public void testCreateDictType_nameDuplicate() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDictTypeCreateReqVO reqVO = randomPojo(SysDictTypeCreateReqVO.class,
o -> o.setName(dbDictType.getName())); // 模拟 name 重复
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.createDictType(reqVO), DICT_TYPE_NAME_DUPLICATE);
}
@Test
public void testCreateDictType_typeDuplicate() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 准备参数
SysDictTypeCreateReqVO reqVO = randomPojo(SysDictTypeCreateReqVO.class,
o -> o.setType(dbDictType.getType())); // 模拟 type 重复
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.createDictType(reqVO), DICT_TYPE_TYPE_DUPLICATE);
}
@Test
public void testUpdateDictType_success() {
// mock 数据
@@ -186,33 +161,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertPojoEquals(reqVO, dictType);
}
@Test
public void testUpdateDictType_notExists() {
// 准备参数
SysDictTypeUpdateReqVO reqVO = randomPojo(SysDictTypeUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.updateDictType(reqVO), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testUpdateDictType_nameDuplicate() {
// mock 数据,稍后更新它
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);
// mock 数据ks稍后模拟重复它的名字
SysDictTypeDO nameDictType = randomDictTypeDO();
dictTypeMapper.insert(nameDictType);
// 准备参数
SysDictTypeUpdateReqVO reqVO = randomPojo(SysDictTypeUpdateReqVO.class, o -> {
o.setId(dbDictType.getId()); // 设置更新的 ID
o.setName(nameDictType.getName()); // 模拟 name 重复
});
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.updateDictType(reqVO), DICT_TYPE_NAME_DUPLICATE);
}
@Test
public void testDeleteDictType_success() {
// mock 数据
@@ -227,15 +175,6 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertNull(dictTypeMapper.selectById(id));
}
@Test
public void testDeleteDictType_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testDeleteDictType_hasChildren() {
// mock 数据
@@ -250,6 +189,83 @@ public class SysDictTypeServiceTest extends BaseSpringBootUnitTest {
assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN);
}
@Test
public void testCheckDictDataExists_success() {
// mock 数据
SysDictTypeDO dbDictType = randomDictTypeDO();
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
// 调用成功
dictTypeService.checkDictTypeExists(dbDictType.getId());
}
@Test
public void testCheckDictDataExists_notExists() {
assertServiceException(() -> dictTypeService.checkDictTypeExists(randomLongId()), DICT_TYPE_NOT_EXISTS);
}
@Test
public void testCheckDictTypeUnique_success() {
// 调用,成功
dictTypeService.checkDictTypeUnique(randomLongId(), randomString());
}
@Test
public void testCheckDictTypeUnique_valueDuplicateForCreate() {
// 准备参数
String type = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeUnique(null, type),
DICT_TYPE_TYPE_DUPLICATE);
}
@Test
public void testCheckDictTypeUnique_valueDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String type = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeUnique(id, type),
DICT_TYPE_TYPE_DUPLICATE);
}
@Test
public void testCheckDictTypNameUnique_success() {
// 调用,成功
dictTypeService.checkDictTypeNameUnique(randomLongId(), randomString());
}
@Test
public void testCheckDictTypeNameUnique_nameDuplicateForCreate() {
// 准备参数
String name = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeNameUnique(null, name),
DICT_TYPE_NAME_DUPLICATE);
}
@Test
public void testCheckDictTypeNameUnique_nameDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String name = randomString();
// mock 数据
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
// 调用,校验异常
assertServiceException(() -> dictTypeService.checkDictTypeNameUnique(id, name),
DICT_TYPE_NAME_DUPLICATE);
}
// ========== 随机对象 ==========
@SafeVarargs

View File

@@ -0,0 +1,164 @@
package cn.iocoder.dashboard.modules.system.service.notice;
import cn.iocoder.dashboard.BaseDbUnitTest;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.system.controller.notice.vo.SysNoticeCreateReqVO;
import cn.iocoder.dashboard.modules.system.controller.notice.vo.SysNoticePageReqVO;
import cn.iocoder.dashboard.modules.system.controller.notice.vo.SysNoticeUpdateReqVO;
import cn.iocoder.dashboard.modules.system.dal.dataobject.notice.SysNoticeDO;
import cn.iocoder.dashboard.modules.system.dal.mysql.notice.SysNoticeMapper;
import cn.iocoder.dashboard.modules.system.enums.notice.SysNoticeTypeEnum;
import cn.iocoder.dashboard.modules.system.service.notice.impl.SysNoticeServiceImpl;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.NOTICE_NOT_FOUND;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.*;
@Import(SysNoticeServiceImpl.class)
class SysNoticeServiceImplTest extends BaseDbUnitTest {
@Resource
private SysNoticeServiceImpl sysNoticeService;
@Resource
private SysNoticeMapper sysNoticeMapper;
@Test
public void testPageNotices_success() {
// 插入前置数据
SysNoticeDO dbNotice = randomPojo(SysNoticeDO.class, o -> {
o.setTitle("尼古拉斯赵四来啦!");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setType(randomEle(SysNoticeTypeEnum.values()).getType());
});
sysNoticeMapper.insert(dbNotice);
// 测试 title 不匹配
sysNoticeMapper.insert(ObjectUtils.clone(dbNotice, o -> o.setTitle("尼古拉斯凯奇也来啦!")));
// 测试 status 不匹配
sysNoticeMapper.insert(ObjectUtils.clone(dbNotice, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 查询
SysNoticePageReqVO reqVO = new SysNoticePageReqVO();
reqVO.setTitle("尼古拉斯赵四来啦!");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
PageResult<SysNoticeDO> pageResult = sysNoticeService.pageNotices(reqVO);
// 验证查询结果经过筛选
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbNotice, pageResult.getList().get(0));
}
@Test
public void testGetNotice_success() {
// 插入前置数据
SysNoticeDO dbNotice = randomSysNoticeDO();
sysNoticeMapper.insert(dbNotice);
// 查询
SysNoticeDO notice = sysNoticeService.getNotice(dbNotice.getId());
// 验证插入与读取对象是否一致
assertNotNull(notice);
assertPojoEquals(dbNotice, notice);
}
@Test
public void testCreateNotice_success() {
// 准备参数
SysNoticeCreateReqVO reqVO = randomSysNoticeCreateReqVO();
// 校验插入是否成功
Long noticeId = sysNoticeService.createNotice(reqVO);
assertNotNull(noticeId);
// 校验插入属性是否正确
SysNoticeDO notice = sysNoticeMapper.selectById(noticeId);
assertPojoEquals(reqVO, notice);
}
@Test
public void testUpdateNotice_success() {
// 插入前置数据
SysNoticeDO dbNoticeDO = randomSysNoticeDO();
sysNoticeMapper.insert(dbNoticeDO);
// 准备更新参数
SysNoticeUpdateReqVO reqVO = randomSysNoticeUpdateReqVO(o -> o.setId(dbNoticeDO.getId()));
// 更新
sysNoticeService.updateNotice(reqVO);
// 检验是否更新成功
SysNoticeDO notice = sysNoticeMapper.selectById(reqVO.getId());
assertPojoEquals(reqVO, notice);
}
@Test
public void testDeleteNotice_success() {
// 插入前置数据
SysNoticeDO dbNotice = randomSysNoticeDO();
sysNoticeMapper.insert(dbNotice);
// 删除
sysNoticeService.deleteNotice(dbNotice.getId());
// 检查是否删除成功
assertNull(sysNoticeMapper.selectById(dbNotice.getId()));
}
@Test
public void checkNoticeExists_success() {
// 插入前置数据
SysNoticeDO dbNotice = randomSysNoticeDO();
sysNoticeMapper.insert(dbNotice);
// 成功调用
sysNoticeService.checkNoticeExists(dbNotice.getId());
}
@Test
public void checkNoticeExists_noExists() {
assertServiceException(() -> sysNoticeService.checkNoticeExists(randomLongId()), NOTICE_NOT_FOUND);
}
@SafeVarargs
private static SysNoticeDO randomSysNoticeDO(Consumer<SysNoticeDO>... consumers) {
SysNoticeDO notice = randomPojo(SysNoticeDO.class, consumers);
notice.setType(randomEle(SysNoticeTypeEnum.values()).getType());
notice.setStatus(CommonStatusEnum.ENABLE.getStatus());
return notice;
}
@SafeVarargs
private static SysNoticeUpdateReqVO randomSysNoticeUpdateReqVO(Consumer<SysNoticeUpdateReqVO>... consumers) {
SysNoticeUpdateReqVO reqVO = randomPojo(SysNoticeUpdateReqVO.class, consumers);
reqVO.setType(randomEle(SysNoticeTypeEnum.values()).getType());
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
return reqVO;
}
private static SysNoticeCreateReqVO randomSysNoticeCreateReqVO() {
SysNoticeCreateReqVO reqVO = randomPojo(SysNoticeCreateReqVO.class);
reqVO.setType(randomEle(SysNoticeTypeEnum.values()).getType());
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
return reqVO;
}
}

View File

@@ -3,21 +3,6 @@ spring:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
# 去除的自动配置项
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 单元测试,禁用 Quartz
- com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration # 单元测试,禁用 Lock4j 分布式锁
- org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration # 单元测试,禁用 Scheduler 定时任务
- org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration # 项目没有使用 Spring Data所以禁用配置类加速启动
# Swagger 接口文档的自动配置(单元测试,禁用 Swagger)
springfox:
documentation:
auto-startup: false
--- #################### 数据库相关配置 ####################
spring:
@@ -29,6 +14,9 @@ spring:
username: sa
password:
schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
@@ -36,17 +24,13 @@ spring:
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
--- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类(单元测试,禁用 Quartz
--- #################### 配置中心相关配置 ####################
# Apollo 配置中心
apollo:
bootstrap:
enabled: false # 单元测试,禁用配置中心
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
@@ -63,23 +47,6 @@ resilience4j:
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
enabled-by-default: false
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
enabled: false
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# 日志文件配置(不需要配置)
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置

View File

@@ -8,6 +8,8 @@ DELETE FROM "sys_role";
DELETE FROM "sys_role_menu";
DELETE FROM "sys_menu";
DELETE FROM "sys_dict_type";
DELETE FROM "sys_user_session";
DELETE FROM "sys_post";
DELETE FROM "sys_login_log";
DELETE FROM "sys_operate_log";
DELETE FROM "sys_user";

View File

@@ -9,9 +9,9 @@ CREATE TABLE IF NOT EXISTS "inf_config" (
"value" varchar(500) NOT NULL DEFAULT '',
"sensitive" bit NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -28,9 +28,9 @@ CREATE TABLE IF NOT EXISTS "sys_dept" (
"phone" varchar(11) DEFAULT NULL,
"email" varchar(50) DEFAULT NULL,
"status" tinyint NOT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -44,9 +44,9 @@ CREATE TABLE IF NOT EXISTS "sys_dict_data" (
"dict_type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -62,9 +62,9 @@ CREATE TABLE IF NOT EXISTS "sys_role" (
"status" tinyint NOT NULL,
"type" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -74,9 +74,9 @@ CREATE TABLE IF NOT EXISTS "sys_role_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"role_id" bigint NOT NULL,
"menu_id" bigint NOT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -93,9 +93,9 @@ CREATE TABLE IF NOT EXISTS "sys_menu" (
"icon" varchar(100) DEFAULT '#',
"component" varchar(255) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
@@ -107,14 +107,61 @@ CREATE TABLE "sys_dict_type" (
"type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '字典类型表';
CREATE TABLE `sys_user_session` (
`id` varchar(32) NOT NULL,
`user_id` bigint DEFAULT NULL,
`username` varchar(50) NOT NULL DEFAULT '',
`user_ip` varchar(50) DEFAULT NULL,
`user_agent` varchar(512) DEFAULT NULL,
`session_timeout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '' ,
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY (`id`)
) COMMENT '用户在线 Session';
CREATE TABLE IF NOT EXISTS "sys_post"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"code" varchar(64) NOT NULL,
"name" varchar(50) NOT NULL,
"sort" integer NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '岗位信息表';
CREATE TABLE IF NOT EXISTS "sys_notice" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"title" varchar(50) NOT NULL COMMENT '公告标题',
"content" text NOT NULL COMMENT '公告内容',
"notice_type" tinyint NOT NULL COMMENT '公告类型1通知 2公告',
"status" tinyint NOT NULL DEFAULT '0' COMMENT '公告状态0正常 1关闭',
"creator" varchar(64) DEFAULT '' COMMENT '创建者',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"updater" varchar(64) DEFAULT '' COMMENT '更新者',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
"deleted" bit NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY("id")
) COMMENT '通知公告表';
CREATE TABLE IF NOT EXISTS `sys_login_log` (
`id` bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`log_type` bigint(4) NOT NULL,