Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mail-1.6.1

This commit is contained in:
YunaiV
2023-01-25 12:40:37 +08:00
932 changed files with 37265 additions and 9012 deletions

View File

@ -139,7 +139,7 @@ public interface ErrorCodeConstants {
// ========== OAuth2 授权 1002022000 =========
ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1002022000, "code 不存在");
ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022000, "code 已过期");
ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022001, "code 已过期");
// TODO @芋艿:需要重新搞下 mail 错误码

View File

@ -54,6 +54,10 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.module.system.controller.admin.captcha;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
@ -10,38 +13,49 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.servlet.http.HttpServletRequest;
/**
* 验证码
*
* 问题:为什么不直接使用 anji 提供的 CaptchaController而要另外继承
* 回答:管理使用 /admin-api/* 作为前缀,所以需要继承!
*
* @author 芋道源码
*/
@Api(tags = "管理后台 - 验证码")
@RestController("adminCaptchaController")
@RequestMapping("/system/captcha")
public class CaptchaController extends com.anji.captcha.controller.CaptchaController {
public class CaptchaController {
@Resource
private CaptchaService captchaService;
@PostMapping({"/get"})
@ApiOperation("获得验证码")
@PermitAll
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
@Override
public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
return super.get(data, request);
assert request.getRemoteHost() != null;
data.setBrowserInfo(getRemoteId(request));
return captchaService.get(data);
}
@PostMapping("/check")
@ApiOperation("校验验证码")
@PermitAll
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
@Override
public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
return super.check(data, request);
data.setBrowserInfo(getRemoteId(request));
return captchaService.check(data);
}
public static String getRemoteId(HttpServletRequest request) {
String ip = ServletUtil.getClientIP(request);
String ua = request.getHeader("user-agent");
if (StrUtil.isNotBlank(ip)) {
return ip + ua;
}
return request.getRemoteAddr() + ua;
}
}

View File

