refactor: 项目目录重构
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.system.converter.ConfigConverter;
|
||||
import com.youlai.boot.system.mapper.SysConfigMapper;
|
||||
import com.youlai.boot.system.model.entity.SysConfig;
|
||||
import com.youlai.boot.system.model.form.ConfigForm;
|
||||
import com.youlai.boot.system.model.query.ConfigPageQuery;
|
||||
import com.youlai.boot.system.model.vo.ConfigVO;
|
||||
import com.youlai.boot.system.service.SysConfigService;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 系统配置Service接口实现
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024-07-29 11:17:26
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig> implements SysConfigService {
|
||||
|
||||
private final SysConfigMapper sysConfigMapper;
|
||||
|
||||
private final ConfigConverter configConverter;
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 系统启动完成后,加载系统配置到缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
refreshCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询系统配置
|
||||
*
|
||||
* @param configPageQuery 查询参数
|
||||
* @return 系统配置分页列表
|
||||
*/
|
||||
@Override
|
||||
public IPage<ConfigVO> page(ConfigPageQuery configPageQuery) {
|
||||
Page<SysConfig> page = new Page<>(configPageQuery.getPageNum(), configPageQuery.getPageSize());
|
||||
String keywords = configPageQuery.getKeywords();
|
||||
LambdaQueryWrapper<SysConfig> query = new LambdaQueryWrapper<SysConfig>()
|
||||
.and(StringUtils.isNotBlank(keywords),
|
||||
q -> q.like(SysConfig::getConfigKey, keywords)
|
||||
.or()
|
||||
.like(SysConfig::getConfigName, keywords)
|
||||
);
|
||||
Page<SysConfig> pageList = this.page(page, query);
|
||||
return configConverter.toPageVo(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存系统配置
|
||||
*
|
||||
* @param configForm 系统配置表单
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
@Override
|
||||
public boolean save(ConfigForm configForm) {
|
||||
Assert.isTrue(
|
||||
super.count(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, configForm.getConfigKey())) == 0,
|
||||
"配置键已存在");
|
||||
SysConfig sysConfig = configConverter.toEntity(configForm);
|
||||
sysConfig.setCreateBy(SecurityUtils.getUserId());
|
||||
return this.save(sysConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置表单数据
|
||||
*
|
||||
* @param id 系统配置ID
|
||||
* @return 系统配置表单数据
|
||||
*/
|
||||
@Override
|
||||
public ConfigForm getConfigFormData(Long id) {
|
||||
SysConfig entity = this.getById(id);
|
||||
return configConverter.toForm(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑系统配置
|
||||
*
|
||||
* @param id 系统配置ID
|
||||
* @param configForm 系统配置表单
|
||||
* @return 是否编辑成功
|
||||
*/
|
||||
@Override
|
||||
public boolean edit(Long id, ConfigForm configForm) {
|
||||
Assert.isTrue(
|
||||
super.count(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, configForm.getConfigKey()).ne(SysConfig::getId, id)) == 0,
|
||||
"配置键已存在");
|
||||
SysConfig sysConfig = configConverter.toEntity(configForm);
|
||||
sysConfig.setUpdateBy(SecurityUtils.getUserId());
|
||||
return this.updateById(sysConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除系统配置
|
||||
*
|
||||
* @param id 系统配置ID
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean delete(Long id) {
|
||||
if (id != null) {
|
||||
return super.remove(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getId,id));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新系统配置缓存
|
||||
*
|
||||
* @return 是否刷新成功
|
||||
*/
|
||||
@Override
|
||||
public boolean refreshCache() {
|
||||
redisTemplate.delete(RedisConstants.SYSTEM_CONFIG_KEY);
|
||||
List<SysConfig> list = this.list();
|
||||
if (list != null) {
|
||||
Map<String, String> map = list.stream().collect(Collectors.toMap(SysConfig::getConfigKey, SysConfig::getConfigValue));
|
||||
redisTemplate.opsForHash().putAll(RedisConstants.SYSTEM_CONFIG_KEY, map);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return 配置值
|
||||
*/
|
||||
@Override
|
||||
public Object getSystemConfig(String key) {
|
||||
if (StringUtils.isNotBlank(key)) {
|
||||
return redisTemplate.opsForHash().get(RedisConstants.SYSTEM_CONFIG_KEY, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.converter.DeptConverter;
|
||||
import com.youlai.boot.system.mapper.SysDeptMapper;
|
||||
import com.youlai.boot.system.model.entity.SysDept;
|
||||
import com.youlai.boot.system.model.form.DeptForm;
|
||||
import com.youlai.boot.system.model.query.DeptQuery;
|
||||
import com.youlai.boot.system.model.vo.DeptVO;
|
||||
import com.youlai.boot.common.constant.SymbolConstant;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.enums.StatusEnum;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.system.service.SysDeptService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 部门 业务实现类
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2021/08/22
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements SysDeptService {
|
||||
|
||||
|
||||
private final DeptConverter deptConverter;
|
||||
|
||||
/**
|
||||
* 获取部门列表
|
||||
*/
|
||||
@Override
|
||||
public List<DeptVO> getDeptList(DeptQuery queryParams) {
|
||||
// 查询参数
|
||||
String keywords = queryParams.getKeywords();
|
||||
Integer status = queryParams.getStatus();
|
||||
|
||||
// 查询数据
|
||||
List<SysDept> deptList = this.list(
|
||||
new LambdaQueryWrapper<SysDept>()
|
||||
.like(StrUtil.isNotBlank(keywords), SysDept::getName, keywords)
|
||||
.eq(status != null, SysDept::getStatus, status)
|
||||
.orderByAsc(SysDept::getSort)
|
||||
);
|
||||
|
||||
if (CollectionUtil.isEmpty(deptList)) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
// 获取所有部门ID
|
||||
Set<Long> deptIds = deptList.stream()
|
||||
.map(SysDept::getId)
|
||||
.collect(Collectors.toSet());
|
||||
// 获取父节点ID
|
||||
Set<Long> parentIds = deptList.stream()
|
||||
.map(SysDept::getParentId)
|
||||
.collect(Collectors.toSet());
|
||||
// 获取根节点ID(递归的起点),即父节点ID中不包含在部门ID中的节点,注意这里不能拿顶级部门 O 作为根节点,因为部门筛选的时候 O 会被过滤掉
|
||||
List<Long> rootIds = CollectionUtil.subtractToList(parentIds, deptIds);
|
||||
|
||||
// 递归生成部门树形列表
|
||||
return rootIds.stream()
|
||||
.flatMap(rootId -> recurDeptList(rootId, deptList).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归生成部门树形列表
|
||||
*
|
||||
* @param parentId 父ID
|
||||
* @param deptList 部门列表
|
||||
* @return 部门树形列表
|
||||
*/
|
||||
public List<DeptVO> recurDeptList(Long parentId, List<SysDept> deptList) {
|
||||
return deptList.stream()
|
||||
.filter(dept -> dept.getParentId().equals(parentId))
|
||||
.map(dept -> {
|
||||
DeptVO deptVO = deptConverter.toVo(dept);
|
||||
List<DeptVO> children = recurDeptList(dept.getId(), deptList);
|
||||
deptVO.setChildren(children);
|
||||
return deptVO;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门下拉选项
|
||||
*
|
||||
* @return 部门下拉List集合
|
||||
*/
|
||||
@Override
|
||||
public List<Option<Long>> listDeptOptions() {
|
||||
|
||||
List<SysDept> deptList = this.list(new LambdaQueryWrapper<SysDept>()
|
||||
.eq(SysDept::getStatus, StatusEnum.ENABLE.getValue())
|
||||
.select(SysDept::getId, SysDept::getParentId, SysDept::getName)
|
||||
.orderByAsc(SysDept::getSort)
|
||||
);
|
||||
if (CollectionUtil.isEmpty(deptList)) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
Set<Long> deptIds = deptList.stream()
|
||||
.map(SysDept::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<Long> parentIds = deptList.stream()
|
||||
.map(SysDept::getParentId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<Long> rootIds = CollectionUtil.subtractToList(parentIds, deptIds);
|
||||
|
||||
// 递归生成部门树形列表
|
||||
return rootIds.stream()
|
||||
.flatMap(rootId -> recurDeptTreeOptions(rootId, deptList).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
*
|
||||
* @param formData 部门表单
|
||||
* @return 部门ID
|
||||
*/
|
||||
@Override
|
||||
public Long saveDept(DeptForm formData) {
|
||||
// 校验部门名称是否存在
|
||||
String code = formData.getCode();
|
||||
long count = this.count(new LambdaQueryWrapper<SysDept>()
|
||||
.eq(SysDept::getCode, code)
|
||||
);
|
||||
Assert.isTrue(count == 0, "部门编号已存在");
|
||||
|
||||
// form->entity
|
||||
SysDept entity = deptConverter.toEntity(formData);
|
||||
|
||||
// 生成部门路径(tree_path),格式:父节点tree_path + , + 父节点ID,用于删除部门时级联删除子部门
|
||||
String treePath = generateDeptTreePath(formData.getParentId());
|
||||
entity.setTreePath(treePath);
|
||||
|
||||
// 保存部门并返回部门ID
|
||||
boolean result = this.save(entity);
|
||||
Assert.isTrue(result, "部门保存失败");
|
||||
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取部门表单
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 部门表单对象
|
||||
*/
|
||||
@Override
|
||||
public DeptForm getDeptForm(Long deptId) {
|
||||
SysDept entity = this.getById(deptId);
|
||||
return deptConverter.toForm(entity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新部门
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @param formData 部门表单
|
||||
* @return 部门ID
|
||||
*/
|
||||
@Override
|
||||
public Long updateDept(Long deptId, DeptForm formData) {
|
||||
// 校验部门名称/部门编号是否存在
|
||||
String code = formData.getCode();
|
||||
long count = this.count(new LambdaQueryWrapper<SysDept>()
|
||||
.ne(SysDept::getId, deptId)
|
||||
.eq(SysDept::getCode, code)
|
||||
);
|
||||
Assert.isTrue(count == 0, "部门编号已存在");
|
||||
|
||||
|
||||
// form->entity
|
||||
SysDept entity = deptConverter.toEntity(formData);
|
||||
entity.setId(deptId);
|
||||
|
||||
// 生成部门路径(tree_path),格式:父节点tree_path + , + 父节点ID,用于删除部门时级联删除子部门
|
||||
String treePath = generateDeptTreePath(formData.getParentId());
|
||||
entity.setTreePath(treePath);
|
||||
|
||||
// 保存部门并返回部门ID
|
||||
boolean result = this.updateById(entity);
|
||||
Assert.isTrue(result, "部门更新失败");
|
||||
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归生成部门表格层级列表
|
||||
*
|
||||
* @param parentId 父ID
|
||||
* @param deptList 部门列表
|
||||
* @return 部门表格层级列表
|
||||
*/
|
||||
public static List<Option<Long>> recurDeptTreeOptions(long parentId, List<SysDept> deptList) {
|
||||
return CollectionUtil.emptyIfNull(deptList).stream()
|
||||
.filter(dept -> dept.getParentId().equals(parentId))
|
||||
.map(dept -> {
|
||||
Option<Long> option = new Option<>(dept.getId(), dept.getName());
|
||||
List<Option<Long>> children = recurDeptTreeOptions(dept.getId(), deptList);
|
||||
if (CollectionUtil.isNotEmpty(children)) {
|
||||
option.setChildren(children);
|
||||
}
|
||||
return option;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*
|
||||
* @param ids 部门ID,多个以英文逗号,拼接字符串
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteByIds(String ids) {
|
||||
// 删除部门及子部门
|
||||
if (StrUtil.isNotBlank(ids)) {
|
||||
String[] menuIds = ids.split(SymbolConstant.COMMA);
|
||||
for (String deptId : menuIds) {
|
||||
this.remove(new LambdaQueryWrapper<SysDept>()
|
||||
.eq(SysDept::getId, deptId)
|
||||
.or()
|
||||
.apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", deptId));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 部门路径生成
|
||||
*
|
||||
* @param parentId 父ID
|
||||
* @return 父节点路径以英文逗号(, )分割,eg: 1,2,3
|
||||
*/
|
||||
private String generateDeptTreePath(Long parentId) {
|
||||
String treePath = null;
|
||||
if (SystemConstants.ROOT_NODE_ID.equals(parentId)) {
|
||||
treePath = String.valueOf(parentId);
|
||||
} else {
|
||||
SysDept parent = this.getById(parentId);
|
||||
if (parent != null) {
|
||||
treePath = parent.getTreePath() + SymbolConstant.COMMA + parent.getId();
|
||||
}
|
||||
}
|
||||
return treePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.mapper.SysDictItemMapper;
|
||||
import com.youlai.boot.system.model.entity.SysDictItem;
|
||||
import com.youlai.boot.system.service.SysDictItemService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据字典 服务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/12
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysDictItemServiceImpl extends ServiceImpl<SysDictItemMapper, SysDictItem> implements SysDictItemService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.converter.DictConverter;
|
||||
import com.youlai.boot.system.converter.DictItemConverter;
|
||||
import com.youlai.boot.system.mapper.SysDictMapper;
|
||||
import com.youlai.boot.system.model.entity.SysDict;
|
||||
import com.youlai.boot.system.model.entity.SysDictItem;
|
||||
import com.youlai.boot.system.model.form.DictForm;
|
||||
import com.youlai.boot.system.model.query.DictPageQuery;
|
||||
import com.youlai.boot.system.model.vo.DictPageVO;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.system.service.SysDictItemService;
|
||||
import com.youlai.boot.system.service.SysDictService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据字典业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/12
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {
|
||||
|
||||
private final SysDictItemService dictItemService;
|
||||
private final DictConverter dictConverter;
|
||||
private final DictItemConverter dictItemConverter;
|
||||
|
||||
/**
|
||||
* 字典分页列表
|
||||
*
|
||||
* @param queryParams 分页查询对象
|
||||
*/
|
||||
@Override
|
||||
public Page<DictPageVO> getDictPage(DictPageQuery queryParams) {
|
||||
// 查询参数
|
||||
int pageNum = queryParams.getPageNum();
|
||||
int pageSize = queryParams.getPageSize();
|
||||
|
||||
// 查询数据
|
||||
return this.baseMapper.getDictPage(new Page<>(pageNum, pageSize), queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典
|
||||
*
|
||||
* @param dictForm 字典表单数据
|
||||
*/
|
||||
@Override
|
||||
public boolean saveDict(DictForm dictForm) {
|
||||
// 保存字典
|
||||
SysDict entity = dictConverter.toEntity(dictForm);
|
||||
|
||||
// 校验 code 是否唯一
|
||||
long count = this.count(new LambdaQueryWrapper<SysDict>()
|
||||
.eq(SysDict::getCode, entity.getCode())
|
||||
);
|
||||
Assert.isTrue(count == 0, "字典编码已存在");
|
||||
|
||||
boolean result = this.save(entity);
|
||||
// 保存字典项
|
||||
if (result) {
|
||||
List<DictForm.DictItem> dictFormDictItems = dictForm.getDictItems();
|
||||
List<SysDictItem> dictItems = dictItemConverter.toEntity(dictFormDictItems);
|
||||
dictItems.forEach(dictItem -> dictItem.setDictId(entity.getId()));
|
||||
dictItemService.saveBatch(dictItems);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取字典表单详情
|
||||
*
|
||||
* @param id 字典ID
|
||||
*/
|
||||
@Override
|
||||
public DictForm getDictForm(Long id) {
|
||||
// 获取字典
|
||||
SysDict entity = this.getById(id);
|
||||
Assert.isTrue(entity != null, "字典不存在");
|
||||
DictForm dictForm = dictConverter.toForm(entity);
|
||||
|
||||
// 获取字典项集合
|
||||
List<SysDictItem> dictItems = dictItemService.list(new LambdaQueryWrapper<SysDictItem>()
|
||||
.eq(SysDictItem::getDictId, id)
|
||||
);
|
||||
// 转换数据项
|
||||
List<DictForm.DictItem> dictItemList = dictItemConverter.toDictItem(dictItems);
|
||||
dictForm.setDictItems(dictItemList);
|
||||
return dictForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典
|
||||
*
|
||||
* @param id 字典ID
|
||||
* @param dictForm 字典表单
|
||||
*/
|
||||
@Override
|
||||
public boolean updateDict(Long id, DictForm dictForm) {
|
||||
// 更新字典
|
||||
SysDict entity = dictConverter.toEntity(dictForm);
|
||||
|
||||
// 校验 code 是否唯一
|
||||
long count = this.count(new LambdaQueryWrapper<SysDict>()
|
||||
.eq(SysDict::getCode, entity.getCode())
|
||||
.ne(SysDict::getId, id)
|
||||
);
|
||||
Assert.isTrue(count == 0, "字典编码已存在");
|
||||
|
||||
boolean result = this.updateById(entity);
|
||||
|
||||
if (result) {
|
||||
// 更新字典项
|
||||
List<DictForm.DictItem> dictFormDictItems = dictForm.getDictItems();
|
||||
List<SysDictItem> dictItems = dictItemConverter.toEntity(dictFormDictItems);
|
||||
|
||||
// 获取当前数据库中的字典项
|
||||
List<SysDictItem> currentDictItemEntities = dictItemService.list(new LambdaQueryWrapper<SysDictItem>()
|
||||
.eq(SysDictItem::getDictId, id)
|
||||
);
|
||||
|
||||
// 获取当前数据库中存在的字典项ID集合
|
||||
Set<Long> currentDictItemIds = currentDictItemEntities.stream()
|
||||
.map(SysDictItem::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 获取新提交的字典项ID集合
|
||||
Set<Long> newAttrIds = dictItems.stream()
|
||||
.map(SysDictItem::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 需要删除的字典项ID集合(存在于数据库但不在新提交的字典项中)
|
||||
Set<Long> idsToDelete = new HashSet<>(currentDictItemIds);
|
||||
idsToDelete.removeAll(newAttrIds);
|
||||
|
||||
// 删除不在新提交字典项中的旧字典项
|
||||
if (!idsToDelete.isEmpty()) {
|
||||
dictItemService.removeByIds(idsToDelete);
|
||||
}
|
||||
|
||||
// 更新或新增字典项
|
||||
for (SysDictItem dictItem : dictItems) {
|
||||
if (dictItem.getId() != null && currentDictItemIds.contains(dictItem.getId())) {
|
||||
// 更新现有字典项
|
||||
dictItemService.updateById(dictItem);
|
||||
} else {
|
||||
// 新增字典项
|
||||
dictItem.setDictId(id);
|
||||
dictItemService.save(dictItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典
|
||||
*
|
||||
* @param ids 字典ID,多个以英文逗号(,)分割
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteDictByIds(String ids) {
|
||||
|
||||
Assert.isTrue(StrUtil.isNotBlank(ids), "请选择需要删除的字典");
|
||||
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.toList();
|
||||
|
||||
for (String id : idList) {
|
||||
boolean result = this.removeById(id);
|
||||
if (result) {
|
||||
// 删除字典下的字典项
|
||||
dictItemService.remove(
|
||||
new LambdaQueryWrapper<SysDictItem>()
|
||||
.eq(SysDictItem::getDictId, id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典的数据项
|
||||
*
|
||||
* @param code 字典编码
|
||||
*/
|
||||
@Override
|
||||
public List<Option<Long>> listDictItemsByCode(String code) {
|
||||
// 根据字典编码获取字典ID
|
||||
SysDict dict = this.getOne(new LambdaQueryWrapper<SysDict>()
|
||||
.eq(SysDict::getCode, code)
|
||||
.select(SysDict::getId)
|
||||
.last("limit 1")
|
||||
);
|
||||
// 如果字典不存在,则返回空集合
|
||||
if (dict == null) {
|
||||
return CollectionUtil.newArrayList();
|
||||
}
|
||||
|
||||
// 获取字典项
|
||||
List<SysDictItem> dictItems = dictItemService.list(
|
||||
new LambdaQueryWrapper<SysDictItem>()
|
||||
.eq(SysDictItem::getDictId, dict.getId())
|
||||
);
|
||||
|
||||
// 转换为 Option
|
||||
return dictItemConverter.toOption(dictItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典列表
|
||||
*/
|
||||
@Override
|
||||
public List<Option<String>> getDictList() {
|
||||
return this.list(new LambdaQueryWrapper<SysDict>()
|
||||
.eq(SysDict::getStatus, 1)
|
||||
.select(SysDict::getName, SysDict::getCode)
|
||||
).stream()
|
||||
.map(dict -> new Option<>(dict.getCode(), dict.getName()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.mapper.SysLogMapper;
|
||||
import com.youlai.boot.system.model.bo.VisitCount;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.model.query.LogPageQuery;
|
||||
import com.youlai.boot.system.model.vo.LogPageVO;
|
||||
import com.youlai.boot.system.model.vo.VisitStatsVO;
|
||||
import com.youlai.boot.system.model.vo.VisitTrendVO;
|
||||
import com.youlai.boot.system.service.SysLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 系统日志 服务实现类
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Service
|
||||
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog>
|
||||
implements SysLogService {
|
||||
|
||||
/**
|
||||
* 获取日志分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Page<LogPageVO> listPagedLogs(LogPageQuery queryParams) {
|
||||
return this.baseMapper.listPagedLogs(new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
|
||||
queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问趋势
|
||||
*
|
||||
* @param startDate 开始时间
|
||||
* @param endDate 结束时间
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public VisitTrendVO getVisitTrend(LocalDate startDate, LocalDate endDate) {
|
||||
VisitTrendVO visitTrend = new VisitTrendVO();
|
||||
List<String> dates = new ArrayList<>();
|
||||
|
||||
// 获取日期范围内的日期
|
||||
while (!startDate.isAfter(endDate)) {
|
||||
dates.add(startDate.toString());
|
||||
startDate = startDate.plusDays(1);
|
||||
}
|
||||
visitTrend.setDates(dates);
|
||||
|
||||
// 获取访问量和访问 IP 数的统计数据
|
||||
List<VisitCount> pvCounts = this.baseMapper.getPvCounts(dates.get(0) + " 00:00:00", dates.get(dates.size() - 1) + " 23:59:59");
|
||||
List<VisitCount> ipCounts = this.baseMapper.getIpCounts(dates.get(0) + " 00:00:00", dates.get(dates.size() - 1) + " 23:59:59");
|
||||
|
||||
// 将统计数据转换为 Map
|
||||
Map<String, Integer> pvMap = pvCounts.stream().collect(Collectors.toMap(VisitCount::getDate, VisitCount::getCount));
|
||||
Map<String, Integer> ipMap = ipCounts.stream().collect(Collectors.toMap(VisitCount::getDate, VisitCount::getCount));
|
||||
|
||||
// 匹配日期和访问量/访问 IP 数
|
||||
List<Integer> pvList = new ArrayList<>();
|
||||
List<Integer> ipList = new ArrayList<>();
|
||||
|
||||
for (String date : dates) {
|
||||
pvList.add(pvMap.getOrDefault(date, 0));
|
||||
ipList.add(ipMap.getOrDefault(date, 0));
|
||||
}
|
||||
|
||||
visitTrend.setPvList(pvList);
|
||||
visitTrend.setIpList(ipList);
|
||||
|
||||
return visitTrend;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问统计
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<VisitStatsVO> getVisitStats() {
|
||||
List<VisitStatsVO> list = new ArrayList<>();
|
||||
|
||||
// 访问量
|
||||
VisitStatsVO pvStats = this.baseMapper.getPvStats();
|
||||
pvStats.setTitle("浏览量");
|
||||
pvStats.setType("pv");
|
||||
pvStats.setGranularityLabel("日");
|
||||
list.add(pvStats);
|
||||
|
||||
// 访客数
|
||||
VisitStatsVO uvStats = new VisitStatsVO();
|
||||
uvStats.setTitle("访客数");
|
||||
uvStats.setType("uv");
|
||||
uvStats.setTodayCount(100);
|
||||
uvStats.setTotalCount(2000);
|
||||
uvStats.setGrowthRate(BigDecimal.ZERO);
|
||||
uvStats.setGranularityLabel("日");
|
||||
list.add(uvStats);
|
||||
|
||||
// IP数
|
||||
VisitStatsVO ipStats = this.baseMapper.getIpStats();
|
||||
ipStats.setTitle("IP数");
|
||||
ipStats.setType("ip");
|
||||
ipStats.setGranularityLabel("日");
|
||||
list.add(ipStats);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.youlai.boot.system.converter.MenuConverter;
|
||||
import com.youlai.boot.system.mapper.SysMenuMapper;
|
||||
import com.youlai.boot.system.model.bo.RouteBO;
|
||||
import com.youlai.boot.infrastructure.generator.model.entity.GenConfig;
|
||||
import com.youlai.boot.system.model.entity.SysMenu;
|
||||
import com.youlai.boot.system.model.form.MenuForm;
|
||||
import com.youlai.boot.system.model.query.MenuQuery;
|
||||
import com.youlai.boot.system.model.vo.MenuVO;
|
||||
import com.youlai.boot.system.model.vo.RouteVO;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.enums.MenuTypeEnum;
|
||||
import com.youlai.boot.common.enums.StatusEnum;
|
||||
import com.youlai.boot.common.model.KeyValue;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.system.service.SysMenuService;
|
||||
import com.youlai.boot.system.service.SysRoleMenuService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 菜单业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2020/11/06
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
|
||||
|
||||
private final MenuConverter menuConverter;
|
||||
|
||||
private final SysRoleMenuService roleMenuService;
|
||||
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
*
|
||||
* @param queryParams {@link MenuQuery}
|
||||
*/
|
||||
@Override
|
||||
public List<MenuVO> listMenus(MenuQuery queryParams) {
|
||||
List<SysMenu> menus = this.list(new LambdaQueryWrapper<SysMenu>()
|
||||
.like(StrUtil.isNotBlank(queryParams.getKeywords()), SysMenu::getName, queryParams.getKeywords())
|
||||
.orderByAsc(SysMenu::getSort)
|
||||
);
|
||||
// 获取所有菜单ID
|
||||
Set<Long> menuIds = menus.stream()
|
||||
.map(SysMenu::getId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 获取所有父级ID
|
||||
Set<Long> parentIds = menus.stream()
|
||||
.map(SysMenu::getParentId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 获取根节点ID(递归的起点),即父节点ID中不包含在部门ID中的节点,注意这里不能拿顶级菜单 O 作为根节点,因为菜单筛选的时候 O 会被过滤掉
|
||||
List<Long> rootIds = parentIds.stream()
|
||||
.filter(id -> !menuIds.contains(id))
|
||||
.toList();
|
||||
|
||||
// 使用递归函数来构建菜单树
|
||||
return rootIds.stream()
|
||||
.flatMap(rootId -> buildMenuTree(rootId, menus).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归生成菜单列表
|
||||
*
|
||||
* @param parentId 父级ID
|
||||
* @param menuList 菜单列表
|
||||
* @return 菜单列表
|
||||
*/
|
||||
private List<MenuVO> buildMenuTree(Long parentId, List<SysMenu> menuList) {
|
||||
return CollectionUtil.emptyIfNull(menuList)
|
||||
.stream()
|
||||
.filter(menu -> menu.getParentId().equals(parentId))
|
||||
.map(entity -> {
|
||||
MenuVO menuVO = menuConverter.toVo(entity);
|
||||
List<MenuVO> children = buildMenuTree(entity.getId(), menuList);
|
||||
menuVO.setChildren(children);
|
||||
return menuVO;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单下拉数据
|
||||
*
|
||||
* @param onlyParent 是否只查询父级菜单 如果为true,排除按钮
|
||||
*/
|
||||
@Override
|
||||
public List<Option> listMenuOptions(boolean onlyParent) {
|
||||
List<SysMenu> menuList = this.list(new LambdaQueryWrapper<SysMenu>()
|
||||
.in(onlyParent, SysMenu::getType, MenuTypeEnum.CATALOG.getValue(), MenuTypeEnum.MENU.getValue())
|
||||
.orderByAsc(SysMenu::getSort)
|
||||
);
|
||||
return buildMenuOptions(SystemConstants.ROOT_NODE_ID, menuList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归生成菜单下拉层级列表
|
||||
*
|
||||
* @param parentId 父级ID
|
||||
* @param menuList 菜单列表
|
||||
* @return 菜单下拉列表
|
||||
*/
|
||||
private List<Option> buildMenuOptions(Long parentId, List<SysMenu> menuList) {
|
||||
List<Option> menuOptions = new ArrayList<>();
|
||||
|
||||
for (SysMenu menu : menuList) {
|
||||
if (menu.getParentId().equals(parentId)) {
|
||||
Option option = new Option(menu.getId(), menu.getName());
|
||||
List<Option> subMenuOptions = buildMenuOptions(menu.getId(), menuList);
|
||||
if (!subMenuOptions.isEmpty()) {
|
||||
option.setChildren(subMenuOptions);
|
||||
}
|
||||
menuOptions.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单路由列表
|
||||
*/
|
||||
@Override
|
||||
public List<RouteVO> listRoutes(Set<String> roles) {
|
||||
|
||||
if (CollectionUtil.isEmpty(roles)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<RouteBO> menuList = this.baseMapper.listRoutes(roles);
|
||||
return buildRoutes(SystemConstants.ROOT_NODE_ID, menuList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归生成菜单路由层级列表
|
||||
*
|
||||
* @param parentId 父级ID
|
||||
* @param menuList 菜单列表
|
||||
* @return 路由层级列表
|
||||
*/
|
||||
private List<RouteVO> buildRoutes(Long parentId, List<RouteBO> menuList) {
|
||||
List<RouteVO> routeList = new ArrayList<>();
|
||||
|
||||
for (RouteBO menu : menuList) {
|
||||
if (menu.getParentId().equals(parentId)) {
|
||||
RouteVO routeVO = toRouteVo(menu);
|
||||
List<RouteVO> children = buildRoutes(menu.getId(), menuList);
|
||||
if (!children.isEmpty()) {
|
||||
routeVO.setChildren(children);
|
||||
}
|
||||
routeList.add(routeVO);
|
||||
}
|
||||
}
|
||||
|
||||
return routeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据RouteBO创建RouteVO
|
||||
*/
|
||||
private RouteVO toRouteVo(RouteBO routeBO) {
|
||||
RouteVO routeVO = new RouteVO();
|
||||
// 获取路由名称
|
||||
String routeName = routeBO.getRouteName();
|
||||
if (StrUtil.isBlank(routeName)) {
|
||||
// 路由 name 需要驼峰,首字母大写
|
||||
routeName = StringUtils.capitalize(StrUtil.toCamelCase(routeBO.getRoutePath(), '-'));
|
||||
}
|
||||
// 根据name路由跳转 this.$router.push({name:xxx})
|
||||
routeVO.setName(routeName);
|
||||
|
||||
// 根据path路由跳转 this.$router.push({path:xxx})
|
||||
routeVO.setPath(routeBO.getRoutePath());
|
||||
routeVO.setRedirect(routeBO.getRedirect());
|
||||
routeVO.setComponent(routeBO.getComponent());
|
||||
|
||||
RouteVO.Meta meta = new RouteVO.Meta();
|
||||
meta.setTitle(routeBO.getName());
|
||||
meta.setIcon(routeBO.getIcon());
|
||||
meta.setHidden(StatusEnum.DISABLE.getValue().equals(routeBO.getVisible()));
|
||||
// 【菜单】是否开启页面缓存
|
||||
if (MenuTypeEnum.MENU.equals(routeBO.getType())
|
||||
&& ObjectUtil.equals(routeBO.getKeepAlive(), 1)) {
|
||||
meta.setKeepAlive(true);
|
||||
}
|
||||
meta.setAlwaysShow(ObjectUtil.equals(routeBO.getAlwaysShow(), 1));
|
||||
|
||||
String paramsJson = routeBO.getParams();
|
||||
// 将 JSON 字符串转换为 Map<String, String>
|
||||
if (StrUtil.isNotBlank(paramsJson)) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
Map<String, String> paramMap = objectMapper.readValue(paramsJson, new TypeReference<>() {
|
||||
});
|
||||
meta.setParams(paramMap);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("解析参数失败", e);
|
||||
}
|
||||
}
|
||||
routeVO.setMeta(meta);
|
||||
return routeVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/修改菜单
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean saveMenu(MenuForm menuForm) {
|
||||
|
||||
MenuTypeEnum menuType = menuForm.getType();
|
||||
|
||||
if (menuType == MenuTypeEnum.CATALOG) { // 如果是外链
|
||||
String path = menuForm.getRoutePath();
|
||||
if (menuForm.getParentId() == 0 && !path.startsWith("/")) {
|
||||
menuForm.setRoutePath("/" + path); // 一级目录需以 / 开头
|
||||
}
|
||||
menuForm.setComponent("Layout");
|
||||
} else if (menuType == MenuTypeEnum.EXTLINK) { // 如果是目录
|
||||
|
||||
menuForm.setComponent(null);
|
||||
}
|
||||
|
||||
SysMenu entity = menuConverter.toEntity(menuForm);
|
||||
String treePath = generateMenuTreePath(menuForm.getParentId());
|
||||
entity.setTreePath(treePath);
|
||||
|
||||
List<KeyValue> params = menuForm.getParams();
|
||||
// 路由参数 [{key:"id",value:"1"},{key:"name",value:"张三"}] 转换为 [{"id":"1"},{"name":"张三"}]
|
||||
if (CollectionUtil.isNotEmpty(params)) {
|
||||
entity.setParams(JSONUtil.toJsonStr(params.stream()
|
||||
.collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue))));
|
||||
} else {
|
||||
entity.setParams(null);
|
||||
}
|
||||
if (menuType != MenuTypeEnum.BUTTON) {
|
||||
Assert.isFalse(this.exists(new LambdaQueryWrapper<SysMenu>()
|
||||
.eq(SysMenu::getRouteName, entity.getRouteName())
|
||||
.ne(menuForm.getId() != null, SysMenu::getId, menuForm.getId())
|
||||
), "路由名称已存在");
|
||||
}
|
||||
|
||||
boolean result = this.saveOrUpdate(entity);
|
||||
if (result) {
|
||||
// 编辑刷新角色权限缓存
|
||||
if (menuForm.getId() != null) {
|
||||
roleMenuService.refreshRolePermsCache();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门路径生成
|
||||
*
|
||||
* @param parentId 父ID
|
||||
* @return 父节点路径以英文逗号(, )分割,eg: 1,2,3
|
||||
*/
|
||||
private String generateMenuTreePath(Long parentId) {
|
||||
if (SystemConstants.ROOT_NODE_ID.equals(parentId)) {
|
||||
return String.valueOf(parentId);
|
||||
} else {
|
||||
SysMenu parent = this.getById(parentId);
|
||||
return parent != null ? parent.getTreePath() + "," + parent.getId() : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改菜单显示状态
|
||||
*
|
||||
* @param menuId 菜单ID
|
||||
* @param visible 是否显示(1->显示;2->隐藏)
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean updateMenuVisible(Long menuId, Integer visible) {
|
||||
return this.update(new LambdaUpdateWrapper<SysMenu>()
|
||||
.eq(SysMenu::getId, menuId)
|
||||
.set(SysMenu::getVisible, visible)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单表单数据
|
||||
*
|
||||
* @param id 菜单ID
|
||||
* @return 菜单表单数据
|
||||
*/
|
||||
@Override
|
||||
public MenuForm getMenuForm(Long id) {
|
||||
SysMenu entity = this.getById(id);
|
||||
Assert.isTrue(entity != null, "菜单不存在");
|
||||
MenuForm formData = menuConverter.toForm(entity);
|
||||
// 路由参数字符串 {"id":"1","name":"张三"} 转换为 [{key:"id", value:"1"}, {key:"name", value:"张三"}]
|
||||
String params = entity.getParams();
|
||||
if (StrUtil.isNotBlank(params)) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
// 解析 JSON 字符串为 Map<String, String>
|
||||
Map<String, String> paramMap = objectMapper.readValue(params, new TypeReference<>() {
|
||||
});
|
||||
|
||||
// 转换为 List<KeyValue> 格式 [{key:"id", value:"1"}, {key:"name", value:"张三"}]
|
||||
List<KeyValue> transformedList = paramMap.entrySet().stream()
|
||||
.map(entry -> new KeyValue(entry.getKey(), entry.getValue()))
|
||||
.toList();
|
||||
|
||||
// 将转换后的列表存入 MenuForm
|
||||
formData.setParams(transformedList);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("解析参数失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*
|
||||
* @param id 菜单ID
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean deleteMenu(Long id) {
|
||||
boolean result = this.remove(new LambdaQueryWrapper<SysMenu>()
|
||||
.eq(SysMenu::getId, id)
|
||||
.or()
|
||||
.apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", id));
|
||||
|
||||
|
||||
// 刷新角色权限缓存
|
||||
if (result) {
|
||||
roleMenuService.refreshRolePermsCache();
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 为代码生成添加菜单
|
||||
*
|
||||
* @param parentMenuId 父菜单ID
|
||||
* @param genConfig 实体名称
|
||||
*/
|
||||
@Override
|
||||
public void saveMenu(Long parentMenuId, GenConfig genConfig) {
|
||||
SysMenu parentMenu = this.getById(parentMenuId);
|
||||
Assert.notNull(parentMenu, "上级菜单不存在");
|
||||
|
||||
String entityName = genConfig.getEntityName();
|
||||
|
||||
long count = this.count(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getRouteName, entityName));
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取父级菜单子菜单最带的排序
|
||||
SysMenu maxSortMenu = this.getOne(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, parentMenuId)
|
||||
.orderByDesc(SysMenu::getSort)
|
||||
.last("limit 1")
|
||||
);
|
||||
int sort = 1;
|
||||
if (maxSortMenu != null) {
|
||||
sort = maxSortMenu.getSort() + 1;
|
||||
}
|
||||
|
||||
|
||||
SysMenu menu = new SysMenu();
|
||||
menu.setParentId(parentMenuId);
|
||||
menu.setName(genConfig.getBusinessName());
|
||||
|
||||
menu.setRouteName(entityName);
|
||||
menu.setRoutePath(StrUtil.toSymbolCase(entityName, '-'));
|
||||
menu.setComponent(genConfig.getModuleName() + "/" + StrUtil.toSymbolCase(entityName, '-') + "/index");
|
||||
menu.setType(MenuTypeEnum.MENU);
|
||||
menu.setSort(sort);
|
||||
menu.setVisible(1);
|
||||
boolean result = this.save(menu);
|
||||
|
||||
if (result) {
|
||||
// 生成treePath
|
||||
String treePath = generateMenuTreePath(parentMenuId);
|
||||
menu.setTreePath(treePath);
|
||||
this.updateById(menu);
|
||||
|
||||
// 生成CURD按钮权限
|
||||
String permPrefix = genConfig.getModuleName() + ":" + StrUtil.lowerFirst(entityName) + ":";
|
||||
String[] actions = {"查询", "新增", "编辑", "删除"};
|
||||
String[] perms = {"query", "add", "edit", "delete"};
|
||||
|
||||
for (int i = 0; i < actions.length; i++) {
|
||||
SysMenu button = new SysMenu();
|
||||
button.setParentId(menu.getId());
|
||||
button.setType(MenuTypeEnum.BUTTON);
|
||||
button.setName(actions[i]);
|
||||
button.setPerm(permPrefix + perms[i]);
|
||||
button.setSort(i + 1);
|
||||
this.save(button);
|
||||
|
||||
// 生成 treepath
|
||||
button.setTreePath(treePath + "," + button.getId());
|
||||
this.updateById(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.mapper.SysRoleMenuMapper;
|
||||
import com.youlai.boot.system.model.bo.RolePermsBO;
|
||||
import com.youlai.boot.system.model.entity.SysRoleMenu;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.system.service.SysRoleMenuService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* 角色菜单业务实现
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenu> implements SysRoleMenuService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 初始化权限缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initRolePermsCache() {
|
||||
log.info("初始化权限缓存... ");
|
||||
refreshRolePermsCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存
|
||||
*/
|
||||
@Override
|
||||
public void refreshRolePermsCache() {
|
||||
// 清理权限缓存
|
||||
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, "*");
|
||||
|
||||
List<RolePermsBO> list = this.baseMapper.getRolePermsList(null);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
list.forEach(item -> {
|
||||
String roleCode = item.getRoleCode();
|
||||
Set<String> perms = item.getPerms();
|
||||
if (CollectionUtil.isNotEmpty(perms)) {
|
||||
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, roleCode, perms);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存
|
||||
*/
|
||||
@Override
|
||||
public void refreshRolePermsCache(String roleCode) {
|
||||
// 清理权限缓存
|
||||
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, roleCode);
|
||||
|
||||
List<RolePermsBO> list = this.baseMapper.getRolePermsList(roleCode);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> perms = rolePerms.getPerms();
|
||||
if (CollectionUtil.isNotEmpty(perms)) {
|
||||
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, roleCode, perms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存 (角色编码变更时调用)
|
||||
*/
|
||||
@Override
|
||||
public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) {
|
||||
// 清理旧角色权限缓存
|
||||
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, oldRoleCode);
|
||||
|
||||
// 添加新角色权限缓存
|
||||
List<RolePermsBO> list = this.baseMapper.getRolePermsList(newRoleCode);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> perms = rolePerms.getPerms();
|
||||
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, newRoleCode, perms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色权限集合
|
||||
*
|
||||
* @param roles 角色编码集合
|
||||
* @return 权限集合
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getRolePermsByRoleCodes(Set<String> roles) {
|
||||
return this.baseMapper.listRolePerms(roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色拥有的菜单ID集合
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 菜单ID集合
|
||||
*/
|
||||
@Override
|
||||
public List<Long> listMenuIdsByRoleId(Long roleId) {
|
||||
return this.baseMapper.listMenuIdsByRoleId(roleId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.converter.RoleConverter;
|
||||
import com.youlai.boot.system.mapper.SysRoleMapper;
|
||||
import com.youlai.boot.system.model.entity.SysRole;
|
||||
import com.youlai.boot.system.model.entity.SysRoleMenu;
|
||||
import com.youlai.boot.system.model.form.RoleForm;
|
||||
import com.youlai.boot.system.model.query.RolePageQuery;
|
||||
import com.youlai.boot.system.model.vo.RolePageVO;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.service.SysRoleMenuService;
|
||||
import com.youlai.boot.system.service.SysRoleService;
|
||||
import com.youlai.boot.system.service.SysUserRoleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 角色业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/6/3
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
|
||||
|
||||
private final SysRoleMenuService roleMenuService;
|
||||
private final SysUserRoleService userRoleService;
|
||||
private final RoleConverter roleConverter;
|
||||
|
||||
/**
|
||||
* 角色分页列表
|
||||
*
|
||||
* @param queryParams 角色查询参数
|
||||
* @return {@link Page< RolePageVO >} – 角色分页列表
|
||||
*/
|
||||
@Override
|
||||
public Page<RolePageVO> getRolePage(RolePageQuery queryParams) {
|
||||
// 查询参数
|
||||
int pageNum = queryParams.getPageNum();
|
||||
int pageSize = queryParams.getPageSize();
|
||||
String keywords = queryParams.getKeywords();
|
||||
|
||||
// 查询数据
|
||||
Page<SysRole> rolePage = this.page(new Page<>(pageNum, pageSize),
|
||||
new LambdaQueryWrapper<SysRole>()
|
||||
.and(StrUtil.isNotBlank(keywords),
|
||||
wrapper ->
|
||||
wrapper.like(StrUtil.isNotBlank(keywords), SysRole::getName, keywords)
|
||||
.or()
|
||||
.like(StrUtil.isNotBlank(keywords), SysRole::getCode, keywords)
|
||||
)
|
||||
.ne(!SecurityUtils.isRoot(), SysRole::getCode, SystemConstants.ROOT_ROLE_CODE) // 非超级管理员不显示超级管理员角色
|
||||
);
|
||||
|
||||
// 实体转换
|
||||
return roleConverter.toPageVo(rolePage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色下拉列表
|
||||
*
|
||||
* @return {@link List<Option>} – 角色下拉列表
|
||||
*/
|
||||
@Override
|
||||
public List<Option<Long>> listRoleOptions() {
|
||||
// 查询数据
|
||||
List<SysRole> roleList = this.list(new LambdaQueryWrapper<SysRole>()
|
||||
.ne(!SecurityUtils.isRoot(), SysRole::getCode, SystemConstants.ROOT_ROLE_CODE)
|
||||
.select(SysRole::getId, SysRole::getName)
|
||||
.orderByAsc(SysRole::getSort)
|
||||
);
|
||||
|
||||
// 实体转换
|
||||
return roleConverter.entities2Options(roleList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存角色
|
||||
*
|
||||
* @param roleForm 角色表单数据
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
public boolean saveRole(RoleForm roleForm) {
|
||||
|
||||
Long roleId = roleForm.getId();
|
||||
|
||||
// 编辑角色时,判断角色是否存在
|
||||
SysRole oldRole = null;
|
||||
if (roleId != null) {
|
||||
oldRole = this.getById(roleId);
|
||||
Assert.isTrue(oldRole != null, "角色不存在");
|
||||
}
|
||||
|
||||
String roleCode = roleForm.getCode();
|
||||
long count = this.count(new LambdaQueryWrapper<SysRole>()
|
||||
.ne(roleId != null, SysRole::getId, roleId)
|
||||
.and(wrapper ->
|
||||
wrapper.eq(SysRole::getCode, roleCode).or().eq(SysRole::getName, roleForm.getName())
|
||||
));
|
||||
Assert.isTrue(count == 0, "角色名称或角色编码已存在,请修改后重试!");
|
||||
|
||||
// 实体转换
|
||||
SysRole role = roleConverter.toEntity(roleForm);
|
||||
|
||||
boolean result = this.saveOrUpdate(role);
|
||||
if (result) {
|
||||
// 判断角色编码或状态是否修改,修改了则刷新权限缓存
|
||||
if (oldRole != null
|
||||
&& (
|
||||
!StrUtil.equals(oldRole.getCode(), roleCode) ||
|
||||
!ObjectUtil.equals(oldRole.getStatus(), roleForm.getStatus())
|
||||
)) {
|
||||
roleMenuService.refreshRolePermsCache(oldRole.getCode(), roleCode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色表单数据
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return {@link RoleForm} – 角色表单数据
|
||||
*/
|
||||
@Override
|
||||
public RoleForm getRoleForm(Long roleId) {
|
||||
SysRole entity = this.getById(roleId);
|
||||
return roleConverter.toForm(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改角色状态
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param status 角色状态(1:启用;0:禁用)
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
public boolean updateRoleStatus(Long roleId, Integer status) {
|
||||
|
||||
SysRole role = this.getById(roleId);
|
||||
Assert.isTrue(role != null, "角色不存在");
|
||||
|
||||
role.setStatus(status);
|
||||
boolean result = this.updateById(role);
|
||||
if (result) {
|
||||
// 刷新角色的权限缓存
|
||||
roleMenuService.refreshRolePermsCache(role.getCode());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除角色
|
||||
*
|
||||
* @param ids 角色ID,多个使用英文逗号(,)分割
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteRoles(String ids) {
|
||||
Assert.isTrue(StrUtil.isNotBlank(ids), "删除的角色ID不能为空");
|
||||
List<Long> roleIds = Arrays.stream(ids.split(","))
|
||||
.map(Long::parseLong)
|
||||
.toList();
|
||||
|
||||
for (Long roleId : roleIds) {
|
||||
SysRole role = this.getById(roleId);
|
||||
Assert.isTrue(role != null, "角色不存在");
|
||||
|
||||
// 判断角色是否被用户关联
|
||||
boolean isRoleAssigned = userRoleService.hasAssignedUsers(roleId);
|
||||
Assert.isTrue(!isRoleAssigned, "角色【{}】已分配用户,请先解除关联后删除", role.getName());
|
||||
|
||||
boolean deleteResult = this.removeById(roleId);
|
||||
if (deleteResult) {
|
||||
// 删除成功,刷新权限缓存
|
||||
roleMenuService.refreshRolePermsCache(role.getCode());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的菜单ID集合
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 菜单ID集合(包括按钮权限ID)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> getRoleMenuIds(Long roleId) {
|
||||
return roleMenuService.listMenuIdsByRoleId(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改角色的资源权限
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param menuIds 菜单ID集合
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean assignMenusToRole(Long roleId, List<Long> menuIds) {
|
||||
SysRole role = this.getById(roleId);
|
||||
Assert.isTrue(role != null, "角色不存在");
|
||||
|
||||
// 删除角色菜单
|
||||
roleMenuService.remove(
|
||||
new LambdaQueryWrapper<SysRoleMenu>()
|
||||
.eq(SysRoleMenu::getRoleId, roleId)
|
||||
);
|
||||
// 新增角色菜单
|
||||
if (CollectionUtil.isNotEmpty(menuIds)) {
|
||||
List<SysRoleMenu> roleMenus = menuIds
|
||||
.stream()
|
||||
.map(menuId -> new SysRoleMenu(roleId, menuId))
|
||||
.toList();
|
||||
roleMenuService.saveBatch(roleMenus);
|
||||
}
|
||||
|
||||
// 刷新角色的权限缓存
|
||||
roleMenuService.refreshRolePermsCache(role.getCode());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大范围的数据权限
|
||||
*
|
||||
* @param roles 角色编码集合
|
||||
* @return {@link Integer} – 数据权限范围
|
||||
*/
|
||||
@Override
|
||||
public Integer getMaximumDataScope(Set<String> roles) {
|
||||
Integer dataScope = this.baseMapper.getMaximumDataScope(roles);
|
||||
return dataScope;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.system.mapper.SysUserRoleMapper;
|
||||
import com.youlai.boot.system.model.entity.SysUserRole;
|
||||
import com.youlai.boot.system.service.SysUserRoleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole> implements SysUserRoleService {
|
||||
|
||||
/**
|
||||
* 保存用户角色
|
||||
*
|
||||
* @param userId
|
||||
* @param roleIds
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean saveUserRoles(Long userId, List<Long> roleIds) {
|
||||
|
||||
if (userId == null || CollectionUtil.isEmpty(roleIds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 用户原角色ID集合
|
||||
List<Long> userRoleIds = this.list(new LambdaQueryWrapper<SysUserRole>()
|
||||
.eq(SysUserRole::getUserId, userId))
|
||||
.stream()
|
||||
.map(SysUserRole::getRoleId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 新增用户角色
|
||||
List<Long> saveRoleIds;
|
||||
if (CollectionUtil.isEmpty(userRoleIds)) {
|
||||
saveRoleIds = roleIds;
|
||||
} else {
|
||||
saveRoleIds = roleIds.stream()
|
||||
.filter(roleId -> !userRoleIds.contains(roleId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<SysUserRole> saveUserRoles = saveRoleIds
|
||||
.stream()
|
||||
.map(roleId -> new SysUserRole(userId, roleId))
|
||||
.collect(Collectors.toList());
|
||||
this.saveBatch(saveUserRoles);
|
||||
|
||||
// 删除用户角色
|
||||
if (CollectionUtil.isNotEmpty(userRoleIds)) {
|
||||
List<Long> removeRoleIds = userRoleIds.stream()
|
||||
.filter(roleId -> !roleIds.contains(roleId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtil.isNotEmpty(removeRoleIds)) {
|
||||
this.remove(new LambdaQueryWrapper<SysUserRole>()
|
||||
.eq(SysUserRole::getUserId, userId)
|
||||
.in(SysUserRole::getRoleId, removeRoleIds)
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断角色是否存在绑定的用户
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return true:已分配 false:未分配
|
||||
*/
|
||||
@Override
|
||||
public boolean hasAssignedUsers(Long roleId) {
|
||||
int count = this.baseMapper.countUsersForRole(roleId);
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.enums.ContactType;
|
||||
import com.youlai.boot.infrastructure.mail.service.MailService;
|
||||
import com.youlai.boot.infrastructure.sms.service.SmsService;
|
||||
import com.youlai.boot.system.model.form.*;
|
||||
import com.youlai.boot.config.property.AliyunSmsProperties;
|
||||
import com.youlai.boot.system.converter.UserConverter;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.mapper.SysUserMapper;
|
||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||
import com.youlai.boot.system.model.bo.UserBO;
|
||||
import com.youlai.boot.system.model.entity.SysUser;
|
||||
import com.youlai.boot.system.model.query.UserPageQuery;
|
||||
import com.youlai.boot.system.model.dto.UserExportDTO;
|
||||
import com.youlai.boot.system.model.vo.UserInfoVO;
|
||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||
import com.youlai.boot.core.security.service.PermissionService;
|
||||
import com.youlai.boot.system.service.SysRoleMenuService;
|
||||
import com.youlai.boot.system.service.SysRoleService;
|
||||
import com.youlai.boot.system.service.SysUserRoleService;
|
||||
import com.youlai.boot.system.service.SysUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/1/14
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
|
||||
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
private final SysUserRoleService userRoleService;
|
||||
|
||||
private final UserConverter userConverter;
|
||||
|
||||
private final SysRoleMenuService roleMenuService;
|
||||
|
||||
private final SysRoleService roleService;
|
||||
|
||||
private final PermissionService permissionService;
|
||||
|
||||
private final SmsService smsService;
|
||||
|
||||
private final MailService mailService;
|
||||
|
||||
private final AliyunSmsProperties aliyunSmsProperties;
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link IPage<UserPageVO>} 用户分页列表
|
||||
*/
|
||||
@Override
|
||||
public IPage<UserPageVO> listPagedUsers(UserPageQuery queryParams) {
|
||||
|
||||
// 参数构建
|
||||
int pageNum = queryParams.getPageNum();
|
||||
int pageSize = queryParams.getPageSize();
|
||||
Page<UserBO> page = new Page<>(pageNum, pageSize);
|
||||
// 查询数据
|
||||
Page<UserBO> userPage = this.baseMapper.listPagedUsers(page, queryParams);
|
||||
|
||||
// 实体转换
|
||||
return userConverter.toPageVo(userPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户表单数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserForm getUserFormData(Long userId) {
|
||||
return this.baseMapper.getUserFormData(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean saveUser(UserForm userForm) {
|
||||
|
||||
String username = userForm.getUsername();
|
||||
|
||||
long count = this.count(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
|
||||
Assert.isTrue(count == 0, "用户名已存在");
|
||||
|
||||
// 实体转换 form->entity
|
||||
SysUser entity = userConverter.toEntity(userForm);
|
||||
|
||||
// 设置默认加密密码
|
||||
String defaultEncryptPwd = passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD);
|
||||
entity.setPassword(defaultEncryptPwd);
|
||||
|
||||
// 新增用户
|
||||
boolean result = this.save(entity);
|
||||
|
||||
if (result) {
|
||||
// 保存用户角色
|
||||
userRoleService.saveUserRoles(entity.getId(), userForm.getRoleIds());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateUser(Long userId, UserForm userForm) {
|
||||
|
||||
String username = userForm.getUsername();
|
||||
|
||||
long count = this.count(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUsername, username)
|
||||
.ne(SysUser::getId, userId)
|
||||
);
|
||||
Assert.isTrue(count == 0, "用户名已存在");
|
||||
|
||||
// form -> entity
|
||||
SysUser entity = userConverter.toEntity(userForm);
|
||||
|
||||
// 修改用户
|
||||
boolean result = this.updateById(entity);
|
||||
|
||||
if (result) {
|
||||
// 保存用户角色
|
||||
userRoleService.saveUserRoles(entity.getId(), userForm.getRoleIds());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param idsStr 用户ID,多个以英文逗号(,)分割
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteUsers(String idsStr) {
|
||||
Assert.isTrue(StrUtil.isNotBlank(idsStr), "删除的用户数据为空");
|
||||
// 逻辑删除
|
||||
List<Long> ids = Arrays.stream(idsStr.split(","))
|
||||
.map(Long::parseLong)
|
||||
.collect(Collectors.toList());
|
||||
return this.removeByIds(ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名获取认证信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 用户认证信息 {@link UserAuthInfo}
|
||||
*/
|
||||
@Override
|
||||
public UserAuthInfo getUserAuthInfo(String username) {
|
||||
UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfo(username);
|
||||
if (userAuthInfo != null) {
|
||||
Set<String> roles = userAuthInfo.getRoles();
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
Set<String> perms = roleMenuService.getRolePermsByRoleCodes(roles);
|
||||
userAuthInfo.setPerms(perms);
|
||||
}
|
||||
|
||||
// 获取最大范围的数据权限
|
||||
Integer dataScope = roleService.getMaximumDataScope(roles);
|
||||
userAuthInfo.setDataScope(dataScope);
|
||||
}
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取导出用户列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link List< UserExportDTO >} 导出用户列表
|
||||
*/
|
||||
@Override
|
||||
public List<UserExportDTO> listExportUsers(UserPageQuery queryParams) {
|
||||
return this.baseMapper.listExportUsers(queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户信息
|
||||
*
|
||||
* @return {@link UserInfoVO} 用户信息
|
||||
*/
|
||||
@Override
|
||||
public UserInfoVO getCurrentUserInfo() {
|
||||
|
||||
String username = SecurityUtils.getUsername();
|
||||
|
||||
// 获取登录用户基础信息
|
||||
SysUser user = this.getOne(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUsername, username)
|
||||
.select(
|
||||
SysUser::getId,
|
||||
SysUser::getUsername,
|
||||
SysUser::getNickname,
|
||||
SysUser::getAvatar
|
||||
)
|
||||
);
|
||||
// entity->VO
|
||||
UserInfoVO userInfoVO = userConverter.toUserInfoVo(user);
|
||||
|
||||
// 用户角色集合
|
||||
Set<String> roles = SecurityUtils.getRoles();
|
||||
userInfoVO.setRoles(roles);
|
||||
|
||||
// 用户权限集合
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
Set<String> perms = permissionService.getRolePermsFormCache(roles);
|
||||
userInfoVO.setPerms(perms);
|
||||
}
|
||||
return userInfoVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人中心用户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserProfileVO getUserProfile(Long userId) {
|
||||
UserBO entity = this.baseMapper.getUserProfile(userId);
|
||||
return userConverter.toProfileVO(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改个人中心用户信息
|
||||
*
|
||||
* @param formData 表单数据
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean updateUserProfile(UserProfileForm formData) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
SysUser entity = userConverter.toEntity(formData);
|
||||
entity.setId(userId);
|
||||
return this.updateById(entity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param data 密码修改表单数据
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean changePassword(Long userId, PasswordChangeForm data) {
|
||||
|
||||
SysUser user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
String oldPassword = data.getOldPassword();
|
||||
|
||||
// 校验原密码
|
||||
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||||
throw new BusinessException("原密码错误");
|
||||
}
|
||||
// 新旧密码不能相同
|
||||
if (passwordEncoder.matches(data.getNewPassword(), user.getPassword())) {
|
||||
throw new BusinessException("新密码不能与原密码相同");
|
||||
}
|
||||
|
||||
String newPassword = data.getNewPassword();
|
||||
return this.update(new LambdaUpdateWrapper<SysUser>()
|
||||
.eq(SysUser::getId, userId)
|
||||
.set(SysUser::getPassword, passwordEncoder.encode(newPassword))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param password 密码重置表单数据
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean resetPassword(Long userId, String password) {
|
||||
return this.update(new LambdaUpdateWrapper<SysUser>()
|
||||
.eq(SysUser::getId, userId)
|
||||
.set(SysUser::getPassword, passwordEncoder.encode(password))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*
|
||||
* @param contact 联系方式 手机号/邮箱
|
||||
* @param type 联系方式类型 {@link ContactType}
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean sendVerificationCode(String contact, ContactType type) {
|
||||
|
||||
// 随机生成4位验证码
|
||||
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||
// 发送验证码
|
||||
|
||||
String verificationCodePrefix = null;
|
||||
switch (type) {
|
||||
case MOBILE:
|
||||
// 获取修改密码的模板code
|
||||
String changePasswordSmsTemplateCode = aliyunSmsProperties.getTemplateCodes().get("changePassword");
|
||||
smsService.sendSms(contact, changePasswordSmsTemplateCode, "[{\"code\":\"" + code + "\"}]");
|
||||
verificationCodePrefix = RedisConstants.MOBILE_VERIFICATION_CODE_PREFIX;
|
||||
break;
|
||||
case EMAIL:
|
||||
mailService.sendSimpleMail(contact, "验证码", "您的验证码是:" + code);
|
||||
verificationCodePrefix = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX;
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException("不支持的联系方式类型");
|
||||
}
|
||||
// 存入 redis 用于校验, 5分钟有效
|
||||
redisTemplate.opsForValue().set(verificationCodePrefix + contact, code, 5, TimeUnit.MINUTES );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改当前用户手机号码
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean bindMobile(MobileBindingForm data) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
SysUser user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
// 校验验证码
|
||||
String verificationCode = data.getCode();
|
||||
String contact = data.getMobile();
|
||||
String verificationCodeKey = RedisConstants.MOBILE_VERIFICATION_CODE_PREFIX + contact;
|
||||
String code = redisTemplate.opsForValue().get(verificationCodeKey);
|
||||
if (!verificationCode.equals(code)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
// 更新手机号码
|
||||
return this.update(new LambdaUpdateWrapper<SysUser>()
|
||||
.eq(SysUser::getId, userId)
|
||||
.set(SysUser::getMobile, contact)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改当前用户邮箱
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean bindEmail(EmailChangeForm data) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
SysUser user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
// 校验验证码
|
||||
String verificationCode = data.getCode();
|
||||
String email = data.getEmail();
|
||||
String verificationCodeKey = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX + email;
|
||||
String code = redisTemplate.opsForValue().get(verificationCodeKey);
|
||||
if (!verificationCode.equals(code)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
// 更新邮箱
|
||||
return this.update(new LambdaUpdateWrapper<SysUser>()
|
||||
.eq(SysUser::getId, userId)
|
||||
.set(SysUser::getEmail, email)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.youlai.boot.system.service.impl;
|
||||
|
||||
import com.youlai.boot.system.event.UserConnectionEvent;
|
||||
import com.youlai.boot.system.service.WebsocketService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class WebsocketServiceImpl implements WebsocketService {
|
||||
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
private final Set<String> onlineUsers = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public void addUser(String username) {
|
||||
onlineUsers.add(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUser(String username) {
|
||||
onlineUsers.remove(username);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleUserConnectionEvent(UserConnectionEvent event) {
|
||||
String username = event.getUsername();
|
||||
if (event.isConnected()) {
|
||||
onlineUsers.add(username);
|
||||
log.info("User connected: {}", username);
|
||||
} else {
|
||||
onlineUsers.remove(username);
|
||||
log.info("User disconnected: {}", username);
|
||||
}
|
||||
// 推送在线用户人数
|
||||
messagingTemplate.convertAndSend("/topic/onlineUserCount", onlineUsers.size());
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 5000)
|
||||
public void sendOnlineUserCount() {
|
||||
messagingTemplate.convertAndSend("/topic/onlineUserCount", onlineUsers.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user