feat: initial commit

This commit is contained in:
gin
2026-05-07 18:39:00 +08:00
commit cdee21ee8e
653 changed files with 63946 additions and 0 deletions
@@ -0,0 +1,52 @@
package com.agileboot.domain.common.cache;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
/**
* 缓存中心 提供全局访问点
* 如果是领域类的缓存 可以自己新建一个直接放在CacheCenter 不用放在infrastructure包里的GuavaCacheService
* 或者RedisCacheService
* @author valarchie
*/
@Component
public class CacheCenter {
public static AbstractGuavaCacheTemplate<String> configCache;
public static AbstractGuavaCacheTemplate<SysDeptEntity> deptCache;
public static RedisCacheTemplate<String> captchaCache;
public static RedisCacheTemplate<SystemLoginUser> loginUserCache;
public static RedisCacheTemplate<SysUserEntity> userCache;
public static RedisCacheTemplate<SysRoleEntity> roleCache;
public static RedisCacheTemplate<SysPostEntity> postCache;
@PostConstruct
public void init() {
GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class);
RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class);
configCache = guavaCache.configCache;
deptCache = guavaCache.deptCache;
captchaCache = redisCache.captchaCache;
loginUserCache = redisCache.loginUserCache;
userCache = redisCache.userCache;
roleCache = redisCache.roleCache;
postCache = redisCache.postCache;
}
}
@@ -0,0 +1,39 @@
package com.agileboot.domain.common.cache;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class GuavaCacheService {
private final SysConfigService configService;
private final SysDeptService deptService;
public final AbstractGuavaCacheTemplate<String> configCache = new AbstractGuavaCacheTemplate<String>() {
@Override
public String getObjectFromDb(Object id) {
return configService.getConfigValueByKey(id.toString());
}
};
public final AbstractGuavaCacheTemplate<SysDeptEntity> deptCache = new AbstractGuavaCacheTemplate<SysDeptEntity>() {
@Override
public SysDeptEntity getObjectFromDb(Object id) {
return deptService.getById(id.toString());
}
};
}
@@ -0,0 +1,83 @@
package com.agileboot.domain.common.cache;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.common.enums.common.GenderEnum;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.common.enums.common.NoticeStatusEnum;
import com.agileboot.common.enums.common.NoticeTypeEnum;
import com.agileboot.common.enums.common.OperationStatusEnum;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.common.UserStatusEnum;
import com.agileboot.common.enums.common.VisibleStatusEnum;
import com.agileboot.common.enums.common.YesOrNoEnum;
import com.agileboot.common.enums.dictionary.Dictionary;
import com.agileboot.common.enums.DictionaryEnum;
import com.agileboot.common.enums.dictionary.DictionaryData;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 本地一级缓存 使用Map
*
* @author valarchie
*/
public class MapCache {
private static final Map<String, List<DictionaryData>> DICTIONARY_CACHE = MapUtil.newHashMap(128);
private MapCache() {
}
static {
initDictionaryCache();
}
private static void initDictionaryCache() {
// TODO 这个可以做成自动扫描
loadInCache(BusinessTypeEnum.values());
loadInCache(YesOrNoEnum.values());
loadInCache(StatusEnum.values());
loadInCache(GenderEnum.values());
loadInCache(NoticeStatusEnum.values());
loadInCache(NoticeTypeEnum.values());
loadInCache(OperationStatusEnum.values());
loadInCache(LoginStatusEnum.values());
loadInCache(VisibleStatusEnum.values());
loadInCache(UserStatusEnum.values());
}
public static Map<String, List<DictionaryData>> dictionaryCache() {
return DICTIONARY_CACHE;
}
private static void loadInCache(DictionaryEnum[] dictionaryEnums) {
DICTIONARY_CACHE.put(getDictionaryName(dictionaryEnums[0].getClass()), arrayToList(dictionaryEnums));
}
private static String getDictionaryName(Class<?> clazz) {
Objects.requireNonNull(clazz);
Dictionary annotation = clazz.getAnnotation(Dictionary.class);
Objects.requireNonNull(annotation);
return annotation.name();
}
@SuppressWarnings("rawtypes")
private static List<DictionaryData> arrayToList(DictionaryEnum[] dictionaryEnums) {
if(ArrayUtil.isEmpty(dictionaryEnums)) {
return ListUtil.empty();
}
return Arrays.stream(dictionaryEnums).map(DictionaryData::new).collect(Collectors.toList());
}
}
@@ -0,0 +1,82 @@
package com.agileboot.domain.common.cache;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.infrastructure.cache.RedisUtil;
import com.agileboot.infrastructure.cache.redis.CacheKeyEnum;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class RedisCacheService {
private final RedisUtil redisUtil;
public RedisCacheTemplate<String> captchaCache;
public RedisCacheTemplate<SystemLoginUser> loginUserCache;
public RedisCacheTemplate<SysUserEntity> userCache;
public RedisCacheTemplate<SysRoleEntity> roleCache;
public RedisCacheTemplate<SysPostEntity> postCache;
// public RedisCacheTemplate<RoleInfo> roleModelInfoCache;
@PostConstruct
public void init() {
captchaCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.CAPTCHAT);
loginUserCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.LOGIN_USER_KEY);
userCache = new RedisCacheTemplate<SysUserEntity>(redisUtil, CacheKeyEnum.USER_ENTITY_KEY) {
@Override
public SysUserEntity getObjectFromDb(Object id) {
SysUserService userService = SpringUtil.getBean(SysUserService.class);
return userService.getById((Serializable) id);
}
};
roleCache = new RedisCacheTemplate<SysRoleEntity>(redisUtil, CacheKeyEnum.ROLE_ENTITY_KEY) {
@Override
public SysRoleEntity getObjectFromDb(Object id) {
SysRoleService roleService = SpringUtil.getBean(SysRoleService.class);
return roleService.getById((Serializable) id);
}
};
// roleModelInfoCache = new RedisCacheTemplate<RoleInfo>(redisUtil, CacheKeyEnum.ROLE_MODEL_INFO_KEY) {
// @Override
// public RoleInfo getObjectFromDb(Object id) {
// UserDetailsService userDetailsService = SpringUtil.getBean(UserDetailsService.class);
// return userDetailsService.getRoleInfo((Long) id);
// }
//
// };
postCache = new RedisCacheTemplate<SysPostEntity>(redisUtil, CacheKeyEnum.POST_ENTITY_KEY) {
@Override
public SysPostEntity getObjectFromDb(Object id) {
SysPostService postService = SpringUtil.getBean(SysPostService.class);
return postService.getById((Serializable) id);
}
};
}
}
@@ -0,0 +1,28 @@
package com.agileboot.domain.common.command;
import cn.hutool.core.collection.CollUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class BulkOperationCommand<T> {
public BulkOperationCommand(List<T> idList) {
if (CollUtil.isEmpty(idList)) {
throw new ApiException(ErrorCode.Business.COMMON_BULK_DELETE_IDS_IS_INVALID);
}
// 移除重复元素
this.ids = new HashSet<>(idList);
}
private Set<T> ids;
}
@@ -0,0 +1,18 @@
package com.agileboot.domain.common.dto;
import com.agileboot.domain.system.user.dto.UserDTO;
import java.util.Set;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class CurrentLoginUserDTO {
private UserDTO userInfo;
private String roleKey;
private Set<String> permissions;
}
@@ -0,0 +1,17 @@
package com.agileboot.domain.common.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@AllArgsConstructor
public class TokenDTO {
private String token;
private CurrentLoginUserDTO currentUser;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.common.dto;
import cn.hutool.core.lang.tree.Tree;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class TreeSelectedDTO {
private List<Long> checkedKeys;
private List<Tree<Long>> menus;
private List<Tree<Long>> depts;
}
@@ -0,0 +1,18 @@
package com.agileboot.domain.common.dto;
import lombok.Builder;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@Builder
public class UploadDTO {
private String url;
private String fileName;
private String newFileName;
private String originalFilename;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.common.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class UploadFileDTO {
public UploadFileDTO(String imgUrl) {
this.imgUrl = imgUrl;
}
private String imgUrl;
}
@@ -0,0 +1,52 @@
package com.agileboot.domain.system.config;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.config.command.ConfigUpdateCommand;
import com.agileboot.domain.system.config.dto.ConfigDTO;
import com.agileboot.domain.system.config.model.ConfigModel;
import com.agileboot.domain.system.config.model.ConfigModelFactory;
import com.agileboot.domain.system.config.query.ConfigQuery;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class ConfigApplicationService {
private final ConfigModelFactory configModelFactory;
private final SysConfigService configService;
public PageDTO<ConfigDTO> getConfigList(ConfigQuery query) {
Page<SysConfigEntity> page = configService.page(query.toPage(), query.toQueryWrapper());
List<ConfigDTO> records = page.getRecords().stream().map(ConfigDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public ConfigDTO getConfigInfo(Long id) {
SysConfigEntity byId = configService.getById(id);
return new ConfigDTO(byId);
}
public void updateConfig(ConfigUpdateCommand updateCommand) {
ConfigModel configModel = configModelFactory.loadById(updateCommand.getConfigId());
configModel.loadUpdateCommand(updateCommand);
configModel.checkCanBeModify();
configModel.updateById();
CacheCenter.configCache.invalidate(configModel.getConfigKey());
}
}
@@ -0,0 +1,24 @@
package com.agileboot.domain.system.config.command;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@Schema
public class ConfigUpdateCommand {
@NotNull
@Positive
private Long configId;
@NotNull
@NotEmpty
private String configValue;
}
@@ -0,0 +1,64 @@
package com.agileboot.domain.system.config.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 参数配置表
* </p>
*
* @author valarchie
* @since 2022-11-03
*/
@Getter
@Setter
@TableName("sys_config")
@ApiModel(value = "SysConfigEntity对象", description = "参数配置表")
public class SysConfigEntity extends BaseEntity<SysConfigEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("参数主键")
@TableId(value = "config_id", type = IdType.AUTO)
private Integer configId;
@ApiModelProperty("配置名称")
@TableField("config_name")
private String configName;
@ApiModelProperty("配置键名")
@TableField("config_key")
private String configKey;
@ApiModelProperty("可选的选项")
@TableField("config_options")
private String configOptions;
@ApiModelProperty("配置值")
@TableField("config_value")
private String configValue;
@ApiModelProperty("是否允许修改")
@TableField("is_allow_change")
private Boolean isAllowChange;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.configId;
}
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.config.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 参数配置表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
public interface SysConfigMapper extends BaseMapper<SysConfigEntity> {
}
@@ -0,0 +1,23 @@
package com.agileboot.domain.system.config.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 参数配置表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
public interface SysConfigService extends IService<SysConfigEntity> {
/**
* 通过key获取配置
*
* @param key 配置对应的key
* @return 配置
*/
String getConfigValueByKey(String key);
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.system.config.db;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 参数配置表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfigEntity> implements
SysConfigService {
@Override
public String getConfigValueByKey(String key) {
if (StrUtil.isBlank(key)) {
return StrUtil.EMPTY;
}
QueryWrapper<SysConfigEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("config_key", key);
SysConfigEntity one = this.getOne(queryWrapper);
if (one == null || one.getConfigValue() == null) {
return StrUtil.EMPTY;
}
return one.getConfigValue();
}
}
@@ -0,0 +1,47 @@
package com.agileboot.domain.system.config.dto;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.enums.common.YesOrNoEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Date;
import java.util.List;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@Schema(name = "ConfigDTO", description = "配置信息")
public class ConfigDTO {
public ConfigDTO(SysConfigEntity entity) {
if (entity != null) {
configId = entity.getConfigId() + "";
configName = entity.getConfigName();
configKey = entity.getConfigKey();
configValue = entity.getConfigValue();
configOptions =
JSONUtil.isTypeJSONArray(entity.getConfigOptions()) ? JSONUtil.toList(entity.getConfigOptions(),
String.class) : ListUtil.empty();
isAllowChange = Convert.toInt(entity.getIsAllowChange());
isAllowChangeStr = BasicEnumUtil.getDescriptionByBool(YesOrNoEnum.class, entity.getIsAllowChange());
remark = entity.getRemark();
createTime = entity.getCreateTime();
}
}
private String configId;
private String configName;
private String configKey;
private String configValue;
private List<String> configOptions;
private Integer isAllowChange;
private String isAllowChangeStr;
private String remark;
private Date createTime;
}
@@ -0,0 +1,60 @@
package com.agileboot.domain.system.config.model;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.config.command.ConfigUpdateCommand;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ConfigModel extends SysConfigEntity {
private SysConfigService configService;
private Set<String> configOptionSet;
public ConfigModel(SysConfigService configService) {
this.configService = configService;
}
public ConfigModel(SysConfigEntity entity, SysConfigService configService) {
BeanUtil.copyProperties(entity, this);
List<String> options =
JSONUtil.isTypeJSONArray(entity.getConfigOptions()) ? JSONUtil.toList(entity.getConfigOptions(),
String.class) : ListUtil.empty();
this.configOptionSet = new HashSet<>(options);
this.configService = configService;
}
public void loadUpdateCommand(ConfigUpdateCommand updateCommand) {
this.setConfigValue(updateCommand.getConfigValue());
}
public void checkCanBeModify() {
if (StrUtil.isBlank(getConfigValue())) {
throw new ApiException(ErrorCode.Business.CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY);
}
if (!configOptionSet.isEmpty() && !configOptionSet.contains(getConfigValue())) {
throw new ApiException(ErrorCode.Business.CONFIG_VALUE_IS_NOT_IN_OPTIONS);
}
}
}
@@ -0,0 +1,33 @@
package com.agileboot.domain.system.config.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 配置模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class ConfigModelFactory {
private final SysConfigService configService;
public ConfigModel loadById(Long configId) {
SysConfigEntity byId = configService.getById(configId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, configId, "参数配置");
}
return new ConfigModel(byId, configService);
}
public ConfigModel create() {
return new ConfigModel(configService);
}
}
@@ -0,0 +1,42 @@
package com.agileboot.domain.system.config.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@Schema(name = "配置查询参数")
public class ConfigQuery extends AbstractPageQuery<SysConfigEntity> {
@Schema(description = "配置名称")
private String configName;
@Schema(description = "配置key")
private String configKey;
@Schema(description = "是否允许更改配置")
private Boolean isAllowChange;
@Override
public QueryWrapper<SysConfigEntity> addQueryCondition() {
QueryWrapper<SysConfigEntity> queryWrapper = new QueryWrapper<SysConfigEntity>()
.like(StrUtil.isNotEmpty(configName), "config_name", configName)
.eq(StrUtil.isNotEmpty(configKey), "config_key", configKey)
.eq(isAllowChange != null, "is_allow_change", isAllowChange);
this.timeRangeColumn = "create_time";
return queryWrapper;
}
}
@@ -0,0 +1,88 @@
package com.agileboot.domain.system.dept;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.domain.system.dept.dto.DeptDTO;
import com.agileboot.domain.system.dept.model.DeptModel;
import com.agileboot.domain.system.dept.model.DeptModelFactory;
import com.agileboot.domain.system.dept.query.DeptQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import com.agileboot.domain.system.role.db.SysRoleService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 部门服务
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class DeptApplicationService {
private final SysDeptService deptService;
private final SysRoleService roleService;
private final DeptModelFactory deptModelFactory;
public List<DeptDTO> getDeptList(DeptQuery query) {
List<SysDeptEntity> list = deptService.list(query.toQueryWrapper());
return list.stream().map(DeptDTO::new).collect(Collectors.toList());
}
public DeptDTO getDeptInfo(Long id) {
SysDeptEntity byId = deptService.getById(id);
return new DeptDTO(byId);
}
public List<Tree<Long>> getDeptTree() {
List<SysDeptEntity> list = deptService.list();
return TreeUtil.build(list, 0L, (dept, tree) -> {
tree.setId(dept.getDeptId());
tree.setParentId(dept.getParentId());
tree.putExtra("label", dept.getDeptName());
});
}
public void addDept(AddDeptCommand addCommand) {
DeptModel deptModel = deptModelFactory.create();
deptModel.loadAddCommand(addCommand);
deptModel.checkDeptNameUnique();
deptModel.generateAncestors();
deptModel.insert();
}
public void updateDept(UpdateDeptCommand updateCommand) {
DeptModel deptModel = deptModelFactory.loadById(updateCommand.getDeptId());
deptModel.loadUpdateCommand(updateCommand);
deptModel.checkDeptNameUnique();
deptModel.checkParentIdConflict();
deptModel.checkStatusAllowChange();
deptModel.generateAncestors();
deptModel.updateById();
}
public void removeDept(Long deptId) {
DeptModel deptModel = deptModelFactory.loadById(deptId);
deptModel.checkHasChildDept();
deptModel.checkDeptAssignedToUsers();
deptModel.deleteById();
}
}
@@ -0,0 +1,58 @@
package com.agileboot.domain.system.dept.command;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddDeptCommand {
/**
* 父部门ID
*/
@NotNull
@PositiveOrZero
private Long parentId;
/**
* 部门名称
*/
@NotBlank(message = "部门名称不能为空")
@Size(max = 30, message = "部门名称长度不能超过30个字符")
private String deptName;
/**
* 显示顺序
*/
@NotNull(message = "显示顺序不能为空")
private Integer orderNum;
/**
* 负责人
*/
private String leaderName;
/**
* 联系电话
*/
@Size(max = 11, message = "联系电话长度不能超过11个字符")
private String phone;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(max = 50, message = "邮箱长度不能超过50个字符")
private String email;
private Integer status;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.system.dept.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateDeptCommand extends AddDeptCommand {
@NotNull
@PositiveOrZero
private Long deptId;
}
@@ -0,0 +1,75 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 部门表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_dept")
@ApiModel(value = "SysDeptEntity对象", description = "部门表")
public class SysDeptEntity extends BaseEntity<SysDeptEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("部门id")
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
@ApiModelProperty("父部门id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty("祖级列表")
@TableField("ancestors")
private String ancestors;
@ApiModelProperty("部门名称")
@TableField("dept_name")
private String deptName;
@ApiModelProperty("显示顺序")
@TableField("order_num")
private Integer orderNum;
@TableField("leader_id")
private Long leaderId;
@ApiModelProperty("负责人")
@TableField("leader_name")
private String leaderName;
@ApiModelProperty("联系电话")
@TableField("phone")
private String phone;
@ApiModelProperty("邮箱")
@TableField("email")
private String email;
@ApiModelProperty("部门状态(0正常 1停用)")
@TableField("`status`")
private Integer status;
@Override
public Serializable pkVal() {
return this.deptId;
}
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 部门表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptMapper extends BaseMapper<SysDeptEntity> {
}
@@ -0,0 +1,50 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 部门表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptService extends IService<SysDeptEntity> {
/**
* 检测部门名称是否一致
*
* @param deptName 部门名称
* @param deptId 部门id
* @param parentId 父级部门id
* @return 校验结果
*/
boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId);
/**
* 检测部门底下是否还有正在使用中的子部门
* @param deptId 部门id
* @param enabled 部门是否开启
* @return 结果
*/
boolean hasChildrenDept(Long deptId, Boolean enabled);
/**
* 是否是目标部门的子部门
* @param parentId 目标部门id
* @param childId 子部门id
* @return 校验结果
*/
boolean isChildOfTheDept(Long parentId, Long childId);
/**
* 检测该部门是否已有用户使用
* @param deptId 部门id
* @return 校验结果
*/
boolean isDeptAssignedToUsers(Long deptId);
}
@@ -0,0 +1,62 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 部门表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDeptEntity> implements SysDeptService {
private final SysUserMapper userMapper;
@Override
public boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_name", deptName)
.ne(deptId != null, "dept_id", deptId)
.eq(parentId != null, "parent_id", parentId);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean hasChildrenDept(Long deptId, Boolean enabled) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(enabled != null, "status", 1)
.and(o -> o.eq("parent_id", deptId).or()
.apply("FIND_IN_SET (" + deptId + " , ancestors)")
);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isChildOfTheDept(Long parentId, Long childId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.apply("dept_id = '" + childId + "' and FIND_IN_SET ( " + parentId + " , ancestors)");
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isDeptAssignedToUsers(Long deptId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_id", deptId);
return userMapper.exists(queryWrapper);
}
}
@@ -0,0 +1,51 @@
package com.agileboot.domain.system.dept.dto;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class DeptDTO {
public DeptDTO(SysDeptEntity entity) {
if (entity != null) {
this.id = entity.getDeptId();
this.parentId = entity.getParentId();
this.deptName = entity.getDeptName();
this.orderNum = entity.getOrderNum();
this.leaderName = entity.getLeaderName();
this.email = entity.getEmail();
this.phone = entity.getPhone();
this.status = entity.getStatus();
this.createTime = entity.getCreateTime();
this.statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
}
}
private Long id;
private Long parentId;
private String deptName;
private Integer orderNum;
private String leaderName;
private String phone;
private String email;
private Integer status;
private String statusStr;
private Date createTime;
}
@@ -0,0 +1,106 @@
package com.agileboot.domain.system.dept.model;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
/**
* @author valarchie
*/
public class DeptModel extends SysDeptEntity {
private final SysDeptService deptService;
public DeptModel(SysDeptService deptService) {
this.deptService = deptService;
}
public DeptModel(SysDeptEntity entity, SysDeptService deptService) {
if (entity != null) {
// 如果大数据量的话 可以用MapStruct优化
BeanUtil.copyProperties(entity, this);
}
this.deptService = deptService;
}
public void loadAddCommand(AddDeptCommand addCommand) {
this.setParentId(addCommand.getParentId());
this.setDeptName(addCommand.getDeptName());
this.setOrderNum(addCommand.getOrderNum());
this.setLeaderName(addCommand.getLeaderName());
this.setPhone(addCommand.getPhone());
this.setEmail(addCommand.getEmail());
this.setStatus(addCommand.getStatus());
}
public void loadUpdateCommand(UpdateDeptCommand updateCommand) {
loadAddCommand(updateCommand);
setStatus(Convert.toInt(updateCommand.getStatus(), 0));
}
public void checkDeptNameUnique() {
if (deptService.isDeptNameDuplicated(getDeptName(), getDeptId(), getParentId())) {
throw new ApiException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE, getDeptName());
}
}
public void checkParentIdConflict() {
if (Objects.equals(getParentId(), getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF);
}
}
public void checkHasChildDept() {
if (deptService.hasChildrenDept(getDeptId(), null)) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE);
}
}
public void checkDeptAssignedToUsers() {
if (deptService.isDeptAssignedToUsers(getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE);
}
}
public void generateAncestors() {
// 处理 getParentId 可能为 null 的情况
if (getParentId() == null || getParentId() == 0) {
setAncestors(String.valueOf(getParentId() == null ? 0 : getParentId()));
return;
}
SysDeptEntity parentDept = deptService.getById(getParentId());
// 检查 parentDept 是否为 null 或者状态为禁用
if (parentDept == null || StatusEnum.DISABLE.equals(
BasicEnumUtil.fromValue(StatusEnum.class, parentDept.getStatus()))) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED);
}
// 处理 parentDept.getAncestors() 可能为 null 的情况
String ancestors = parentDept.getAncestors() == null ? "" : parentDept.getAncestors();
setAncestors(ancestors + "," + getParentId());
}
/**
* DDD 有些阻抗 如果为了追求性能的话 还是得通过 数据库的方式来判断
*/
public void checkStatusAllowChange() {
if (StatusEnum.DISABLE.getValue().equals(getStatus()) &&
deptService.hasChildrenDept(getDeptId(), true)) {
throw new ApiException(ErrorCode.Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE);
}
}
}
@@ -0,0 +1,45 @@
package com.agileboot.domain.system.dept.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 部门模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class DeptModelFactory {
private final SysDeptService deptService;
public DeptModel loadById(Long deptId) {
SysDeptEntity byId = deptService.getById(deptId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, deptId, "部门");
}
return new DeptModel(byId, deptService);
}
public DeptModel create() {
return new DeptModel(deptService);
}
public DeptModel loadFromAddCommand(AddDeptCommand addCommand, DeptModel model) {
model.setParentId(addCommand.getParentId());
model.setDeptName(addCommand.getDeptName());
model.setOrderNum(addCommand.getOrderNum());
model.setLeaderName(addCommand.getLeaderName());
model.setPhone(addCommand.getPhone());
model.setEmail(addCommand.getEmail());
return model;
}
}
@@ -0,0 +1,36 @@
package com.agileboot.domain.system.dept.query;
import com.agileboot.common.core.page.AbstractQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class DeptQuery extends AbstractQuery<SysDeptEntity> {
private Long deptId;
private Long parentId;
@Override
public QueryWrapper<SysDeptEntity> addQueryCondition() {
// TODO parentId 这个似乎没使用
return new QueryWrapper<SysDeptEntity>()
// .eq(status != null, "status", status)
.eq(parentId != null, "parent_id", parentId);
// .like(StrUtil.isNotEmpty(deptName), "dept_name", deptName);
// .and(deptId != null && isExcludeCurrentDept, o ->
// o.ne("dept_id", deptId)
// .or()
// .apply("FIND_IN_SET (dept_id , ancestors)")
// );
}
}
@@ -0,0 +1,54 @@
package com.agileboot.domain.system.log;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.log.dto.LoginLogDTO;
import com.agileboot.domain.system.log.query.LoginLogQuery;
import com.agileboot.domain.system.log.dto.OperationLogDTO;
import com.agileboot.domain.system.log.query.OperationLogQuery;
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import com.agileboot.domain.system.log.db.SysLoginInfoService;
import com.agileboot.domain.system.log.db.SysOperationLogService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class LogApplicationService {
// TODO 命名到时候统一改成叫LoginLog
private final SysLoginInfoService loginInfoService;
private final SysOperationLogService operationLogService;
public PageDTO<LoginLogDTO> getLoginInfoList(LoginLogQuery query) {
Page<SysLoginInfoEntity> page = loginInfoService.page(query.toPage(), query.toQueryWrapper());
List<LoginLogDTO> records = page.getRecords().stream().map(LoginLogDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public void deleteLoginInfo(BulkOperationCommand<Long> deleteCommand) {
QueryWrapper<SysLoginInfoEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.in("info_id", deleteCommand.getIds());
loginInfoService.remove(queryWrapper);
}
public PageDTO<OperationLogDTO> getOperationLogList(OperationLogQuery query) {
Page<SysOperationLogEntity> page = operationLogService.page(query.toPage(), query.toQueryWrapper());
List<OperationLogDTO> records = page.getRecords().stream().map(OperationLogDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public void deleteOperationLog(BulkOperationCommand<Long> deleteCommand) {
operationLogService.removeBatchByIds(deleteCommand.getIds());
}
}
@@ -0,0 +1,79 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 系统访问记录
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_login_info")
@ApiModel(value = "SysLoginInfoEntity对象", description = "系统访问记录")
public class SysLoginInfoEntity extends Model<SysLoginInfoEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("访问ID")
@TableId(value = "info_id", type = IdType.AUTO)
private Long infoId;
@ApiModelProperty("用户账号")
@TableField("username")
private String username;
@ApiModelProperty("登录IP地址")
@TableField("ip_address")
private String ipAddress;
@ApiModelProperty("登录地点")
@TableField("login_location")
private String loginLocation;
@ApiModelProperty("浏览器类型")
@TableField("browser")
private String browser;
@ApiModelProperty("操作系统")
@TableField("operation_system")
private String operationSystem;
@ApiModelProperty("登录状态(1成功 0失败)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("提示消息")
@TableField("msg")
private String msg;
@ApiModelProperty("访问时间")
@TableField("login_time")
private Date loginTime;
@ApiModelProperty("逻辑删除")
@TableField("deleted")
@TableLogic
private Boolean deleted;
@Override
public Serializable pkVal() {
return this.infoId;
}
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 系统访问记录 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-06
*/
public interface SysLoginInfoMapper extends BaseMapper<SysLoginInfoEntity> {
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 系统访问记录 服务类
* </p>
*
* @author valarchie
* @since 2022-06-06
*/
public interface SysLoginInfoService extends IService<SysLoginInfoEntity> {
}
@@ -0,0 +1,18 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 系统访问记录 服务实现类
* </p>
*
* @author valarchie
* @since 2022-07-10
*/
@Service
public class SysLoginInfoServiceImpl extends ServiceImpl<SysLoginInfoMapper, SysLoginInfoEntity> implements
SysLoginInfoService {
}
@@ -0,0 +1,115 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 操作日志记录
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_operation_log")
@ApiModel(value = "SysOperationLogEntity对象", description = "操作日志记录")
public class SysOperationLogEntity extends Model<SysOperationLogEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("日志主键")
@TableId(value = "operation_id", type = IdType.AUTO)
private Long operationId;
@ApiModelProperty("业务类型(0其它 1新增 2修改 3删除)")
@TableField("business_type")
private Integer businessType;
@ApiModelProperty("请求方式")
@TableField("request_method")
private Integer requestMethod;
@ApiModelProperty("请求模块")
@TableField("request_module")
private String requestModule;
@ApiModelProperty("请求URL")
@TableField("request_url")
private String requestUrl;
@ApiModelProperty("调用方法")
@TableField("called_method")
private String calledMethod;
@ApiModelProperty("操作类别(0其它 1后台用户 2手机端用户)")
@TableField("operator_type")
private Integer operatorType;
@ApiModelProperty("用户ID")
@TableField("user_id")
private Long userId;
@ApiModelProperty("操作人员")
@TableField("username")
private String username;
@ApiModelProperty("操作人员ip")
@TableField("operator_ip")
private String operatorIp;
@ApiModelProperty("操作地点")
@TableField("operator_location")
private String operatorLocation;
@ApiModelProperty("部门ID")
@TableField("dept_id")
private Long deptId;
@ApiModelProperty("部门名称")
@TableField("dept_name")
private String deptName;
@ApiModelProperty("请求参数")
@TableField("operation_param")
private String operationParam;
@ApiModelProperty("返回参数")
@TableField("operation_result")
private String operationResult;
@ApiModelProperty("操作状态(1正常 0异常)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("错误消息")
@TableField("error_stack")
private String errorStack;
@ApiModelProperty("操作时间")
@TableField("operation_time")
private Date operationTime;
@ApiModelProperty("逻辑删除")
@TableField("deleted")
@TableLogic
private Boolean deleted;
@Override
public Serializable pkVal() {
return this.operationId;
}
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 操作日志记录 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-08
*/
public interface SysOperationLogMapper extends BaseMapper<SysOperationLogEntity> {
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 操作日志记录 服务类
* </p>
*
* @author valarchie
* @since 2022-06-08
*/
public interface SysOperationLogService extends IService<SysOperationLogEntity> {
}
@@ -0,0 +1,18 @@
package com.agileboot.domain.system.log.db;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 操作日志记录 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-08
*/
@Service
public class SysOperationLogServiceImpl extends ServiceImpl<SysOperationLogMapper, SysOperationLogEntity> implements
SysOperationLogService {
}
@@ -0,0 +1,63 @@
package com.agileboot.domain.system.log.dto;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.annotation.ExcelSheet;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@ExcelSheet(name = "登录日志")
public class LoginLogDTO {
public LoginLogDTO(SysLoginInfoEntity entity) {
if (entity != null) {
logId = entity.getInfoId() + "";
username = entity.getUsername();
ipAddress = entity.getIpAddress();
loginLocation = entity.getLoginLocation();
operationSystem = entity.getOperationSystem();
browser = entity.getBrowser();
status = entity.getStatus();
statusStr = BasicEnumUtil.getDescriptionByValue(LoginStatusEnum.class, entity.getStatus());
msg = entity.getMsg();
loginTime = entity.getLoginTime();
}
}
@ExcelColumn(name = "ID")
private String logId;
@ExcelColumn(name = "用户名")
private String username;
@ExcelColumn(name = "ip地址")
private String ipAddress;
@ExcelColumn(name = "登录地点")
private String loginLocation;
@ExcelColumn(name = "操作系统")
private String operationSystem;
@ExcelColumn(name = "浏览器")
private String browser;
private Integer status;
@ExcelColumn(name = "状态")
private String statusStr;
@ExcelColumn(name = "描述")
private String msg;
@ExcelColumn(name = "登录时间")
private Date loginTime;
}
@@ -0,0 +1,95 @@
package com.agileboot.domain.system.log.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.annotation.ExcelSheet;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.common.enums.common.OperationStatusEnum;
import com.agileboot.common.enums.common.OperatorTypeEnum;
import com.agileboot.common.enums.common.RequestMethodEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@ExcelSheet(name = "操作日志")
public class OperationLogDTO {
public OperationLogDTO(SysOperationLogEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
this.requestMethod = BasicEnumUtil.getDescriptionByValue(RequestMethodEnum.class,
entity.getRequestMethod());
this.statusStr = BasicEnumUtil.getDescriptionByValue(OperationStatusEnum.class, entity.getStatus());
businessTypeStr = BasicEnumUtil.getDescriptionByValue(BusinessTypeEnum.class, entity.getBusinessType());
operatorTypeStr = BasicEnumUtil.getDescriptionByValue(OperatorTypeEnum.class, entity.getOperatorType());
}
}
@ExcelColumn(name = "ID")
private Long operationId;
private Integer businessType;
@ExcelColumn(name = "操作类型")
private String businessTypeStr;
@ExcelColumn(name = "操作类型")
private String requestMethod;
@ExcelColumn(name = "操作类型")
private String requestModule;
@ExcelColumn(name = "操作类型")
private String requestUrl;
@ExcelColumn(name = "操作类型")
private String calledMethod;
private Integer operatorType;
@ExcelColumn(name = "操作人类型")
private String operatorTypeStr;
@ExcelColumn(name = "用户ID")
private Long userId;
@ExcelColumn(name = "用户名")
private String username;
@ExcelColumn(name = "ip地址")
private String operatorIp;
@ExcelColumn(name = "ip地点")
private String operatorLocation;
@ExcelColumn(name = "部门ID")
private Long deptId;
@ExcelColumn(name = "部门")
private String deptName;
@ExcelColumn(name = "操作参数")
private String operationParam;
@ExcelColumn(name = "操作结果")
private String operationResult;
private Integer status;
@ExcelColumn(name = "状态")
private String statusStr;
@ExcelColumn(name = "错误堆栈")
private String errorStack;
@ExcelColumn(name = "操作时间")
private Date operationTime;
}
@@ -0,0 +1,37 @@
package com.agileboot.domain.system.log.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginLogQuery extends AbstractPageQuery<SysLoginInfoEntity> {
private String ipAddress;
private String status;
private String username;
@Override
public QueryWrapper<SysLoginInfoEntity> addQueryCondition() {
QueryWrapper<SysLoginInfoEntity> queryWrapper = new QueryWrapper<SysLoginInfoEntity>()
.like(StrUtil.isNotEmpty(ipAddress), "ip_address", ipAddress)
.eq(StrUtil.isNotEmpty(status), "status", status)
.like(StrUtil.isNotEmpty(username), "username", username);
addSortCondition(queryWrapper);
// 可以手动设置 也可以由前端传回
// this.timeRangeColumn = "login_time";
// addTimeCondition(queryWrapper, "login_time");
return queryWrapper;
}
}
@@ -0,0 +1,34 @@
package com.agileboot.domain.system.log.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class OperationLogQuery extends AbstractPageQuery<SysOperationLogEntity> {
private String businessType;
private String status;
private String username;
private String requestModule;
@Override
public QueryWrapper<SysOperationLogEntity> addQueryCondition() {
QueryWrapper<SysOperationLogEntity> queryWrapper = new QueryWrapper<SysOperationLogEntity>()
.like(businessType!=null, "business_type", businessType)
.eq(status != null, "status", status)
.like(StrUtil.isNotEmpty(username), "username", username)
.like(StrUtil.isNotEmpty(requestModule), "request_module", requestModule);
this.timeRangeColumn = "operation_time";
return queryWrapper;
}
}
@@ -0,0 +1,171 @@
package com.agileboot.domain.system.menu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.domain.system.menu.command.AddMenuCommand;
import com.agileboot.domain.system.menu.command.UpdateMenuCommand;
import com.agileboot.domain.system.menu.dto.MenuDTO;
import com.agileboot.domain.system.menu.dto.MenuDetailDTO;
import com.agileboot.domain.system.menu.dto.RouterDTO;
import com.agileboot.domain.system.menu.model.MenuModel;
import com.agileboot.domain.system.menu.model.MenuModelFactory;
import com.agileboot.domain.system.menu.query.MenuQuery;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.agileboot.domain.system.menu.db.SysMenuService;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 菜单应用服务
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class MenuApplicationService {
private final SysMenuService menuService;
private final MenuModelFactory menuModelFactory;
public List<MenuDTO> getMenuList(MenuQuery query) {
List<SysMenuEntity> list = menuService.list(query.toQueryWrapper());
return list.stream().map(MenuDTO::new)
.sorted(Comparator.comparing(MenuDTO::getRank, Comparator.nullsLast(Integer::compareTo)))
.collect(Collectors.toList());
}
public MenuDetailDTO getMenuInfo(Long menuId) {
SysMenuEntity byId = menuService.getById(menuId);
return new MenuDetailDTO(byId);
}
public List<Tree<Long>> getDropdownList(SystemLoginUser loginUser) {
List<SysMenuEntity> menuEntityList =
loginUser.isAdmin() ? menuService.list() : menuService.getMenuListByUserId(loginUser.getUserId());
return buildMenuTreeSelect(menuEntityList);
}
public void addMenu(AddMenuCommand addCommand) {
MenuModel model = menuModelFactory.create();
model.loadAddCommand(addCommand);
// TODO 只允许在页面类型上添加按钮
// 目前前端不支持嵌套的外链跳转
model.checkMenuNameUnique();
model.checkAddButtonInIframeOrOutLink();
model.checkAddMenuNotInCatalog();
model.checkExternalLink();
model.insert();
}
public void updateMenu(UpdateMenuCommand updateCommand) {
MenuModel model = menuModelFactory.loadById(updateCommand.getMenuId());
model.loadUpdateCommand(updateCommand);
model.checkMenuNameUnique();
model.checkAddButtonInIframeOrOutLink();
model.checkAddMenuNotInCatalog();
model.checkExternalLink();
model.checkParentIdConflict();
model.updateById();
}
public void remove(Long menuId) {
MenuModel menuModel = menuModelFactory.loadById(menuId);
menuModel.checkHasChildMenus();
menuModel.checkMenuAlreadyAssignToRole();
menuModel.deleteById();
}
/**
* 构建前端所需要树结构
*
* @param menus 菜单列表
* @return 树结构列表
*/
public List<Tree<Long>> buildMenuTreeSelect(List<SysMenuEntity> menus) {
TreeNodeConfig config = new TreeNodeConfig();
//默认为id可以不设置
config.setIdKey("menuId");
return TreeUtil.build(menus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值
tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId());
tree.putExtra("label", menu.getMenuName());
});
}
public List<Tree<Long>> buildMenuEntityTree(SystemLoginUser loginUser) {
List<SysMenuEntity> allMenus;
if (loginUser.isAdmin()) {
allMenus = menuService.list();
} else {
allMenus = menuService.getMenuListByUserId(loginUser.getUserId());
}
// 传给前端的路由排除掉按钮和停用的菜单
List<SysMenuEntity> noButtonMenus = allMenus.stream()
.filter(menu -> !menu.getIsButton())
.filter(menu-> StatusEnum.ENABLE.getValue().equals(menu.getStatus()))
.collect(Collectors.toList());
TreeNodeConfig config = new TreeNodeConfig();
// 默认为id 可以不设置
config.setIdKey("menuId");
return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值
tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId());
// TODO 可以取meta中的rank来排序
// tree.setWeight(menu.getRank());
tree.putExtra("entity", menu);
});
}
public List<RouterDTO> buildRouterTree(List<Tree<Long>> trees) {
List<RouterDTO> routers = new LinkedList<>();
if (CollUtil.isNotEmpty(trees)) {
for (Tree<Long> tree : trees) {
Object entity = tree.get("entity");
if (entity != null) {
RouterDTO routerDTO = new RouterDTO((SysMenuEntity) entity);
List<Tree<Long>> children = tree.getChildren();
if (CollUtil.isNotEmpty(children)) {
routerDTO.setChildren(buildRouterTree(children));
}
routers.add(routerDTO);
}
}
}
return routers;
}
public List<RouterDTO> getRouterTree(SystemLoginUser loginUser) {
List<Tree<Long>> trees = buildMenuEntityTree(loginUser);
return buildRouterTree(trees);
}
}
@@ -0,0 +1,37 @@
package com.agileboot.domain.system.menu.command;
import com.agileboot.domain.system.menu.dto.MetaDTO;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddMenuCommand {
private Long parentId;
@NotBlank(message = "菜单名称不能为空")
@Size(max = 50, message = "菜单名称长度不能超过50个字符")
private String menuName;
/**
* 路由名称 必须唯一
*/
private String routerName;
@Size(max = 200, message = "路由地址不能超过200个字符")
private String path;
private Integer status;
private Integer menuType;
private Boolean isButton;
@Size(max = 100, message = "权限标识长度不能超过100个字符")
private String permission;
private MetaDTO meta;
}
@@ -0,0 +1,17 @@
package com.agileboot.domain.system.menu.command;
import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateMenuCommand extends AddMenuCommand {
@NotNull
private Long menuId;
}
@@ -0,0 +1,80 @@
package com.agileboot.domain.system.menu.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 菜单权限表
* </p>
*
* @author valarchie
* @since 2023-07-21
*/
@Getter
@Setter
@TableName("sys_menu")
@ApiModel(value = "SysMenuEntity对象", description = "菜单权限表")
public class SysMenuEntity extends BaseEntity<SysMenuEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("菜单ID")
@TableId(value = "menu_id", type = IdType.AUTO)
private Long menuId;
@ApiModelProperty("菜单名称")
@TableField("menu_name")
private String menuName;
@ApiModelProperty("菜单的类型(1为普通菜单2为目录3为iFrame4为外部网站)")
@TableField("menu_type")
private Integer menuType;
@ApiModelProperty("路由名称(需保持和前端对应的vue文件中的name保持一致defineOptions方法中设置的name")
@TableField("router_name")
private String routerName;
@ApiModelProperty("父菜单ID")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty("组件路径(对应前端项目view文件夹中的路径)")
@TableField("path")
private String path;
@ApiModelProperty("是否按钮")
@TableField("is_button")
private Boolean isButton;
@ApiModelProperty("权限标识")
@TableField("permission")
private String permission;
@ApiModelProperty("路由元信息(前端根据这个信息进行逻辑处理)")
@TableField("meta_info")
private String metaInfo;
@ApiModelProperty("菜单状态(1启用 0停用)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.menuId;
}
}
@@ -0,0 +1,50 @@
package com.agileboot.domain.system.menu.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* <p>
* 菜单权限表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysMenuMapper extends BaseMapper<SysMenuEntity> {
/**
* 根据用户查询出所有菜单
*
* @param userId 用户id
* @return 菜单列表
*/
@Select("SELECT DISTINCT m.* "
+ "FROM sys_menu m "
+ " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
+ " LEFT JOIN sys_user u ON rm.role_id = u.role_id "
+ "WHERE u.user_id = #{userId} "
+ " AND m.status = 1 "
+ " AND m.deleted = 0 "
+ "ORDER BY m.parent_id")
List<SysMenuEntity> selectMenuListByUserId(@Param("userId")Long userId);
/**
* 根据角色ID查询菜单树信息
*
* @param roleId 角色ID
* @return 选中菜单列表
*/
@Select("SELECT DISTINCT m.menu_id "
+ "FROM sys_menu m "
+ " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
+ "WHERE rm.role_id = #{roleId} "
+ " AND m.deleted = 0 "
+ "GROUP BY m.menu_id ")
List<Long> selectMenuIdsByRoleId(@Param("roleId") Long roleId);
}
@@ -0,0 +1,59 @@
package com.agileboot.domain.system.menu.db;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 菜单权限表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysMenuService extends IService<SysMenuEntity> {
/**
* 根据用户查询系统菜单列表
*
* @param userId 用户ID
* @return 菜单列表
*/
List<SysMenuEntity> getMenuListByUserId(Long userId);
/**
* 根据角色ID查询菜单树信息
*
* @param roleId 角色ID
* @return 选中菜单列表
*/
List<Long> getMenuIdsByRoleId(Long roleId);
/**
* 校验菜单名称是否唯一
*
* @param menuName 菜单名称
* @param menuId 菜单id
* @param parentId 父级菜单id
* @return 校验结果
*/
boolean isMenuNameDuplicated(String menuName, Long menuId, Long parentId);
/**
* 是否存在菜单子节点
* @param menuId 菜单ID
* @return 结果 true 存在 false 不存在
*/
boolean hasChildrenMenu(Long menuId);
/**
* 查询菜单是否存在角色
* @param menuId 菜单ID
* @return 结果 true 存在 false 不存在
*/
boolean isMenuAssignToRoles(Long menuId);
}
@@ -0,0 +1,75 @@
package com.agileboot.domain.system.menu.db;
import com.agileboot.domain.system.role.db.SysRoleMenuEntity;
import com.agileboot.domain.system.role.db.SysRoleMenuMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 菜单权限表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenuEntity> implements SysMenuService {
private final SysRoleMenuMapper roleMenuMapper;
/**
* 根据角色ID查询菜单树信息
*
* @param roleId 角色ID
* @return 选中菜单列表
*/
@Override
public List<Long> getMenuIdsByRoleId(Long roleId) {
return this.baseMapper.selectMenuIdsByRoleId(roleId);
}
@Override
public boolean isMenuNameDuplicated(String menuName, Long menuId, Long parentId) {
QueryWrapper<SysMenuEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("menu_name", menuName)
.ne(menuId != null, "menu_id", menuId)
.eq(parentId != null, "parent_id", parentId);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean hasChildrenMenu(Long menuId) {
QueryWrapper<SysMenuEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", menuId);
return baseMapper.exists(queryWrapper);
}
/**
* 查询菜单使用数量
*
* @param menuId 菜单ID
* @return 结果
*/
@Override
public boolean isMenuAssignToRoles(Long menuId) {
QueryWrapper<SysRoleMenuEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("menu_id", menuId);
return roleMenuMapper.exists(queryWrapper);
}
@Override
public List<SysMenuEntity> getMenuListByUserId(Long userId) {
return baseMapper.selectMenuListByUserId(userId);
}
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.system.menu.dto;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class ExtraIconDTO {
// 是否是svg
private boolean svg;
// iconfont名称,目前只支持iconfont,后续拓展
private String name;
}
@@ -0,0 +1,76 @@
package com.agileboot.domain.system.menu.dto;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.utils.jackson.JacksonUtil;
import com.agileboot.common.enums.common.MenuTypeEnum;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class MenuDTO {
public MenuDTO(SysMenuEntity entity) {
if (entity != null) {
this.id = entity.getMenuId();
this.parentId = entity.getParentId();
this.menuName = entity.getMenuName();
this.routerName = entity.getRouterName();
this.path = entity.getPath();
this.status = entity.getStatus();
this.isButton = entity.getIsButton();
this.statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
if (!entity.getIsButton()) {
this.menuType = entity.getMenuType();
this.menuTypeStr = BasicEnumUtil.getDescriptionByValue(MenuTypeEnum.class, entity.getMenuType());
} else {
this.menuType = 0;
}
if (StrUtil.isNotEmpty(entity.getMetaInfo()) && JacksonUtil.isJson(entity.getMetaInfo())) {
MetaDTO meta = JacksonUtil.from(entity.getMetaInfo(), MetaDTO.class);
this.rank = meta.getRank();
this.icon = meta.getIcon();
}
this.createTime = entity.getCreateTime();
}
}
// 设置成id和parentId 便于前端处理树级结构
private Long id;
private Long parentId;
private String menuName;
private String routerName;
private String path;
private Integer rank;
private Integer menuType;
private String menuTypeStr;
private Boolean isButton;
private Integer status;
private String statusStr;
private Date createTime;
private String icon;
}
@@ -0,0 +1,30 @@
package com.agileboot.domain.system.menu.dto;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.utils.jackson.JacksonUtil;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class MenuDetailDTO extends MenuDTO {
public MenuDetailDTO(SysMenuEntity entity) {
super(entity);
if (entity == null) {
return;
}
if (StrUtil.isNotEmpty(entity.getMetaInfo()) && JacksonUtil.isJson(entity.getMetaInfo())) {
this.meta = JacksonUtil.from(entity.getMetaInfo(), MetaDTO.class);
}
this.permission = entity.getPermission();
}
private String permission;
private MetaDTO meta;
}
@@ -0,0 +1,60 @@
package com.agileboot.domain.system.menu.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 路由显示信息
* 必须加上@JsonInclude(Include.NON_NULL)的注解 否则传null值给Vue动态路由渲染时会出错
* @author valarchie
*/
@Data
@NoArgsConstructor
@JsonInclude(Include.NON_NULL)
public class MetaDTO {
// 菜单名称(兼容国际化、非国际化,如果用国际化的写法就必须在根目录的locales文件夹下对应添加)
private String title;
// 菜单图标
private String icon;
// 是否显示该菜单
private Boolean showLink;
// 是否显示父级菜单
private Boolean showParent;
// 页面级别权限设置
private List<String> roles;
// 按钮级别权限设置
private List<String> auths;
// 需要内嵌的iframe链接地址
private String frameSrc;
/**
* 是否是内部页面 使用frameSrc来嵌入页面时,当isFrameSrcInternal=true的时候, 前端需要做特殊处理
* 比如链接是 /druid/login.html
* 前端需要处理成 http://localhost:8080/druid/login.html
*/
private Boolean isFrameSrcInternal;
/**
* 菜单排序,值越高排的越后(只针对顶级路由)
*/
private Integer rank;
// ========= 目前系统仅支持以上这些参数的设置 后续有需要的话开发者可自行设置的这些参数 ===========
// 菜单名称右侧的额外图标
private ExtraIconDTO extraIcon;
// 是否缓存该路由页面(开启后,会保存该页面的整体状态,刷新后会清空状态)
private Boolean keepAlive;
// 内嵌的iframe页面是否开启首次加载动画
private Boolean frameLoading;
// 页面加载动画(两种模式,第一种直接采用vue内置的transitions动画,第二种是使用animate.css编写进、离场动画,平台更推荐使用第二种模式,已经内置了animate.css,直接写对应的动画名即可)
private TransitionDTO transition;
// 当前菜单名称或自定义信息禁止添加到标签页
private Boolean hiddenTag;
// 显示在标签页的最大数量,需满足后面的条件:不显示在菜单中的路由并且是通过query或params传参模式打开的页面。在完整版全局搜dynamicLevel即可查看代码演示
private Integer dynamicLevel;
}
@@ -0,0 +1,77 @@
package com.agileboot.domain.system.menu.dto;
import com.agileboot.common.utils.jackson.JacksonUtil;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.google.common.collect.Lists;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 动态路由信息
* 必须加上@JsonInclude(Include.NON_NULL)的注解 否则传null值给Vue动态路由渲染时会出错
* @author valarchie
*/
@JsonInclude(Include.NON_NULL)
@Data
@NoArgsConstructor
public class RouterDTO {
public RouterDTO(SysMenuEntity entity) {
if (entity != null) {
this.name = entity.getRouterName();
this.path = entity.getPath();
// 暂时不需要component
// this.component = entity.getComponent();
// this.rank = entity.getRank();
// this.redirect = entity.getRedirect();
if (JacksonUtil.isJson(entity.getMetaInfo())) {
this.meta = JacksonUtil.from(entity.getMetaInfo(), MetaDTO.class);
} else {
this.meta = new MetaDTO();
}
this.meta.setAuths(Lists.newArrayList(entity.getPermission()));
}
}
/**
* 路由名字 根据前端的要求 必须唯一
* 并按照前端项目的推荐 这个Name最好和组件的Name一样 使用菜单表中的router_name
* TODO 这里后端需要加校验
*/
private String name;
/**
* 路由路径地址
*/
private String path;
/**
* 路由重定向
*/
private String redirect;
/**
* 组件地址
*/
private String component;
/**
* 一级菜单排序值(排序仅支持一级菜单)
*/
private Integer rank;
/**
* 其他元素
*/
private MetaDTO meta;
/**
* 子路由
*/
private List<RouterDTO> children;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.system.menu.dto;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class TransitionDTO {
// 当前页面动画,这里是第一种模式,比如 name: "fade" 更具体看后面链接
// https://cn.vuejs.org/api/built-in-components.html#transition
private String name;
// 当前页面进场动画,这里是第二种模式,比如 enterTransition: "animate__fadeInLeft"
private String enterTransition;
// 当前页面离场动画,这里是第二种模式,比如 leaveTransition: "animate__fadeOutRight"
private String leaveTransition;
}
@@ -0,0 +1,116 @@
package com.agileboot.domain.system.menu.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.common.utils.jackson.JacksonUtil;
import com.agileboot.domain.system.menu.command.AddMenuCommand;
import com.agileboot.domain.system.menu.command.UpdateMenuCommand;
import com.agileboot.common.enums.common.MenuTypeEnum;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.agileboot.domain.system.menu.db.SysMenuService;
import java.util.Objects;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@NoArgsConstructor
public class MenuModel extends SysMenuEntity {
private SysMenuService menuService;
public MenuModel(SysMenuService menuService) {
this.menuService = menuService;
}
public MenuModel(SysMenuEntity entity, SysMenuService menuService) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
this.menuService = menuService;
}
public void loadAddCommand(AddMenuCommand command) {
if (command != null) {
BeanUtil.copyProperties(command, this, "menuId");
String metaInfo = JacksonUtil.to(command.getMeta());
this.setMetaInfo(metaInfo);
}
}
public void loadUpdateCommand(UpdateMenuCommand command) {
if (command != null) {
if (!Objects.equals(this.getMenuType(), command.getMenuType()) && !this.getIsButton()) {
throw new ApiException(Business.MENU_CAN_NOT_CHANGE_MENU_TYPE);
}
loadAddCommand(command);
}
}
public void checkMenuNameUnique() {
if (menuService.isMenuNameDuplicated(getMenuName(), getMenuId(), getParentId())) {
throw new ApiException(ErrorCode.Business.MENU_NAME_IS_NOT_UNIQUE);
}
}
/**
* Iframe和外链跳转类型 不允许添加按钮
*/
public void checkAddButtonInIframeOrOutLink() {
SysMenuEntity parentMenu = menuService.getById(getParentId());
if (parentMenu != null && getIsButton() && (
Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.IFRAME.getValue())
|| Objects.equals(parentMenu.getMenuType(),MenuTypeEnum.OUTSIDE_LINK_REDIRECT.getValue())
)) {
throw new ApiException(Business.MENU_NOT_ALLOWED_TO_CREATE_BUTTON_ON_IFRAME_OR_OUT_LINK);
}
}
/**
* 只允许在目录菜单类型底下 添加子菜单
*/
public void checkAddMenuNotInCatalog() {
SysMenuEntity parentMenu = menuService.getById(getParentId());
if (parentMenu != null && !getIsButton() && (
!Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.CATALOG.getValue())
)) {
throw new ApiException(Business.MENU_ONLY_ALLOWED_TO_CREATE_SUB_MENU_IN_CATALOG);
}
}
public void checkExternalLink() {
// if (getIsExternal() && !HttpUtil.isHttp(getPath()) && !HttpUtil.isHttps(getPath())) {
// throw new ApiException(ErrorCode.Business.MENU_EXTERNAL_LINK_MUST_BE_HTTP);
// }
}
public void checkParentIdConflict() {
if (getMenuId().equals(getParentId())) {
throw new ApiException(ErrorCode.Business.MENU_PARENT_ID_NOT_ALLOW_SELF);
}
}
public void checkHasChildMenus() {
if (menuService.hasChildrenMenu(getMenuId())) {
throw new ApiException(ErrorCode.Business.MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE);
}
}
public void checkMenuAlreadyAssignToRole() {
if (menuService.isMenuAssignToRoles(getMenuId())) {
throw new ApiException(ErrorCode.Business.MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE);
}
}
}
@@ -0,0 +1,33 @@
package com.agileboot.domain.system.menu.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.agileboot.domain.system.menu.db.SysMenuService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 菜单模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class MenuModelFactory {
private final SysMenuService menuService;
public MenuModel loadById(Long menuId) {
SysMenuEntity byId = menuService.getById(menuId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, menuId, "菜单");
}
return new MenuModel(byId, menuService);
}
public MenuModel create() {
return new MenuModel(menuService);
}
}
@@ -0,0 +1,205 @@
package com.agileboot.domain.system.menu.model;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
/**
* @author valarchie
*/
public class RouterModel extends SysMenuEntity {
// public RouterDTO produceMultipleLevelMenuRouterVO(List<RouterDTO> children) {
// RouterDTO router = produceDefaultRouterVO();
//
// if (CollUtil.isNotEmpty(children) && Objects.equals(MenuTypeEnum.DIRECTORY.getValue(), getMenuType())) {
// router.setAlwaysShow(true);
// router.setRedirect("noRedirect");
// router.setChildren(children);
// }
//
// return router;
// }
//
//
// public RouterDTO produceSingleLevelMenuRouterVO() {
// RouterDTO router = produceDefaultRouterVO();
//
// router.setMeta(null);
// List<RouterDTO> childrenList = new ArrayList<>();
// RouterDTO children = new RouterDTO();
// children.setPath(getPath());
// children.setComponent(getComponent());
// children.setName(StrUtil.upperFirst(getPath()));
// children.setMeta(new MetaDTO(getMenuName(), getIcon(), !getIsCache(), getPath()));
// children.setQuery(getQuery());
// childrenList.add(children);
// router.setChildren(childrenList);
//
// return router;
// }
//
//
// public RouterDTO produceInnerLinkRouterVO() {
//
// RouterDTO router = produceDefaultRouterVO();
//
// router.setMeta(new MetaDTO(getMenuName(), getIcon()));
// router.setPath("/");
// List<RouterDTO> childrenList = new ArrayList<>();
// RouterDTO children = new RouterDTO();
// String routerPath = trimHttpPrefixForPath(getPath());
// children.setPath(routerPath);
// children.setComponent(MenuComponentEnum.INNER_LINK.description());
// children.setName(StrUtil.upperFirst(routerPath));
// children.setMeta(new MetaDTO(getMenuName(), getIcon(), getPath()));
// childrenList.add(children);
// router.setChildren(childrenList);
//
// return router;
// }
//
// public RouterDTO produceDefaultRouterVO() {
// RouterDTO router = new RouterDTO();
// router.setHidden(!getIsVisible());
// router.setName(calculateRouteName());
// router.setPath(calculateRouterPath());
// router.setComponent(calculateComponentType());
// router.setQuery(getQuery());
// router.setMeta(new MetaDTO(getMenuName(), getIcon(), !getIsCache(), getPath()));
// return router;
// }
//
//
// /**
// * 获取路由名称
// * @return 路由名称
// */
// public String calculateRouteName() {
// String routerName = StrUtil.upperFirst(getPath());
// // 非外链并且是一级目录(类型为目录)
// if (isSingleLevelMenu()) {
// routerName = StrUtil.EMPTY;
// }
// return routerName;
// }
//
//
// /**
// * 是否为单个一级菜单
// *
// * @return 结果
// */
// public boolean isSingleLevelMenu() {
// return isTopLevel() && MenuTypeEnum.MENU.getValue().equals(getMenuType()) && !getIsExternal();
// }
//
// /**
// * 是否为顶级内部链接菜单
// *
// * @return 结果
// */
// public boolean isTopInnerLink() {
// return isTopLevel() && isInnerLink();
// }
//
//
// /**
// * 是否为多级菜单
// *
// * @return 结果
// */
// public boolean isMultipleLevelMenu(Tree<Long> tree) {
// return MenuTypeEnum.DIRECTORY.getValue().equals(getMenuType()) && tree.hasChild();
// }
//
//
// /**
// * 获取路由地址
// * @return 路由地址
// */
// public String calculateRouterPath() {
// String routerPath = getPath();
// // 内链打开外网方式
// if (!isTopLevel() && isInnerLink()) {
// routerPath = trimHttpPrefixForPath(routerPath);
// }
// // 非外链并且是一级目录(类型为目录)
// if (isTopLevel() && Objects.equals(MenuTypeEnum.DIRECTORY.getValue(), getMenuType()) && !getIsExternal()) {
// routerPath = "/" + getPath();
// // 非外链并且是一级目录(类型为菜单)
// } else if (isSingleLevelMenu()) {
// routerPath = "/";
// }
// return routerPath;
// }
//
// /**
// * 是否为内链组件
// *
// * @return 结果
// */
// public boolean isInnerLink() {
// return !getIsExternal() && (HttpUtil.isHttp(getPath()) || HttpUtil.isHttps(getPath()));
// }
//
// /**
// * 是否顶层目录或者菜单
// *
// * @return 结果
// */
// public boolean isTopLevel() {
// return Objects.equals(getParentId(), 0L);
// }
//
//
// /**
// * 内链域名特殊字符替换
// */
// public String trimHttpPrefixForPath(String path) {
// if (HttpUtil.isHttp(path)) {
// return StrUtil.stripIgnoreCase(path, Constants.HTTP, "");
// }
// if (HttpUtil.isHttps(path)) {
// return StrUtil.stripIgnoreCase(path, Constants.HTTPS, "");
// }
// return path;
// }
//
// /**
// * 获取组件信息
// *
// * @return 组件信息
// */
// public String calculateComponentType() {
// String component = MenuComponentEnum.LAYOUT.description();
// if (StrUtil.isNotEmpty(getComponent()) && !isSingleLevelMenu()) {
// component = getComponent();
// } else if (isInnerLinkView()) {
// component = MenuComponentEnum.INNER_LINK.description();
// } else if (isParentView()) {
// component = MenuComponentEnum.PARENT_VIEW.description();
// }
// return component;
// }
//
// /**
// * 是否为inner_link_view组件
// *
// * @return 结果
// */
// public boolean isInnerLinkView() {
// return StrUtil.isEmpty(getComponent()) && !isTopLevel() && isInnerLink();
// }
//
//
// /**
// * 是否为parent_view组件
// *
// * @return 结果
// */
// public boolean isParentView() {
// return StrUtil.isEmpty(getComponent()) && !isTopLevel() &&
// MenuTypeEnum.DIRECTORY.getValue().equals(getMenuType());
// }
}
@@ -0,0 +1,32 @@
package com.agileboot.domain.system.menu.query;
import com.agileboot.common.core.page.AbstractQuery;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class MenuQuery extends AbstractQuery<SysMenuEntity> {
// 直接交给前端筛选
// private String menuName;
// private Boolean isVisible;
// private Integer status;
private Boolean isButton;
@Override
public QueryWrapper<SysMenuEntity> addQueryCondition() {
QueryWrapper<SysMenuEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(isButton != null, "is_button", isButton);
// .like(StrUtil.isNotEmpty(menuName), "menu_name", menuName)
// .eq(isVisible != null, "is_visible", isVisible)
// .eq(status != null, "status", status);
this.orderColumn = "parent_id";
this.orderDirection = "descending";
return queryWrapper;
}
}
@@ -0,0 +1,87 @@
package com.agileboot.domain.system.monitor;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Internal;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.monitor.dto.OnlineUserDTO;
import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO;
import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO.CommandStatusDTO;
import com.agileboot.domain.system.monitor.dto.ServerInfo;
import com.agileboot.infrastructure.cache.redis.CacheKeyEnum;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class MonitorApplicationService {
private final RedisTemplate<String, ?> redisTemplate;
public RedisCacheInfoDTO getRedisCacheInfo() {
Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) RedisServerCommands::info);
Properties commandStats = (Properties) redisTemplate.execute(
(RedisCallback<Object>) connection -> connection.info("commandstats"));
Long dbSize = redisTemplate.execute(RedisServerCommands::dbSize);
if (commandStats == null || info == null) {
throw new ApiException(Internal.INTERNAL_ERROR, "获取Redis监控信息失败。");
}
RedisCacheInfoDTO cacheInfo = new RedisCacheInfoDTO();
cacheInfo.setInfo(info);
cacheInfo.setDbSize(dbSize);
cacheInfo.setCommandStats(new ArrayList<>());
commandStats.stringPropertyNames().forEach(key -> {
String property = commandStats.getProperty(key);
CommandStatusDTO commonStatus = new CommandStatusDTO();
commonStatus.setName(StrUtil.removePrefix(key, "cmdstat_"));
commonStatus.setValue(StrUtil.subBetween(property, "calls=", ",usec"));
cacheInfo.getCommandStats().add(commonStatus);
});
return cacheInfo;
}
public List<OnlineUserDTO> getOnlineUserList(String username, String ipAddress) {
Collection<String> keys = redisTemplate.keys(CacheKeyEnum.LOGIN_USER_KEY.key() + "*");
Stream<OnlineUserDTO> onlineUserStream = keys.stream().map(o ->
CacheCenter.loginUserCache.getObjectOnlyInCacheByKey(o))
.filter(Objects::nonNull).map(OnlineUserDTO::new);
List<OnlineUserDTO> filteredOnlineUsers = onlineUserStream
.filter(o ->
StrUtil.isEmpty(username) || username.equals(o.getUsername())
).filter( o ->
StrUtil.isEmpty(ipAddress) || ipAddress.equals(o.getIpAddress())
).collect(Collectors.toList());
Collections.reverse(filteredOnlineUsers);
return filteredOnlineUsers;
}
public ServerInfo getServerInfo() {
return ServerInfo.fillInfo();
}
}
@@ -0,0 +1,64 @@
package com.agileboot.domain.system.monitor.dto;
import cn.hutool.core.util.NumberUtil;
import lombok.Data;
/**
* CPU相关信息
*
* @author ruoyi
*/
@Data
public class CpuInfo {
/**
* 核心数
*/
private int cpuNum;
/**
* CPU总的使用率
*/
private double total;
/**
* CPU系统使用率
*/
private double sys;
/**
* CPU用户使用率
*/
private double used;
/**
* CPU当前等待率
*/
private double wait;
/**
* CPU当前空闲率
*/
private double free;
public double getTotal() {
return NumberUtil.round(total * 100, 2).doubleValue();
}
public double getSys() {
return NumberUtil.div(sys * 100, total, 2);
}
public double getUsed() {
return NumberUtil.div(used * 100, total, 2);
}
public double getWait() {
return NumberUtil.div(wait * 100, total, 2);
}
public double getFree() {
return NumberUtil.div(free * 100, total, 2);
}
}
@@ -0,0 +1,48 @@
package com.agileboot.domain.system.monitor.dto;
import lombok.Data;
/**
* 系统文件相关信息
*
* @author ruoyi
*/
@Data
public class DiskInfo {
/**
* 盘符路径
*/
private String dirName;
/**
* 盘符类型
*/
private String sysTypeName;
/**
* 文件类型
*/
private String typeName;
/**
* 总大小
*/
private String total;
/**
* 剩余大小
*/
private String free;
/**
* 已经使用量
*/
private String used;
/**
* 资源的使用率
*/
private double usage;
}
@@ -0,0 +1,92 @@
package com.agileboot.domain.system.monitor.dto;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import com.agileboot.common.constant.Constants;
import java.lang.management.ManagementFactory;
import lombok.Data;
/**
* JVM相关信息
*
* @author ruoyi
*/
@Data
public class JvmInfo {
/**
* 当前JVM占用的内存总数(M)
*/
private double total;
/**
* JVM最大可用内存总数(M)
*/
private double max;
/**
* JVM空闲内存(M)
*/
private double free;
/**
* JDK版本
*/
private String version;
/**
* JDK路径
*/
private String home;
public double getTotal() {
return NumberUtil.div(total, Constants.MB, 2);
}
public double getMax() {
return NumberUtil.div(max, Constants.MB, 2);
}
public double getFree() {
return NumberUtil.div(free, Constants.MB, 2);
}
public double getUsed() {
return NumberUtil.div(total - free, Constants.MB, 2);
}
public double getUsage() {
return NumberUtil.div((total - free) * 100, total, 2);
}
/**
* 获取JDK名称
*/
public String getName() {
return ManagementFactory.getRuntimeMXBean().getVmName();
}
/**
* JDK启动时间
*/
public String getStartTime() {
return DateUtil.format(DateUtil.date(ManagementFactory.getRuntimeMXBean().getStartTime()),
DatePattern.NORM_DATETIME_PATTERN);
}
/**
* JDK运行时间
*/
public String getRunTime() {
return DateUtil.formatBetween(DateUtil.date(ManagementFactory.getRuntimeMXBean().getStartTime()),
DateUtil.date());
}
/**
* 运行参数
*/
public String getInputArgs() {
return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
}
}
@@ -0,0 +1,45 @@
package com.agileboot.domain.system.monitor.dto;
import cn.hutool.core.util.NumberUtil;
import com.agileboot.common.constant.Constants;
import lombok.Data;
/**
* 內存相关信息
*
* @author valarchie
*/
@Data
public class MemoryInfo {
/**
* 内存总量
*/
private double total;
/**
* 已用内存
*/
private double used;
/**
* 剩余内存
*/
private double free;
public double getTotal() {
return NumberUtil.div(total, Constants.GB, 2);
}
public double getUsed() {
return NumberUtil.div(used, Constants.GB, 2);
}
public double getFree() {
return NumberUtil.div(free, Constants.GB, 2);
}
public double getUsage() {
return NumberUtil.div(used * 100, total, 2);
}
}
@@ -0,0 +1,77 @@
package com.agileboot.domain.system.monitor.dto;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import lombok.Data;
/**
* 当前在线会话
*
* @author ruoyi
*/
@Data
public class OnlineUserDTO {
/**
* 会话编号
*/
private String tokenId;
/**
* 部门名称
*/
private String deptName;
/**
* 用户名称
*/
private String username;
/**
* 登录IP地址
*/
private String ipAddress;
/**
* 登录地址
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String operationSystem;
/**
* 登录时间
*/
private Long loginTime;
public OnlineUserDTO(SystemLoginUser user) {
if (user == null) {
return;
}
this.setTokenId(user.getCachedKey());
this.tokenId = user.getCachedKey();
this.username = user.getUsername();
this.ipAddress = user.getLoginInfo().getIpAddress();
this.loginLocation = user.getLoginInfo().getLocation();
this.browser = user.getLoginInfo().getBrowser();
this.operationSystem = user.getLoginInfo().getOperationSystem();
this.loginTime = user.getLoginInfo().getLoginTime();
SysDeptEntity deptEntity = CacheCenter.deptCache.get(user.getDeptId() + "");
if (deptEntity != null) {
this.deptName = deptEntity.getDeptName();
}
}
}
@@ -0,0 +1,24 @@
package com.agileboot.domain.system.monitor.dto;
import java.util.List;
import java.util.Properties;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class RedisCacheInfoDTO {
private Properties info;
private Long dbSize;
private List<CommandStatusDTO> commandStats;
@Data
public static class CommandStatusDTO {
private String name;
private String value;
}
}
@@ -0,0 +1,175 @@
package com.agileboot.domain.system.monitor.dto;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.NumberUtil;
import com.agileboot.common.constant.Constants;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import lombok.Data;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.TickType;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
/**
* 服务器相关信息
*
* @author ruoyi
* @author valarchie
*/
@Data
public class ServerInfo {
private static final int OSHI_WAIT_SECOND = 1000;
/**
* CPU相关信息
*/
private CpuInfo cpuInfo = new CpuInfo();
/**
* 內存相关信息
*/
private MemoryInfo memoryInfo = new MemoryInfo();
/**
* JVM相关信息
*/
private JvmInfo jvmInfo = new JvmInfo();
/**
* 服务器相关信息
*/
private SystemInfo systemInfo = new SystemInfo();
/**
* 磁盘相关信息
*/
private List<DiskInfo> diskInfos = new LinkedList<>();
public static ServerInfo fillInfo() {
ServerInfo serverInfo = new ServerInfo();
oshi.SystemInfo si = new oshi.SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
serverInfo.fillCpuInfo(hal.getProcessor());
serverInfo.fillMemoryInfo(hal.getMemory());
serverInfo.fillSystemInfo();
serverInfo.fillJvmInfo();
serverInfo.fillDiskInfos(si.getOperatingSystem());
return serverInfo;
}
/**
* 设置CPU信息
*/
private void fillCpuInfo(CentralProcessor processor) {
// CPU信息
long[] prevTicks = processor.getSystemCpuLoadTicks();
Util.sleep(OSHI_WAIT_SECOND);
long[] ticks = processor.getSystemCpuLoadTicks();
long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
long softIrq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
long ioWait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
long totalCpu = user + nice + cSys + idle + ioWait + irq + softIrq + steal;
cpuInfo.setCpuNum(processor.getLogicalProcessorCount());
cpuInfo.setTotal(totalCpu);
cpuInfo.setSys(cSys);
cpuInfo.setUsed(user);
cpuInfo.setWait(ioWait);
cpuInfo.setFree(idle);
}
/**
* 设置内存信息
*/
private void fillMemoryInfo(GlobalMemory memory) {
memoryInfo.setTotal(memory.getTotal());
memoryInfo.setUsed(memory.getTotal() - memory.getAvailable());
memoryInfo.setFree(memory.getAvailable());
}
/**
* 设置服务器信息
*/
private void fillSystemInfo() {
Properties props = System.getProperties();
systemInfo.setComputerName(NetUtil.getLocalHostName());
systemInfo.setComputerIp(NetUtil.getLocalhost().getHostAddress());
systemInfo.setOsName(props.getProperty("os.name"));
systemInfo.setOsArch(props.getProperty("os.arch"));
systemInfo.setUserDir(props.getProperty("user.dir"));
}
/**
* 设置Java虚拟机
*/
private void fillJvmInfo() {
Properties props = System.getProperties();
jvmInfo.setTotal(Runtime.getRuntime().totalMemory());
jvmInfo.setMax(Runtime.getRuntime().maxMemory());
jvmInfo.setFree(Runtime.getRuntime().freeMemory());
jvmInfo.setVersion(props.getProperty("java.version"));
jvmInfo.setHome(props.getProperty("java.home"));
}
/**
* 设置磁盘信息
*/
private void fillDiskInfos(OperatingSystem os) {
FileSystem fileSystem = os.getFileSystem();
List<OSFileStore> fsArray = fileSystem.getFileStores();
for (OSFileStore fs : fsArray) {
long free = fs.getUsableSpace();
long total = fs.getTotalSpace();
long used = total - free;
DiskInfo diskInfo = new DiskInfo();
diskInfo.setDirName(fs.getMount());
diskInfo.setSysTypeName(fs.getType());
diskInfo.setTypeName(fs.getName());
diskInfo.setTotal(convertFileSize(total));
diskInfo.setFree(convertFileSize(free));
diskInfo.setUsed(convertFileSize(used));
if (total != 0){
diskInfo.setUsage(NumberUtil.div(used * 100, total, 4));
} else {
//Windows下如果有光驱(可能是虚拟光驱),total为0,不能做除数
diskInfo.setUsage(0);
}
diskInfos.add(diskInfo);
}
}
/**
* 字节转换
*
* @param size 字节大小
* @return 转换后值
*/
public String convertFileSize(long size) {
float castedSize = (float) size;
if (size >= Constants.GB) {
return String.format("%.1f GB", castedSize / Constants.GB);
}
if (size >= Constants.MB) {
return String.format("%.1f MB", castedSize / Constants.MB);
}
return String.format("%.1f KB", castedSize / Constants.KB);
}
}
@@ -0,0 +1,38 @@
package com.agileboot.domain.system.monitor.dto;
import lombok.Data;
/**
* 系统相关信息
*
* @author ruoyi
*/
@Data
public class SystemInfo {
/**
* 服务器名称
*/
private String computerName;
/**
* 服务器Ip
*/
private String computerIp;
/**
* 项目路径
*/
private String userDir;
/**
* 操作系统
*/
private String osName;
/**
* 系统架构
*/
private String osArch;
}
@@ -0,0 +1,69 @@
package com.agileboot.domain.system.notice;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.notice.command.NoticeAddCommand;
import com.agileboot.domain.system.notice.command.NoticeUpdateCommand;
import com.agileboot.domain.system.notice.dto.NoticeDTO;
import com.agileboot.domain.system.notice.model.NoticeModel;
import com.agileboot.domain.system.notice.model.NoticeModelFactory;
import com.agileboot.domain.system.notice.query.NoticeQuery;
import com.agileboot.domain.system.notice.db.SysNoticeEntity;
import com.agileboot.domain.system.notice.db.SysNoticeService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class NoticeApplicationService {
private final SysNoticeService noticeService;
private final NoticeModelFactory noticeModelFactory;
public PageDTO<NoticeDTO> getNoticeList(NoticeQuery query) {
Page<SysNoticeEntity> page = noticeService.getNoticeList(query.toPage(), query.toQueryWrapper());
List<NoticeDTO> records = page.getRecords().stream().map(NoticeDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public NoticeDTO getNoticeInfo(Long id) {
NoticeModel noticeModel = noticeModelFactory.loadById(id);
return new NoticeDTO(noticeModel);
}
public void addNotice(NoticeAddCommand addCommand) {
NoticeModel noticeModel = noticeModelFactory.create();
noticeModel.loadAddCommand(addCommand);
noticeModel.checkFields();
noticeModel.insert();
}
public void updateNotice(NoticeUpdateCommand updateCommand) {
NoticeModel noticeModel = noticeModelFactory.loadById(updateCommand.getNoticeId());
noticeModel.loadUpdateCommand(updateCommand);
noticeModel.checkFields();
noticeModel.updateById();
}
public void deleteNotice(BulkOperationCommand<Integer> deleteCommand) {
noticeService.removeBatchByIds(deleteCommand.getIds());
}
}
@@ -0,0 +1,30 @@
package com.agileboot.domain.system.notice.command;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class NoticeAddCommand {
@NotBlank(message = "公告标题不能为空")
@Size(max = 50, message = "公告标题不能超过50个字符")
protected String noticeTitle;
protected String noticeType;
/**
* 想要支持富文本的话, 避免Xss过滤的话, 请加上@JsonDeserialize(using = StringDeserializer.class) 注解
*/
@NotBlank
@JsonDeserialize(using = StringDeserializer.class)
protected String noticeContent;
protected String status;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.system.notice.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class NoticeUpdateCommand extends NoticeAddCommand {
@NotNull
@Positive
protected Long noticeId;
}
@@ -0,0 +1,60 @@
package com.agileboot.domain.system.notice.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 通知公告表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_notice")
@ApiModel(value = "SysNoticeEntity对象", description = "通知公告表")
public class SysNoticeEntity extends BaseEntity<SysNoticeEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("公告ID")
@TableId(value = "notice_id", type = IdType.AUTO)
private Integer noticeId;
@ApiModelProperty("公告标题")
@TableField("notice_title")
private String noticeTitle;
@ApiModelProperty("公告类型(1通知 2公告)")
@TableField("notice_type")
private Integer noticeType;
@ApiModelProperty("公告内容")
@TableField("notice_content")
private String noticeContent;
@ApiModelProperty("公告状态(1正常 0关闭)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.noticeId;
}
}
@@ -0,0 +1,34 @@
package com.agileboot.domain.system.notice.db;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* <p>
* 通知公告表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysNoticeMapper extends BaseMapper<SysNoticeEntity> {
/**
* 根据条件分页查询角色关联的用户列表
*
* @param page 分页对象
* @param queryWrapper 条件选择器
* @return 分页处理后的公告列表
*/
@Select("SELECT n.* "
+ "FROM sys_notice n "
+ "LEFT JOIN sys_user u ON n.creator_id = u.user_id"
+ " ${ew.customSqlSegment}")
Page<SysNoticeEntity> getNoticeList(Page<SysNoticeEntity> page,
@Param(Constants.WRAPPER) Wrapper<SysNoticeEntity> queryWrapper);
}
@@ -0,0 +1,29 @@
package com.agileboot.domain.system.notice.db;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.apache.ibatis.annotations.Param;
/**
* <p>
* 通知公告表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysNoticeService extends IService<SysNoticeEntity> {
/**
* 获取公告列表
*
* @param page 页码对象
* @param queryWrapper 查询对象
* @return 分页处理后的公告列表
*/
Page<SysNoticeEntity> getNoticeList(Page<SysNoticeEntity> page,
@Param(Constants.WRAPPER) Wrapper<SysNoticeEntity> queryWrapper);
}
@@ -0,0 +1,24 @@
package com.agileboot.domain.system.notice.db;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 通知公告表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
public class SysNoticeServiceImpl extends ServiceImpl<SysNoticeMapper, SysNoticeEntity> implements SysNoticeService {
@Override
public Page<SysNoticeEntity> getNoticeList(Page<SysNoticeEntity> page, Wrapper<SysNoticeEntity> queryWrapper) {
return this.baseMapper.getNoticeList(page, queryWrapper);
}
}
@@ -0,0 +1,45 @@
package com.agileboot.domain.system.notice.dto;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.notice.db.SysNoticeEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class NoticeDTO {
public NoticeDTO(SysNoticeEntity entity) {
if (entity != null) {
this.noticeId = entity.getNoticeId() + "";
this.noticeTitle = entity.getNoticeTitle();
this.noticeType = entity.getNoticeType();
this.noticeContent = entity.getNoticeContent();
this.status = entity.getStatus();
this.createTime = entity.getCreateTime();
SysUserEntity cacheUser = CacheCenter.userCache.getObjectById(entity.getCreatorId());
if (cacheUser != null) {
this.creatorName = cacheUser.getUsername();
}
}
}
private String noticeId;
private String noticeTitle;
private Integer noticeType;
private String noticeContent;
private Integer status;
private Date createTime;
private String creatorName;
}
@@ -0,0 +1,45 @@
package com.agileboot.domain.system.notice.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.system.notice.command.NoticeAddCommand;
import com.agileboot.domain.system.notice.command.NoticeUpdateCommand;
import com.agileboot.common.enums.common.NoticeTypeEnum;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.notice.db.SysNoticeEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class NoticeModel extends SysNoticeEntity {
public NoticeModel(SysNoticeEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
public void loadAddCommand(NoticeAddCommand command) {
if (command != null) {
BeanUtil.copyProperties(command, this, "noticeId");
}
}
public void loadUpdateCommand(NoticeUpdateCommand command) {
if (command != null) {
loadAddCommand(command);
}
}
public void checkFields() {
BasicEnumUtil.fromValue(NoticeTypeEnum.class, getNoticeType());
BasicEnumUtil.fromValue(StatusEnum.class, getStatus());
}
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.system.notice.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.notice.db.SysNoticeEntity;
import com.agileboot.domain.system.notice.db.SysNoticeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 公告模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class NoticeModelFactory {
private final SysNoticeService noticeService;
public NoticeModel loadById(Long noticeId) {
SysNoticeEntity byId = noticeService.getById(noticeId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, noticeId, "通知公告");
}
return new NoticeModel(byId);
}
public NoticeModel create() {
return new NoticeModel();
}
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.system.notice.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.notice.db.SysNoticeEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class NoticeQuery extends AbstractPageQuery<SysNoticeEntity> {
private String noticeType;
private String noticeTitle;
private String creatorName;
@Override
public QueryWrapper<SysNoticeEntity> addQueryCondition() {
QueryWrapper<SysNoticeEntity> queryWrapper = new QueryWrapper<SysNoticeEntity>()
.like(StrUtil.isNotEmpty(noticeTitle), "notice_title", noticeTitle)
.eq(StrUtil.isNotEmpty(noticeType), "notice_type", noticeType)
.eq("n.deleted", 0)
.like(StrUtil.isNotEmpty(creatorName), "u.username", creatorName);
return queryWrapper;
}
}
@@ -0,0 +1,85 @@
package com.agileboot.domain.system.post;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.post.model.PostModel;
import com.agileboot.domain.system.post.model.PostModelFactory;
import com.agileboot.domain.system.post.query.PostQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class PostApplicationService {
private final PostModelFactory postModelFactory;
private final SysPostService postService;
public PageDTO<PostDTO> getPostList(PostQuery query) {
Page<SysPostEntity> page = postService.page(query.toPage(), query.toQueryWrapper());
List<PostDTO> records = page.getRecords().stream().map(PostDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
/**
* 查询满足条件的所有岗位,不分页
* @param query 查询条件
* @return 满足查询条件的岗位列表
* @author Kevin Zhang
* @date 2023-10-02
*/
public List<PostDTO> getPostListAll(PostQuery query) {
List<SysPostEntity> all = postService.list(query.toQueryWrapper());
List<PostDTO> records = all.stream().map(PostDTO::new).collect(Collectors.toList());
return records;
}
public PostDTO getPostInfo(Long postId) {
SysPostEntity byId = postService.getById(postId);
return new PostDTO(byId);
}
public void addPost(AddPostCommand addCommand) {
PostModel postModel = postModelFactory.create();
postModel.loadFromAddCommand(addCommand);
postModel.checkPostNameUnique();
postModel.checkPostCodeUnique();
postModel.insert();
}
public void updatePost(UpdatePostCommand updateCommand) {
PostModel postModel = postModelFactory.loadById(updateCommand.getPostId());
postModel.loadFromUpdateCommand(updateCommand);
postModel.checkPostNameUnique();
postModel.checkPostCodeUnique();
postModel.updateById();
}
public void deletePost(BulkOperationCommand<Long> deleteCommand) {
for (Long id : deleteCommand.getIds()) {
PostModel postModel = postModelFactory.loadById(id);
postModel.checkCanBeDelete();
}
postService.removeBatchByIds(deleteCommand.getIds());
}
}
@@ -0,0 +1,37 @@
package com.agileboot.domain.system.post.command;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddPostCommand {
@NotBlank(message = "岗位编码不能为空")
@Size(max = 64, message = "岗位编码长度不能超过64个字符")
protected String postCode;
/**
* 岗位名称
*/
@NotBlank(message = "岗位名称不能为空")
@Size(max = 64, message = "岗位名称长度不能超过64个字符")
protected String postName;
/**
* 岗位排序
*/
@NotNull(message = "显示顺序不能为空")
protected Integer postSort;
protected String remark;
@PositiveOrZero
protected String status;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.system.post.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdatePostCommand extends AddPostCommand {
@NotNull(message = "岗位ID不能为空")
@Positive
private Long postId;
}
@@ -0,0 +1,60 @@
package com.agileboot.domain.system.post.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 岗位信息表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_post")
@ApiModel(value = "SysPostEntity对象", description = "岗位信息表")
public class SysPostEntity extends BaseEntity<SysPostEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("岗位ID")
@TableId(value = "post_id", type = IdType.AUTO)
private Long postId;
@ApiModelProperty("岗位编码")
@TableField("post_code")
private String postCode;
@ApiModelProperty("岗位名称")
@TableField("post_name")
private String postName;
@ApiModelProperty("显示顺序")
@TableField("post_sort")
private Integer postSort;
@ApiModelProperty("状态(1正常 0停用)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.postId;
}
}
@@ -0,0 +1,15 @@
package com.agileboot.domain.system.post.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 岗位信息表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysPostMapper extends BaseMapper<SysPostEntity> {
}
@@ -0,0 +1,40 @@
package com.agileboot.domain.system.post.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 岗位信息表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysPostService extends IService<SysPostEntity> {
/**
* 校验岗位名称
* @param postId 职位Id
* @param postName 职位名称
* @return 结果
*/
boolean isPostNameDuplicated(Long postId, String postName);
/**
* 校验岗位编码
* @param postId 职位id
* @param postCode 职位代码
* @return 结果
*/
boolean isPostCodeDuplicated(Long postId, String postCode);
/**
* 检测职位是否分配给用户
*
* @param postId 职位id
* @return 校验结果
*/
boolean isAssignedToUsers(Long postId);
}
@@ -0,0 +1,55 @@
package com.agileboot.domain.system.post.db;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 岗位信息表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPostEntity> implements SysPostService {
private final SysUserMapper userMapper;
/**
* 校验岗位名称是否唯一
*
* @param postName 岗位名称
* @return 结果
*/
@Override
public boolean isPostNameDuplicated(Long postId, String postName) {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.ne(postId != null, "post_id", postId)
.eq("post_name", postName);
return baseMapper.exists(queryWrapper);
}
@Override
public boolean isPostCodeDuplicated(Long postId, String postCode) {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.ne(postId != null, "post_id", postId)
.eq("post_code", postCode);
return baseMapper.exists(queryWrapper);
}
@Override
public boolean isAssignedToUsers(Long postId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("post_id", postId);
return userMapper.exists(queryWrapper);
}
}
@@ -0,0 +1,52 @@
package com.agileboot.domain.system.post.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.post.db.SysPostEntity;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PostDTO {
public PostDTO(SysPostEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
}
}
@ExcelColumn(name = "岗位ID")
private Long postId;
@ExcelColumn(name = "岗位编码")
private String postCode;
@ExcelColumn(name = "岗位名称")
private String postName;
@ExcelColumn(name = "岗位排序")
private Integer postSort;
@ExcelColumn(name = "备注")
private String remark;
private Integer status;
@ExcelColumn(name = "状态")
private String statusStr;
private Date createTime;
}
@@ -0,0 +1,63 @@
package com.agileboot.domain.system.post.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@NoArgsConstructor
public class PostModel extends SysPostEntity {
private SysPostService postService;
public PostModel(SysPostService postService) {
this.postService = postService;
}
public PostModel(SysPostEntity entity, SysPostService postService) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
this.postService = postService;
}
public void loadFromAddCommand(AddPostCommand addCommand) {
if (addCommand != null) {
BeanUtil.copyProperties(addCommand, this, "postId");
}
}
public void loadFromUpdateCommand(UpdatePostCommand command) {
if (command != null) {
loadFromAddCommand(command);
}
}
public void checkCanBeDelete() {
if (postService.isAssignedToUsers(this.getPostId())) {
throw new ApiException(ErrorCode.Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED);
}
}
public void checkPostNameUnique() {
if (postService.isPostNameDuplicated(getPostId(), getPostName())) {
throw new ApiException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, getPostName());
}
}
public void checkPostCodeUnique() {
if (postService.isPostCodeDuplicated(getPostId(), getPostCode())) {
throw new ApiException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, getPostCode());
}
}
}
@@ -0,0 +1,31 @@
package com.agileboot.domain.system.post.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class PostModelFactory {
private final SysPostService postService;
public PostModel loadById(Long postId) {
SysPostEntity byId = postService.getById(postId);
if (byId == null) {
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND, postId, "职位");
}
return new PostModel(byId, postService);
}
public PostModel create() {
return new PostModel(postService);
}
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.system.post.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PostQuery extends AbstractPageQuery<SysPostEntity> {
private String postCode;
private String postName;
private Integer status;
@Override
public QueryWrapper<SysPostEntity> addQueryCondition() {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<SysPostEntity>()
.eq(status != null, "status", status)
.eq(StrUtil.isNotEmpty(postCode), "post_code", postCode)
.like(StrUtil.isNotEmpty(postName), "post_name", postName);
// 当前端没有选择排序字段时,则使用post_sort字段升序排序(在父类AbstractQuery中默认为升序)
if (StrUtil.isEmpty(this.getOrderColumn())) {
this.setOrderColumn("post_sort");
}
this.setTimeRangeColumn("create_time");
return queryWrapper;
}
}
@@ -0,0 +1,164 @@
package com.agileboot.domain.system.role;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.role.command.AddRoleCommand;
import com.agileboot.domain.system.role.command.UpdateDataScopeCommand;
import com.agileboot.domain.system.role.command.UpdateRoleCommand;
import com.agileboot.domain.system.role.command.UpdateStatusCommand;
import com.agileboot.domain.system.role.dto.RoleDTO;
import com.agileboot.domain.system.role.model.RoleModel;
import com.agileboot.domain.system.role.model.RoleModelFactory;
import com.agileboot.domain.system.role.query.AllocatedRoleQuery;
import com.agileboot.domain.system.role.query.RoleQuery;
import com.agileboot.domain.system.role.query.UnallocatedRoleQuery;
import com.agileboot.domain.system.user.dto.UserDTO;
import com.agileboot.domain.system.user.model.UserModel;
import com.agileboot.domain.system.user.model.UserModelFactory;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.menu.db.SysMenuService;
import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class RoleApplicationService {
private final RoleModelFactory roleModelFactory;
private final UserModelFactory userModelFactory;
private final SysRoleService roleService;
private final SysUserService userService;
private final SysMenuService menuService;
public PageDTO<RoleDTO> getRoleList(RoleQuery query) {
Page<SysRoleEntity> page = roleService.page(query.toPage(), query.toQueryWrapper());
List<RoleDTO> records = page.getRecords().stream().map(RoleDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public RoleDTO getRoleInfo(Long roleId) {
SysRoleEntity byId = roleService.getById(roleId);
RoleDTO roleDTO = new RoleDTO(byId);
List<Long> selectedDeptList = StrUtil.split(byId.getDeptIdSet(), ",")
.stream().filter(StrUtil::isNotEmpty).map(Long::new).collect(Collectors.toList());
roleDTO.setSelectedDeptList(selectedDeptList);
roleDTO.setSelectedMenuList(menuService.getMenuIdsByRoleId(roleId));
return roleDTO;
}
public void addRole(AddRoleCommand addCommand) {
RoleModel roleModel = roleModelFactory.create();
roleModel.loadAddCommand(addCommand);
roleModel.checkRoleNameUnique();
roleModel.checkRoleKeyUnique();
roleModel.insert();
}
public void deleteRoleByBulk(List<Long> roleIds) {
if (roleIds != null) {
for (Long roleId : roleIds) {
RoleModel roleModel = roleModelFactory.loadById(roleId);
roleModel.checkRoleCanBeDelete();
roleModel.deleteById();
}
}
}
public void updateRole(UpdateRoleCommand updateCommand) {
RoleModel roleModel = roleModelFactory.loadById(updateCommand.getRoleId());
roleModel.loadUpdateCommand(updateCommand);
roleModel.checkRoleKeyUnique();
roleModel.checkRoleNameUnique();
roleModel.updateById();
}
public void updateStatus(UpdateStatusCommand command) {
RoleModel roleModel = roleModelFactory.loadById(command.getRoleId());
roleModel.setStatus(command.getStatus());
roleModel.updateById();
}
public void updateDataScope(UpdateDataScopeCommand command) {
RoleModel roleModel = roleModelFactory.loadById(command.getRoleId());
roleModel.setDeptIds(command.getDeptIds());
roleModel.setDataScope(command.getDataScope());
roleModel.generateDeptIdSet();
roleModel.updateById();
}
public PageDTO<UserDTO> getAllocatedUserList(AllocatedRoleQuery query) {
Page<SysUserEntity> page = userService.getUserListByRole(query);
List<UserDTO> dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList());
return new PageDTO<>(dtoList, page.getTotal());
}
public PageDTO<UserDTO> getUnallocatedUserList(UnallocatedRoleQuery query) {
Page<SysUserEntity> page = userService.getUserListByRole(query);
List<UserDTO> dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList());
return new PageDTO<>(dtoList, page.getTotal());
}
public void deleteRoleOfUserByBulk(List<Long> userIds) {
if (CollUtil.isEmpty(userIds)) {
return;
}
for (Long userId : userIds) {
LambdaUpdateWrapper<SysUserEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(SysUserEntity::getRoleId, null).eq(SysUserEntity::getUserId, userId);
userService.update(updateWrapper);
CacheCenter.userCache.delete(userId);
}
}
public void addRoleOfUserByBulk(Long roleId, List<Long> userIds) {
if (CollUtil.isEmpty(userIds)) {
return;
}
RoleModel roleModel = roleModelFactory.loadById(roleId);
roleModel.checkRoleAvailable();
for (Long userId : userIds) {
UserModel user = userModelFactory.loadById(userId);
user.setRoleId(roleId);
user.updateById();
CacheCenter.userCache.delete(userId);
}
}
}
@@ -0,0 +1,52 @@
package com.agileboot.domain.system.role.command;
import com.agileboot.common.annotation.ExcelColumn;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddRoleCommand {
/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空")
@Size(max = 30, message = "角色名称长度不能超过30个字符")
private String roleName;
/**
* 角色权限
*/
@ExcelColumn(name = "角色权限")
@NotBlank(message = "权限字符不能为空")
@Size(max = 100, message = "权限字符长度不能超过100个字符")
private String roleKey;
/**
* 角色排序
*/
@ExcelColumn(name = "角色排序")
@NotNull(message = "显示顺序不能为空")
private Integer roleSort;
private String remark;
@ExcelColumn(name = "数据范围")
private String dataScope;
@PositiveOrZero
private String status;
@NotNull
private List<Long> menuIds;
}
@@ -0,0 +1,26 @@
package com.agileboot.domain.system.role.command;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class UpdateDataScopeCommand {
@NotNull
@Positive
private Long roleId;
@NotNull
@NotEmpty
private List<Long> deptIds;
private Integer dataScope;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.system.role.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateRoleCommand extends AddRoleCommand {
@NotNull
@PositiveOrZero
private Long roleId;
}
@@ -0,0 +1,17 @@
package com.agileboot.domain.system.role.command;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class UpdateStatusCommand {
private Long roleId;
private Integer status;
}
@@ -0,0 +1,68 @@
package com.agileboot.domain.system.role.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 角色信息表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_role")
@ApiModel(value = "SysRoleEntity对象", description = "角色信息表")
public class SysRoleEntity extends BaseEntity<SysRoleEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("角色ID")
@TableId(value = "role_id", type = IdType.AUTO)
private Long roleId;
@ApiModelProperty("角色名称")
@TableField("role_name")
private String roleName;
@ApiModelProperty("角色权限字符串")
@TableField("role_key")
private String roleKey;
@ApiModelProperty("显示顺序")
@TableField("role_sort")
private Integer roleSort;
@ApiModelProperty("数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)")
@TableField("data_scope")
private Integer dataScope;
@ApiModelProperty("角色所拥有的部门数据权限")
@TableField("dept_id_set")
private String deptIdSet;
@ApiModelProperty("角色状态(1正常 0停用)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.roleId;
}
}
@@ -0,0 +1,33 @@
package com.agileboot.domain.system.role.db;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
/**
* <p>
* 角色信息表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysRoleMapper extends BaseMapper<SysRoleEntity> {
/**
* 根据角色ID查询对应的菜单权限
*
* @param roleId 角色ID
* @return 权限列表
*/
@Select("SELECT m.* "
+ "FROM sys_menu m "
+ " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
+ " LEFT JOIN sys_role r ON r.role_id = rm.role_id "
+ "WHERE m.status = 1 AND m.deleted = 0 "
+ " AND r.status = 1 AND r.deleted = 0 "
+ " AND r.role_id = #{roleId}")
List<SysMenuEntity> getMenuListByRoleId(Long roleId);
}
@@ -0,0 +1,44 @@
package com.agileboot.domain.system.role.db;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 角色和菜单关联表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_role_menu")
@ApiModel(value = "SysRoleMenuXEntity对象", description = "角色和菜单关联表")
public class SysRoleMenuEntity extends Model<SysRoleMenuEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("角色ID")
@TableId(value = "role_id", type = IdType.AUTO)
private Long roleId;
@ApiModelProperty("菜单ID")
@TableField("menu_id")
private Long menuId;
@Override
public Serializable pkVal() {
return this.menuId;
}
}

Some files were not shown because too many files have changed in this diff Show More