@ -0,0 +1,5 @@
### 获得地区树
GET {{baseUrl}}/system/area/tree
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.system.controller.admin.ip;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.framework.ip.core.utils.IPUtils;
import cn.iocoder.yudao.module.system.controller.admin.ip.vo.AreaNodeRespVO;
import cn.iocoder.yudao.module.system.convert.ip.AreaConvert;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 地区")
@RestController
@RequestMapping("/system/area")
@Validated
public class AreaController {
@GetMapping("/tree")
@ApiOperation("获得地区树")
public CommonResult<List<AreaNodeRespVO>> getAreaTree() {
Area area = AreaUtils.getArea(Area.ID_CHINA);
Assert.notNull(area, "获取不到中国");
return success(AreaConvert.INSTANCE.convertList(area.getChildren()));
}
@GetMapping("/get-by-ip")
@ApiOperation("获得 IP 对应的地区名")
@ApiImplicitParam(name = "ip", value = "IP", required = true, dataTypeClass = String.class)
public CommonResult<String> getAreaByIp(@RequestParam("ip") String ip) {
// 获得城市
Area area = IPUtils.getArea(ip);
if (area == null) {
return success("未知");
}
// 格式化返回
return success(AreaUtils.format(area.getId()));
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.system.controller.admin.ip.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("管理后台 - 地区节点 Response VO")
@Data
public class AreaNodeRespVO {
@ApiModelProperty(value = "编号", required = true, example = "110000")
private Integer id;
@ApiModelProperty(value = "名字", required = true, example = "北京")
private String name;
/**
* 子节点
*/
private List<AreaNodeRespVO> children;
}

View File

@ -112,7 +112,7 @@ public class UserController {
@GetMapping("/list-all-simple")
@ApiOperation(value = "获取用户精简信息列表", notes = "只包含被开启的用户,主要用于前端的下拉选项")
public CommonResult<List<UserSimpleRespVO>> getSimpleUsers() {
// 获用户列表,只要开启状态的
// 获用户列表,只要开启状态的
List<AdminUserDO> list = userService.getUsersByStatus(CommonStatusEnum.ENABLE.getStatus());
// 排序后,返回给前端
return success(UserConvert.INSTANCE.convertList04(list));

View File

@ -6,7 +6,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("用户精简信息 Response VO")
@ApiModel("管理后台 - 用户精简信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.system.convert.ip;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.module.system.controller.admin.ip.vo.AreaNodeRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface AreaConvert {
AreaConvert INSTANCE = Mappers.getMapper(AreaConvert.class);
List<AreaNodeRespVO> convertList(List<Area> list);
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.system.dal.dataobject.dept;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_dept_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DeptDO extends BaseDO {
public class DeptDO extends TenantBaseDO {
/**
* 部门ID

View File

@ -6,9 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@ -30,7 +28,4 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
return selectCount(DeptDO::getParentId, parentId);
}
@Select("SELECT COUNT(*) FROM system_dept WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -38,14 +38,15 @@ public interface DictDataMapper extends BaseMapperX<DictDataDO> {
default PageResult<DictDataDO> selectPage(DictDataPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DictDataDO>()
.likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
.likeIfPresent(DictDataDO::getDictType, reqVO.getDictType())
.eqIfPresent(DictDataDO::getDictType, reqVO.getDictType())
.eqIfPresent(DictDataDO::getStatus, reqVO.getStatus())
.orderByDesc(Arrays.asList(DictDataDO::getDictType, DictDataDO::getSort)));
}
default List<DictDataDO> selectList(DictDataExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<DictDataDO>().likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
.likeIfPresent(DictDataDO::getDictType, reqVO.getDictType())
return selectList(new LambdaQueryWrapperX<DictDataDO>()
.likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
.eqIfPresent(DictDataDO::getDictType, reqVO.getDictType())
.eqIfPresent(DictDataDO::getStatus, reqVO.getStatus()));
}

View File

@ -5,9 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@ -43,10 +41,8 @@ public interface DictTypeMapper extends BaseMapperX<DictTypeDO> {
return selectOne(DictTypeDO::getName, name);
}
@Update("UPDATE system_dict_type SET DELETED = 1,DELETED_TIME=#{deletedTime} WHERE id = #{id}")
int deleteById(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime);
default int deleteById(Long id) {
return deleteById(id, LocalDateTime.now());
}
@Update("UPDATE system_dict_type SET deleted = 1, deleted_time = #{deletedTime} WHERE id = #{id}")
void updateToDelete(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime);
}

View File

@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
/**
@ -30,7 +27,4 @@ public interface OAuth2ClientMapper extends BaseMapperX<OAuth2ClientDO> {
return selectOne(OAuth2ClientDO::getClientId, clientId);
}
@Select("SELECT COUNT(*) FROM system_oauth2_client WHERE update_time > #{maxUpdateTime}")
int selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -6,9 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuLi
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
@ -28,7 +26,4 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
}
@Select("SELECT COUNT(*) FROM system_menu WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -8,10 +8,8 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleEx
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.lang.Nullable;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -47,7 +45,4 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
return selectList(RoleDO::getStatus, statuses);
}
@Select("SELECT COUNT(*) FROM system_role WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
/**
* 实体 {@link RoleMenuDO} 的批量插入 Mapper
*
* @author 芋道源码
*/
@Repository
public class RoleMenuBatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
}

View File

@ -2,13 +2,11 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -20,23 +18,21 @@ public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
}
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
return selectList(new QueryWrapper<RoleMenuDO>().eq("role_id", roleId));
return selectList(RoleMenuDO::getRoleId, roleId);
}
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
delete(new QueryWrapper<RoleMenuDO>().eq("role_id", roleId)
.in("menu_id", menuIds));
delete(new LambdaQueryWrapper<RoleMenuDO>()
.eq(RoleMenuDO::getRoleId, roleId)
.in(RoleMenuDO::getMenuId, menuIds));
}
default void deleteListByMenuId(Long menuId) {
delete(new QueryWrapper<RoleMenuDO>().eq("menu_id", menuId));
delete(new LambdaQueryWrapper<RoleMenuDO>().eq(RoleMenuDO::getMenuId, menuId));
}
default void deleteListByRoleId(Long roleId) {
delete(new QueryWrapper<RoleMenuDO>().eq("role_id", roleId));
delete(new LambdaQueryWrapper<RoleMenuDO>().eq(RoleMenuDO::getRoleId, roleId));
}
@Select("SELECT COUNT(*) FROM system_role_menu WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
/**
* 实体 {@link UserRoleDO} 的批量插入 Mapper
*
* @author 芋道源码
*/
@Repository
public class UserRoleBatchInsertMapper extends ServiceImpl<UserRoleMapper, UserRoleDO> {
}

View File

@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -14,32 +12,25 @@ import java.util.List;
public interface UserRoleMapper extends BaseMapperX<UserRoleDO> {
default List<UserRoleDO> selectListByUserId(Long userId) {
return selectList(new QueryWrapper<UserRoleDO>().eq("user_id", userId));
}
default List<UserRoleDO> selectListByRoleId(Long roleId) {
return selectList(new QueryWrapper<UserRoleDO>().eq("role_id", roleId));
return selectList(UserRoleDO::getUserId, userId);
}
default void deleteListByUserIdAndRoleIdIds(Long userId, Collection<Long> roleIds) {
delete(new QueryWrapper<UserRoleDO>().eq("user_id", userId)
.in("role_id", roleIds));
delete(new LambdaQueryWrapper<UserRoleDO>()
.eq(UserRoleDO::getUserId, userId)
.in(UserRoleDO::getRoleId, roleIds));
}
default void deleteListByUserId(Long userId) {
delete(new QueryWrapper<UserRoleDO>().eq("user_id", userId));
delete(new LambdaQueryWrapper<UserRoleDO>().eq(UserRoleDO::getUserId, userId));
}
default void deleteListByRoleId(Long roleId) {
delete(new QueryWrapper<UserRoleDO>().eq("role_id", roleId));
delete(new LambdaQueryWrapper<UserRoleDO>().eq(UserRoleDO::getRoleId, roleId));
}
default List<UserRoleDO> selectListByRoleIds(Collection<Long> roleIds) {
return selectList(UserRoleDO::getRoleId, roleIds);
}
@Select("SELECT COUNT(*) FROM system_user_role WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -7,9 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -42,7 +40,4 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
return selectOne(SensitiveWordDO::getName, name);
}
@Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
@Mapper
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
@ -21,7 +18,4 @@ public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
.orderByDesc(SmsChannelDO::getId));
}
@Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -7,17 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTempla
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
@Select("SELECT COUNT(*) FROM system_sms_template WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
default SmsTemplateDO selectByCode(String code) {
return selectOne(SmsTemplateDO::getCode, code);
}

View File

@ -1,15 +1,13 @@
package cn.iocoder.yudao.module.system.dal.mysql.tenant;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -52,7 +50,4 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
return selectList(TenantDO::getPackageId, packageId);
}
@Select("SELECT COUNT(*) FROM system_tenant WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
}

View File

@ -23,7 +23,7 @@ public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener<Sm
@Override
public void onMessage(SmsChannelRefreshMessage message) {
log.info("[onMessage][收到 SmsChannel 刷新消息]");
smsChannelService.initSmsClients();
smsChannelService.initLocalCache();
}
}

View File

@ -22,9 +22,9 @@ import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

View File

@ -4,7 +4,8 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
@ -16,8 +17,8 @@ import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -40,19 +41,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class DeptServiceImpl implements DeptService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 部门缓存
* key部门编号 {@link DeptDO#getId()}
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
@SuppressWarnings("FieldCanBeLocal")
@Getter
private volatile Map<Long, DeptDO> deptCache;
/**
* 父部门缓存
@ -61,11 +56,8 @@ public class DeptServiceImpl implements DeptService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
@Getter
private volatile Multimap<Long, DeptDO> parentDeptCache;
/**
* 缓存部门的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile LocalDateTime maxUpdateTime;
@Resource
private DeptMapper deptMapper;
@ -73,58 +65,28 @@ public class DeptServiceImpl implements DeptService {
@Resource
private DeptProducer deptProducer;
@Resource
@Lazy // 注入自己,所以延迟加载
private DeptService self;
/**
* 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
*/
@Override
@PostConstruct
@TenantIgnore // 初始化缓存,无需租户过滤
public synchronized void initLocalCache() {
// 获取部门列表,如果有更新
List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(deptList)) {
return;
}
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步:查询数据
List<DeptDO> depts = deptMapper.selectList();
log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
// 构建缓存
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
deptList.forEach(sysRoleDO -> {
builder.put(sysRoleDO.getId(), sysRoleDO);
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
// 第二步:构建缓存
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
depts.forEach(sysRoleDO -> {
builder.put(sysRoleDO.getId(), sysRoleDO);
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
});
deptCache = builder.build();
parentDeptCache = parentBuilder.build();
});
// 设置缓存
deptCache = builder.build();
parentDeptCache = parentBuilder.build();
maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime);
log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
self.initLocalCache();
}
/**
* 如果部门发生变化,从数据库中获取最新的全量部门。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前部门的最大更新时间
* @return 部门列表
*/
protected List<DeptDO> loadDeptIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadDeptIfUpdate][首次加载全量部门]");
} else { // 判断数据库中是否有更新的部门
if (deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadDeptIfUpdate][增量加载全量部门]");
}
// 第二步,如果有更新,则从数据库加载所有部门
return deptMapper.selectList();
}
@Override
@ -202,12 +164,19 @@ public class DeptServiceImpl implements DeptService {
if (recursiveCount == 0) {
return;
}
// 获得子部门
Collection<DeptDO> depts = parentDeptMap.get(parentId);
if (CollUtil.isEmpty(depts)) {
return;
}
// 针对多租户,过滤掉非当前租户的部门
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
depts = CollUtil.filterNew(depts, dept -> tenantId.equals(dept.getTenantId()));
}
result.addAll(depts);
// 继续递归
depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
recursiveCount - 1, parentDeptMap));

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.dict;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
@ -58,7 +59,8 @@ public class DictTypeServiceImpl implements DictTypeService {
// 校验正确性
checkCreateOrUpdate(null, reqVO.getName(), reqVO.getType());
// 插入字典类型
DictTypeDO dictType = DictTypeConvert.INSTANCE.convert(reqVO);
DictTypeDO dictType = DictTypeConvert.INSTANCE.convert(reqVO)
.setDeletedTime(LocalDateTimeUtils.EMPTY); // 唯一索引,避免 null 值
dictTypeMapper.insert(dictType);
return dictType.getId();
}
@ -81,7 +83,7 @@ public class DictTypeServiceImpl implements DictTypeService {
throw exception(DICT_TYPE_HAS_CHILDREN);
}
// 删除字典类型
dictTypeMapper.deleteById(id);
dictTypeMapper.updateToDelete(id, LocalDateTime.now());
}
@Override

