业务情景:
根据用户标识userId查询菜单列表。首先查询redis是否有数据,有数据取出,无数据在MySQL中查询,并将结果保存到redis
代码一
package top.lookupstar.services.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import top.lookupstar.constant.ManagerConstants;
import top.lookupstar.domain.SysMenu;
import top.lookupstar.mapper.SysMenuMapper;
import top.lookupstar.services.SysMenuService;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
@Resource
private SysMenuMapper sysMenuMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Set<SysMenu> querySysMenuListByUserId(Long userId) {
// 从redis中查询菜单权限
String jsonStr = stringRedisTemplate.opsForValue().get(ManagerConstants.LOGON_SYS_MENU_PREFIX + userId);
// 判断是否有值
Set<SysMenu> menus = null;
if (StringUtils.hasText(jsonStr)) {
// 有值,将json格式菜单权限字符串转换为对象
List<SysMenu> sysMenu = JSONObject.parseArray(jsonStr, SysMenu.class);
// 将List集合转换成Set集合
menus = sysMenu.stream().collect(Collectors.toSet());
} else {
// 没有,去查询数据库.并将结果存放到redis
Set<SysMenu> sysMenus = sysMenuMapper.selectSysMenuByUserId(userId);
// 将set集合转换为树结构 过滤转换
menus = transitionTree(sysMenus, 0L);
stringRedisTemplate.opsForValue().set(ManagerConstants.LOGON_SYS_MENU_PREFIX + userId, JSONObject.toJSONString(menus));
}
return menus;
}
/**
* 集合转换树
* @param menus
* @param pid
* @return
*/
private Set<SysMenu> transitionTree(Set<SysMenu> menus, Long pid) {
// 未知菜单深度
Set<SysMenu> roots = menus.stream()
.filter(m -> m.getParentId().equals(pid))
.collect(Collectors.toSet());
// 获取字节点集合
roots.forEach(r -> {
Set<SysMenu> child = transitionTree(menus, r.getMenuId());
r.setList(child);
});
return roots;
}
}
代码二
@Override
public Set<SysMenu> querySysMenuListByUserId(Long userId) {
// 从redis中查询菜单权限
String jsonStr = stringRedisTemplate.opsForValue().get(ManagerConstants.LOGON_SYS_MENU_PREFIX + userId);
// 判断是否有值
Set<SysMenu> menus = null;
if (StringUtils.hasText(jsonStr)) {
// 有值,将json格式菜单权限字符串转换为对象
List<SysMenu> sysMenu = JSONObject.parseArray(jsonStr, SysMenu.class);
// 将List集合转换成Set集合
menus = sysMenu.stream().collect(Collectors.toSet());
} else {
// 没有,去查询数据库.并将结果存放到redis
menus = sysMenuMapper.selectSysMenuByUserId(userId);
// 将set集合转换为树结构 过滤转换
stringRedisTemplate.opsForValue().set(ManagerConstants.LOGON_SYS_MENU_PREFIX + userId, JSONObject.toJSONString(menus));
}
return transitionTree(menus, 0L);
}
起因:前端显示菜单顺序异常。
初次调用接口查询到的数据显示顺序正常,再次调用接口查询,返回的是redis里面的数据,前端显示顺序不符合预期,排序字段没有生效。
原因分析:
代码一,从redis查询到的记录先是从JSON字符串转换成list集合,此时数据顺序依然是转换成树结构的顺序,list转换set顺序变化。
前端接收到数据并没有按照ordernum字段的数字排序显示,而是直接显示后端数据导致显示结果异常。
解决方案1:
代码二所示,将从MySQL查询的结果,不转换存入redis中,转换树结构最后执行。这样每次返回的数据顺序不变
解决方案2:
将querySysMenuListByUserId返回的集合转换为list集合,并按照orderNum字段的数字大小排序。
解决方案3:
后端代码不变,前端对接收到的数据排序