View File

@ -17,18 +17,17 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
@ -41,12 +40,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class OAuth2ClientServiceImpl implements OAuth2ClientService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 客户端缓存
* key客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
@ -56,11 +49,6 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
@Getter // 解决单测
@Setter // 解决单测
private volatile Map<String, OAuth2ClientDO> clientCache;
/**
* 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@Getter
private volatile LocalDateTime maxUpdateTime;
@Resource
private OAuth2ClientMapper oauth2ClientMapper;
@ -74,42 +62,12 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
@Override
@PostConstruct
public void initLocalCache() {
// 获取客户端列表,如果有更新
List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(tenantList)) {
return;
}
// 第一步:查询数据
List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
log.info("[initLocalCache][缓存 OAuth2 客户端,数量为:{}]", clients.size());
// 写入缓存
clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCache();
}
/**
* 如果客户端发生变化,从数据库中获取最新的全量客户端。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前客户端的最大更新时间
* @return 客户端列表
*/
private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
} else { // 判断数据库中是否有更新的客户端
if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
}
// 第二步,如果有更新,则从数据库加载所有客户端
return oauth2ClientMapper.selectList();
// 第二步:构建缓存
clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
}
@Override

View File

@ -18,9 +18,9 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@ -28,7 +28,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@ -43,18 +42,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class MenuServiceImpl implements MenuService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 菜单缓存
* key菜单编号
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
@Getter
private volatile Map<Long, MenuDO> menuCache;
/**
* 权限与菜单缓存
@ -63,11 +57,8 @@ public class MenuServiceImpl implements MenuService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
@Getter
private volatile Multimap<String, MenuDO> permissionMenuCache;
/**
* 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile LocalDateTime maxUpdateTime;
@Resource
private MenuMapper menuMapper;
@ -86,32 +77,11 @@ public class MenuServiceImpl implements MenuService {
@Override
@PostConstruct
public synchronized void initLocalCache() {
initLocalCacheIfUpdate(null);
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCacheIfUpdate(this.maxUpdateTime);
}
/**
* 刷新本地缓存
*
* @param maxUpdateTime 最大更新时间
* 1. 如果 maxUpdateTime 为 null则“强制”刷新缓存
* 2. 如果 maxUpdateTime 不为 null判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
*/
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
// 如果没有增量的数据变化,则不进行本地缓存的刷新
if (maxUpdateTime != null
&& menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
return;
}
// 第一步:查询数据
List<MenuDO> menuList = menuMapper.selectList();
log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
// 构建缓存
// 第二步:构建缓存
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
menuList.forEach(menuDO -> {
@ -122,9 +92,6 @@ public class MenuServiceImpl implements MenuService {
});
menuCache = menuCacheBuilder.build();
permissionMenuCache = permMenuCacheBuilder.build();
// 设置最新的 maxUpdateTime用于下次的增量判断
this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
}
@Override

View File

@ -9,15 +9,14 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
@ -31,8 +30,6 @@ import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@ -40,12 +37,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static java.util.Collections.singleton;
/**
@ -57,12 +52,6 @@ import static java.util.Collections.singleton;
@Slf4j
public class PermissionServiceImpl implements PermissionService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 角色编号与菜单编号的缓存映射
* key角色编号
@ -83,11 +72,6 @@ public class PermissionServiceImpl implements PermissionService {
@Getter
@Setter // 单元测试需要
private volatile Multimap<Long, Long> menuRoleCache;
/**
* 缓存 RoleMenu 的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@Getter
private volatile LocalDateTime roleMenuMaxUpdateTime;
/**
* 用户编号与角色编号的缓存映射
@ -99,20 +83,11 @@ public class PermissionServiceImpl implements PermissionService {
@Getter
@Setter // 单元测试需要
private volatile Map<Long, Set<Long>> userRoleCache;
/**
* 缓存 UserRole 的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@Getter
private volatile LocalDateTime userRoleMaxUpdateTime;
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
private RoleMenuBatchInsertMapper roleMenuBatchInsertMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private UserRoleBatchInsertMapper userRoleBatchInsertMapper;
@Resource
private RoleService roleService;
@ -126,106 +101,52 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private PermissionProducer permissionProducer;
@Resource
@Lazy // 注入自己,所以延迟加载
private PermissionService self;
@Override
@PostConstruct
@TenantIgnore // 初始化缓存,无需租户过滤
public void initLocalCache() {
initUserRoleLocalCache();
initRoleMenuLocalCache();
initLocalCacheForRoleMenu();
initLocalCacheForUserRole();
}
/**
* 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
* 刷新 RoleMenu 本地缓存
*/
@VisibleForTesting
void initRoleMenuLocalCache() {
// 获取角色与菜单的关联列表,如果有更新
List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
if (CollUtil.isEmpty(roleMenuList)) {
return;
}
void initLocalCacheForRoleMenu() {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步:查询数据
List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
// 初始化 roleMenuCache 和 menuRoleCache 缓存
ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
roleMenuList.forEach(roleMenuDO -> {
roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
// 第二步:构建缓存
ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
roleMenus.forEach(roleMenuDO -> {
roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
});
roleMenuCache = roleMenuCacheBuilder.build();
menuRoleCache = menuRoleCacheBuilder.build();
});
roleMenuCache = roleMenuCacheBuilder.build();
menuRoleCache = menuRoleCacheBuilder.build();
roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
}
/**
* 初始化 {@link #userRoleCache} 缓存
* 刷新 UserRole 本地缓存
*/
@VisibleForTesting
void initUserRoleLocalCache() {
// 获取用户与角色的关联列表,如果有更新
List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
if (CollUtil.isEmpty(userRoleList)) {
return;
}
void initLocalCacheForUserRole() {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步:加载数据
List<UserRoleDO> userRoles = userRoleMapper.selectList();
log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
// 初始化 userRoleCache 缓存
ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
log.info("[initUserRoleLocalCache][初始化用户与角色的关联数量为 {}]", userRoleList.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
self.initLocalCache();
}
/**
* 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
* @return 角色与菜单的关联列表
*/
protected List<RoleMenuDO> loadRoleMenuIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
} else { // 判断数据库中是否有更新的角色与菜单的关联
if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
}
// 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
return roleMenuMapper.selectList();
}
/**
* 如果用户与角色的关联发生变化,从数据库中获取最新的全量用户与角色的关联。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
* @return 角色与菜单的关联列表
*/
protected List<UserRoleDO> loadUserRoleIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
} else { // 判断数据库中是否有更新的用户与角色的关联
if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
}
// 第二步,如果有更新,则从数据库加载所有用户与角色的关联
return userRoleMapper.selectList();
// 第二步:构建缓存
ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
});
}
@Override
@ -286,7 +207,7 @@ public class PermissionServiceImpl implements PermissionService {
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
// 执行新增和删除。对于已经授权的菜单,不用做任何处理
if (!CollectionUtil.isEmpty(createMenuIds)) {
roleMenuBatchInsertMapper.saveBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
RoleMenuDO entity = new RoleMenuDO();
entity.setRoleId(roleId);
entity.setMenuId(menuId);
@ -330,7 +251,7 @@ public class PermissionServiceImpl implements PermissionService {
Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIds);
// 执行新增和删除。对于已经授权的角色,不用做任何处理
if (!CollectionUtil.isEmpty(createRoleIds)) {
userRoleBatchInsertMapper.saveBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
UserRoleDO entity = new UserRoleDO();
entity.setUserId(userId);
entity.setRoleId(roleId);

View File

@ -6,8 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
@ -15,15 +14,14 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUp
import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@ -32,7 +30,6 @@ import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@ -48,12 +45,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class RoleServiceImpl implements RoleService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 角色缓存
* key角色编号 {@link RoleDO#getId()}
@ -62,11 +53,6 @@ public class RoleServiceImpl implements RoleService {
*/
@Getter
private volatile Map<Long, RoleDO> roleCache;
/**
* 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@Getter
private volatile LocalDateTime maxUpdateTime;
@Resource
private PermissionService permissionService;
@ -77,53 +63,21 @@ public class RoleServiceImpl implements RoleService {
@Resource
private RoleProducer roleProducer;
@Resource
@Lazy // 注入自己,所以延迟加载
private RoleService self;
/**
* 初始化 {@link #roleCache} 缓存
*/
@Override
@PostConstruct
@TenantIgnore // 忽略自动多租户,全局初始化缓存
public void initLocalCache() {
// 获取角色列表,如果有更新
List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(roleList)) {
return;
}
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步:查询数据
List<RoleDO> roleList = roleMapper.selectList();
log.info("[initLocalCache][缓存角色,数量为:{}]", roleList.size());
// 写入缓存
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
self.initLocalCache();
}
/**
* 如果角色发生变化,从数据库中获取最新的全量角色。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前角色的最大更新时间
* @return 角色列表
*/
private List<RoleDO> loadRoleIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadRoleIfUpdate][首次加载全量角色]");
} else { // 判断数据库中是否有更新的角色
if (roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadRoleIfUpdate][增量加载全量角色]");
}
// 第二步,如果有更新,则从数据库加载所有角色
return roleMapper.selectList();
// 第二步:构建缓存
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
});
}
@Override

View File

@ -17,13 +17,11 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -40,12 +38,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
@Validated
public class SensitiveWordServiceImpl implements SensitiveWordService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 敏感词标签缓存
* key敏感词编号 {@link SensitiveWordDO#getId()}
@ -55,12 +47,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Getter
private volatile Set<String> sensitiveWordTagsCache = Collections.emptySet();
/**
* 缓存敏感词的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@Getter
private volatile LocalDateTime maxUpdateTime;
@Resource
private SensitiveWordMapper sensitiveWordMapper;
@ -84,21 +70,17 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Override
@PostConstruct
public void initLocalCache() {
// 获取敏感词列表,如果有更新
List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(sensitiveWordList)) {
return;
}
// 第一步:查询数据
List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
log.info("[initLocalCache][缓存敏感词,数量为:{}]", sensitiveWords.size());
// 第二步:构建缓存
// 写入 sensitiveWordTagsCache 缓存
Set<String> tags = new HashSet<>();
sensitiveWordList.forEach(word -> tags.addAll(word.getTags()));
sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
sensitiveWordTagsCache = tags;
// 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
initSensitiveWordTrie(sensitiveWordList);
// 写入 maxUpdateTime 最大更新时间
maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime);
log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size());
initSensitiveWordTrie(sensitiveWords);
}
private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
@ -122,33 +104,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
this.tagSensitiveWordTries = tagSensitiveWordTries;
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCache();
}
/**
* 如果敏感词发生变化,从数据库中获取最新的全量敏感词。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前敏感词的最大更新时间
* @return 敏感词列表
*/
private List<SensitiveWordDO> loadSensitiveWordIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
// 如果更新时间为空,说明 DB 一定有新数据
if (maxUpdateTime == null) {
log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]");
} else { // 判断数据库中是否有更新的敏感词
if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]");
}
// 第二步,如果有更新,则从数据库加载所有敏感词
return sensitiveWordMapper.selectList();
}
@Override
public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
// 校验唯一性

View File

@ -21,7 +21,7 @@ public interface SmsChannelService {
/**
* 初始化短信客户端
*/
void initSmsClients();
void initLocalCache();
/**
* 创建短信渠道

View File

@ -1,8 +1,6 @@
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
@ -13,12 +11,10 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@ -27,26 +23,14 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
/**
* 短信渠道Service实现类
* 短信渠道 Service 实现类
*
* @author zzf
* @date 2021/1/25 9:25
*/
@Service
@Slf4j
public class SmsChannelServiceImpl implements SmsChannelService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile LocalDateTime maxUpdateTime;
@Resource
private SmsClientFactory smsClientFactory;
@ -61,46 +45,14 @@ public class SmsChannelServiceImpl implements SmsChannelService {
@Override
@PostConstruct
public void initSmsClients() {
// 获取短信渠道,如果有更新
List<SmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(smsChannels)) {
return;
}
public void initLocalCache() {
// 第一步:查询数据
List<SmsChannelDO> channels = smsChannelMapper.selectList();
log.info("[initLocalCache][缓存短信渠道,数量为:{}]", channels.size());
// 创建或更新短信 Client
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(smsChannels);
// 第二步:构建缓存:创建或更新短信 Client
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
// 写入缓存
maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime);
log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initSmsClients();
}
/**
* 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前短信渠道的最大更新时间
* @return 短信渠道列表
*/
private List<SmsChannelDO> loadSmsChannelIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
} else { // 判断数据库中是否有更新的短信渠道
if (smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
}
// 第二步,如果有更新,则从数据库加载所有短信渠道
return smsChannelMapper.selectList();
}
@Override

View File

@ -113,7 +113,7 @@ public class SmsSendServiceImpl implements SmsSendService {
SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);
// 短信模板不存在
if (channelDO == null) {
throw exception(SMS_SEND_TEMPLATE_NOT_EXISTS);
throw exception(SMS_CHANNEL_NOT_EXISTS);
}
return channelDO;
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@ -20,15 +19,17 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -44,12 +45,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class SmsTemplateServiceImpl implements SmsTemplateService {
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
/**
* 正则表达式,匹配 {} 中的变量
*/
@ -73,51 +68,18 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
@Getter // 为了方便测试,这里提供 getter 方法
private volatile Map<String, SmsTemplateDO> smsTemplateCache;
/**
* 缓存短信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile LocalDateTime maxUpdateTime;
@Override
@PostConstruct
public void initLocalCache() {
// 获取短信模板列表,如果有更新
List<SmsTemplateDO> smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
if (CollUtil.isEmpty(smsTemplateList)) {
return;
}
// 第一步:查询数据
List<SmsTemplateDO> smsTemplateList = smsTemplateMapper.selectList();
log.info("[initLocalCache][缓存短信模版,数量为:{}]", smsTemplateList.size());
// 写入缓存
// 第二步:构建缓存
smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime);
log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
}
/**
* 如果短信模板发生变化,从数据库中获取最新的全量短信模板。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前短信模板的最大更新时间
* @return 短信模板列表
*/
private List<SmsTemplateDO> loadSmsTemplateIfUpdate(LocalDateTime maxUpdateTime) {
// 第一步,判断是否要更新。
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
log.info("[loadSmsTemplateIfUpdate][首次加载全量短信模板]");
} else { // 判断数据库中是否有更新的短信模板
if (smsTemplateMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadSmsTemplateIfUpdate][增量加载全量短信模板]");
}
// 第二步,如果有更新,则从数据库加载所有短信模板
return smsTemplateMapper.selectList();
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCache();
}
@Override

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
@ -72,6 +73,10 @@ public class AdminUserServiceImpl implements AdminUserService {
@Resource
private FileApi fileApi;
@Resource
@Lazy // 循环依赖(自己依赖自己),避免报错
private AdminUserServiceImpl self;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createUser(UserCreateReqVO reqVO) {
@ -83,7 +88,7 @@ public class AdminUserServiceImpl implements AdminUserService {
}
});
// 校验正确性
checkCreateOrUpdate(null, reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
self.checkCreateOrUpdate(null, reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
reqVO.getDeptId(), reqVO.getPostIds());
// 插入用户
AdminUserDO user = UserConvert.INSTANCE.convert(reqVO);
@ -102,7 +107,7 @@ public class AdminUserServiceImpl implements AdminUserService {
@Transactional(rollbackFor = Exception.class)
public void updateUser(UserUpdateReqVO reqVO) {
// 校验正确性
checkCreateOrUpdate(reqVO.getId(), reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
self.checkCreateOrUpdate(reqVO.getId(), reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
reqVO.getDeptId(), reqVO.getPostIds());
// 更新用户
AdminUserDO updateObj = UserConvert.INSTANCE.convert(reqVO);
@ -299,7 +304,8 @@ public class AdminUserServiceImpl implements AdminUserService {
return deptIds;
}
private void checkCreateOrUpdate(Long id, String username, String mobile, String email,
@DataPermission(enable = false) // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
public void checkCreateOrUpdate(Long id, String username, String mobile, String email,
Long deptId, Set<Long> postIds) {
// 校验用户存在
checkUserExists(id);

View File

@ -5,8 +5,6 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
@ -16,7 +14,7 @@ import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.anji.captcha.service.CaptchaService;
import com.xingyuv.captcha.service.CaptchaService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@ -25,7 +23,6 @@ import javax.annotation.Resource;
import javax.validation.Validator;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.system.service.dept;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
@ -8,26 +12,22 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import com.google.common.collect.Multimap;
import org.junit.jupiter.api.BeforeEach;
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.time.LocalDateTime;
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.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -47,9 +47,14 @@ public class DeptServiceTest extends BaseDbUnitTest {
@MockBean
private DeptProducer deptProducer;
@BeforeEach
public void setUp() {
// 清理租户上下文
TenantContextHolder.clear();
}
@Test
@SuppressWarnings("unchecked")
void testInitLocalCache() {
public void testInitLocalCache() {
// mock 数据
DeptDO deptDO1 = randomDeptDO();
deptMapper.insert(deptDO1);
@ -59,18 +64,15 @@ public class DeptServiceTest extends BaseDbUnitTest {
// 调用
deptService.initLocalCache();
// 断言 deptCache 缓存
Map<Long, DeptDO> deptCache = (Map<Long, DeptDO>) getFieldValue(deptService, "deptCache");
Map<Long, DeptDO> deptCache = deptService.getDeptCache();
assertEquals(2, deptCache.size());
assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
// 断言 parentDeptCache 缓存
Multimap<Long, DeptDO> parentDeptCache = (Multimap<Long, DeptDO>) getFieldValue(deptService, "parentDeptCache");
Multimap<Long, DeptDO> parentDeptCache = deptService.getParentDeptCache();
assertEquals(2, parentDeptCache.size());
assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
// 断言 maxUpdateTime 缓存
LocalDateTime maxUpdateTime = (LocalDateTime) getFieldValue(deptService, "maxUpdateTime");
assertEquals(ObjectUtils.max(deptDO1.getUpdateTime(), deptDO2.getUpdateTime()), maxUpdateTime);
}
@Test

View File

@ -57,7 +57,7 @@ public class DictDataServiceTest extends BaseDbUnitTest {
// 准备参数
DictDataPageReqVO reqVO = new DictDataPageReqVO();
reqVO.setLabel("");
reqVO.setDictType("yu");
reqVO.setDictType("yunai");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用
@ -86,7 +86,7 @@ public class DictDataServiceTest extends BaseDbUnitTest {
// 准备参数
DictDataExportReqVO reqVO = new DictDataExportReqVO();
reqVO.setLabel("");
reqVO.setDictType("yu");
reqVO.setDictType("yunai");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用

View File

@ -19,7 +19,6 @@ import java.util.Collections;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@ -59,8 +58,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
assertEquals(2, clientCache.size());
assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
// 断言 maxUpdateTime 缓存
assertEquals(max(clientDO1.getUpdateTime(), clientDO2.getUpdateTime()), oauth2ClientService.getMaxUpdateTime());
}
@Test

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringAopUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
@ -20,7 +19,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
@ -51,32 +49,24 @@ public class MenuServiceTest extends BaseDbUnitTest {
private TenantService tenantService;
@Test
public void testInitLocalCache_success() throws Exception {
MenuDO menuDO1 = createMenuDO(MenuTypeEnum.MENU, "xxxx", 0L);
public void testInitLocalCache_success() {
MenuDO menuDO1 = randomPojo(MenuDO.class);
menuMapper.insert(menuDO1);
MenuDO menuDO2 = createMenuDO(MenuTypeEnum.MENU, "xxxx", 0L);
MenuDO menuDO2 = randomPojo(MenuDO.class);
menuMapper.insert(menuDO2);
// 调用
menuService.initLocalCache();
// 获取代理对象
MenuServiceImpl target = (MenuServiceImpl) SpringAopUtils.getTarget(menuService);
Map<Long, MenuDO> menuCache =
(Map<Long, MenuDO>) BeanUtil.getFieldValue(target, "menuCache");
// 校验 menuCache 缓存
Map<Long, MenuDO> menuCache = menuService.getMenuCache();
Assert.isTrue(menuCache.size() == 2);
assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
Multimap<String, MenuDO> permissionMenuCache =
(Multimap<String, MenuDO>) BeanUtil.getFieldValue(target, "permissionMenuCache");
// 校验 permissionMenuCache 缓存
Multimap<String, MenuDO> permissionMenuCache = menuService.getPermissionMenuCache();
Assert.isTrue(permissionMenuCache.size() == 2);
assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(target, "maxUpdateTime");
assertEquals(ObjectUtils.max(menuDO1.getUpdateTime(), menuDO2.getUpdateTime()), maxUpdateTime);
}
@Test

View File

@ -3,18 +3,15 @@ package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
@ -27,8 +24,10 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@ -42,8 +41,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Import({PermissionServiceImpl.class,
RoleMenuBatchInsertMapper.class, UserRoleBatchInsertMapper.class})
@Import({PermissionServiceImpl.class})
public class PermissionServiceTest extends BaseDbUnitTest {
@Resource
@ -52,11 +50,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
private RoleMenuBatchInsertMapper roleMenuBatchInsertMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private UserRoleBatchInsertMapper userRoleBatchInsertMapper;
@MockBean
private RoleService roleService;
@ -71,7 +65,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
private PermissionProducer permissionProducer;
@Test
public void testInitRoleMenuLocalCache() {
public void testInitLocalCacheForRoleMenu() {
// mock 数据
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
roleMenuMapper.insert(roleMenuDO01);
@ -79,7 +73,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
roleMenuMapper.insert(roleMenuDO02);
// 调用
permissionService.initRoleMenuLocalCache();
permissionService.initLocalCacheForRoleMenu();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
@ -87,13 +81,10 @@ public class PermissionServiceTest extends BaseDbUnitTest {
assertEquals(2, permissionService.getMenuRoleCache().size());
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
// 断言 maxUpdateTime 缓存
LocalDateTime maxUpdateTime = permissionService.getRoleMenuMaxUpdateTime();
assertEquals(ObjectUtils.max(roleMenuDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test
public void testInitUserRoleLocalCache() {
public void testInitLocalCacheForUserRole() {
// mock 数据
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
userRoleMapper.insert(userRoleDO01);
@ -101,13 +92,10 @@ public class PermissionServiceTest extends BaseDbUnitTest {
userRoleMapper.insert(roleMenuDO02);
// 调用
permissionService.initUserRoleLocalCache();
permissionService.initLocalCacheForUserRole();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getUserRoleCache().size());
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
// 断言 maxUpdateTime 缓存
LocalDateTime maxUpdateTime = permissionService.getUserRoleMaxUpdateTime();
assertEquals(ObjectUtils.max(userRoleDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test

View File

@ -23,7 +23,6 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@ -59,8 +58,6 @@ public class RoleServiceTest extends BaseDbUnitTest {
Map<Long, RoleDO> roleCache = roleService.getRoleCache();
assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));
assertPojoEquals(roleDO2, roleCache.get(roleDO2.getId()));
// 断言 maxUpdateTime 缓存
assertEquals(max(roleDO1.getUpdateTime(), roleDO2.getUpdateTime()), roleService.getMaxUpdateTime());
}
@Test

View File

@ -60,8 +60,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
// 调用
sensitiveWordService.initLocalCache();
// 断言 maxUpdateTime 缓存
assertEquals(max(wordDO1.getUpdateTime(), wordDO2.getUpdateTime()), sensitiveWordService.getMaxUpdateTime());
// 断言 sensitiveWordTagsCache 缓存
assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTags());
// 断言 tagSensitiveWordTries 缓存

View File

@ -1,18 +1,17 @@
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@ -22,12 +21,11 @@ import java.time.LocalDateTime;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ -57,10 +55,7 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
smsChannelMapper.insert(smsChannelDO02);
// 调用
smsChannelService.initSmsClients();
// 校验 maxUpdateTime 属性
LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
smsChannelService.initLocalCache();
// 校验调用
verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
argThat(properties -> isPojoEquals(smsChannelDO01, properties)));

View File

@ -61,7 +61,6 @@ public class SmsTemplateServiceTest extends BaseDbUnitTest {
private SmsProducer smsProducer;
@Test
@SuppressWarnings("unchecked")
void testInitLocalCache() {
// mock 数据
SmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
@ -72,13 +71,10 @@ public class SmsTemplateServiceTest extends BaseDbUnitTest {
// 调用
smsTemplateService.initLocalCache();
// 断言 deptCache 缓存
Map<String, SmsTemplateDO> smsTemplateCache = (Map<String, SmsTemplateDO>) getFieldValue(smsTemplateService, "smsTemplateCache");
Map<String, SmsTemplateDO> smsTemplateCache = smsTemplateService.getSmsTemplateCache();
assertEquals(2, smsTemplateCache.size());
assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
// 断言 maxUpdateTime 缓存
LocalDateTime maxUpdateTime = (LocalDateTime) getFieldValue(smsTemplateService, "maxUpdateTime");
assertEquals(max(smsTemplate01.getUpdateTime(), smsTemplate02.getUpdateTime()), maxUpdateTime);
}
@Test

View File

@ -9,7 +9,7 @@ spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:

View File

@ -111,6 +111,7 @@ CREATE TABLE IF NOT EXISTS "system_dict_type" (
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"deleted_time" timestamp NOT NULL,
PRIMARY KEY ("id")
) COMMENT '字典类型表';