后台项目改进点
关于项目中实现后台模块中的各个菜单及其子菜单的实现
1.首先查询对应用户的菜单
/** * 根据用户id查询相关的权限菜单信息 * @param userId 用户id * @return 返回符合要求的val */ @Override public List<Menu> selectRouterMenuTreeByUserId(Long userId) { MenuMapper menuMapper = getBaseMapper(); List<Menu> menus = null; //封装Menu //如果是管理员,返回所有的菜单 if(SecurityUtils.isAdmin()){ menus = menuMapper.selectAllRouterMenu(); } else{ //如果不是管理员 那么返回对应有权限的菜单按钮 menus = menuMapper.selectRouterMenuTreeByUserId(userId); } //通过上述得到的Menu无法得到我们需要的父子菜单管理,所以我们需要通过(buildMenuTree)来构建这种父子菜单关系 //构建Tree //先找出第一层的菜单,接着找到他们的子菜单,然后就可以设置children属性中 List<Menu> menuTree = buildMenuTree(menus,0L); return menuTree; }
2.接着对于那些第一级菜单进行查询(parentId == 1)
/** * 构建菜单的父子菜单关系 * <br> * 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段 * @param menus 传入的方法 * @param parentId 父菜单id * @return */ private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) { //用常规的方法 List<Menu> test = new ArrayList<>(); for(Menu menu : menus){ //查询出那些 父菜单的id为 【 1】 的 ,也就是第一级菜单 if(menu.getParentId().equals(parentId)){ //获取这些菜单的子菜单 Menu menu1 = menu.setChildren(getChildren(menu, menus)); test.add(menu1); } } return test; }
3.查询对应的子菜单
/** * 获取传入参数的子menu的list集合 * 在menus中找打当前传入的menu的子菜单 * @param menu 获取它的子菜单 * @param menus 全部菜单集合 */ private List<Menu> getChildren(Menu menu, List<Menu> menus){ List<Menu> test1 = new ArrayList<>(); for (Menu menu1 : menus){ if(menu1.getParentId().equals(menu.getId())){ test1.add(menu1); } } return test1; }
改进
在上述我们的代码中,如果仅仅是实现两层菜单还可以满足, 但是如果出现菜单层级是3 、4、5…等情况我上述实现的代码就无法满足。
这是我们就需要使用到递归算法 但是这里如果单单使用递归好像很难实现
所以我们在这里可以使用函数式编程,然后在其中套用递归来实现
/** * 构建菜单的父子菜单关系 * <br> * 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段 * @param menus 传入的方法 * @param parentId 父菜单id * @return */ private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) { List<Menu> menuList = menus.stream()//通过这样筛选就可以得到第一层级的menu .filter(menu -> menu.getParentId().equals(parentId)) /* 传入的menus是得到了第一层的menus(相当于Tree中的root节点),然后需要设置他的子菜单(left 和 right) 因为menus中有所有的菜单(父子都有), 所以我们在设置left和right时需要找到他们的子菜单 所以就调用getChildren找到left或者right的子菜单,然后得到之后再设置给他们 */ .map(menu -> menu.setChildren(getChildren(menu, menus))) .collect(Collectors.toList()); return menuList; }
/** * 获取传入参数的子menu的list集合 * 在menus中找打当前传入的menu的子菜单 * @param menu 获取它的子菜单 * @param menus 全部菜单集合 */ private List<Menu> getChildren(Menu menu, List<Menu> menus){ List<Menu> children = menus.stream() .filter(menu1 -> menu1.getParentId().equals(menu.getId())) .map(menu1 -> menu1.setChildren(getChildren(menu1,menus))) //如果有很多的子菜单,那么就可以用到这个递归 .collect(Collectors.toList()); return children; }
通过这样的该进,我们就可以实现多层菜单的查询
查询标签列表
接口
需求 :
提供标签功能,一个文章可以有多个标签。
在后台需要分页查询标签功能,要求能够根据签名进行分页查询。后期可以增加备注查询等需求
注意 :不要把删除了的标签查询出来
实现
/** * 标签请求 */ @RestController @RequestMapping("/content/tag") public class TagController { @Resource private TagService tagService; /** *提供标签功能,一个文章可以有多个标签。 * * 在后台需要分页查询标签功能,要求能够根据签名进行分页查询。**后期可以增加备注查询等需求** * * 注意 :不要把删除了的标签查询出来 * @param pageNum 第几页 * @param pageSize 分页大小 // * @param name 标签名 // * @param remark 备注 * @return */ @GetMapping("/list") public ResponseResult<PageVo> list(int pageNum, int pageSize, TagListDto tagListDto){ return tagService.pageTagList(pageNum,pageSize,tagListDto); } }
service层实现
/** * 标签表服务接口 * @author ray2310 */ @Service("tagService") public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagService { //实现分页tag列表 @Override public ResponseResult<PageVo> pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) { //分页查询 Page<Tag> page = new Page<>(); page.setCurrent(pageNum); page.setSize(pageSize); LambdaQueryWrapper<Tag> wrapper = new LambdaQueryWrapper<>(); //如果他们有值 ,那么就会调用这个方法, 如果没有就不会调用 wrapper.eq(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName()); wrapper.eq(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark()); page(page,wrapper); PageVo pageVo = new PageVo(page.getRecords(),page.getTotal()); //封装数据返回 return ResponseResult.okResult(pageVo); } }
新增标签
接口
实现
//TODO 新增标签, 测试时需要在数据库记录中有创建时间、更新时间、创建人、创建人字段 @PostMapping public ResponseResult addTag(@RequestBody TagListDto tagListDto){ return tagService.addTag(tagListDto); }
serivce层
//todo 新增标签需求 需要在数据库记录中有创建时间、更新时间、创建人、创建人字段 @Override public ResponseResult addTag(TagListDto tagListDto) { //1. 接收请求信息,判断信息是否为空 if(ObjectUtils.isEmpty(tagListDto)){ return ResponseResult.errorResult(AppHttpCodeEnum.TAG_ERROR); } //2. 获取标签创建者 、获取创建时间 Long userId = SecurityUtils.getUserId(); //3. 将的到的信息转换为tag 存储到数据库中 Tag tag = new Tag(); tag.setName(tagListDto.getName()); tag.setRemark(tagListDto.getRemark()); tag.setCreateBy(userId); save(tag); return ResponseResult.okResult(tag); }
删除标签
接口
实现
@DeleteMapping("/{id}") public ResponseResult deleteTag(@PathVariable Long id){ return tagService.deleteTag(id); }
service层
//todo 删除标签需求 需要设置逻辑删除 也就是 //`del_flag` int DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)', @Override public ResponseResult deleteTag(Long id) { //1. 从数据库中查找要删除的id //2. 修改其中的delFlag = 1 //返回删除信息 UpdateWrapper<Tag> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("id",id); updateWrapper.set("del_flag",1); update(updateWrapper); return ResponseResult.okResult(); }
修改标签
接口
1.先获取接口信息
2.修改接口
实现
首先获取指定id的标签
@GetMapping("/{id}") public ResponseResult getTagById(@PathVariable Long id){ return tagService.getTagById(id); }
//todo 获取需要修改的标签信息 @Override public ResponseResult getTagById(Long id) { Tag tag = getById(id); TagDto dto = BeanCopyUtils.copyBean(tag, TagDto.class); return ResponseResult.okResult(dto); }
修改获取的内容
@PutMapping public ResponseResult updateTag(@RequestBody TagDto tagDto){ return tagService.updateTag(tagDto); }
//todo 修改信息 @Override public ResponseResult updateTag(TagDto tagDto) { System.out.println(tagDto.toString()); UpdateWrapper<Tag> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("id",tagDto.getId()); updateWrapper.set("name",tagDto.getName()); updateWrapper.set("remark",tagDto.getRemark()); update(updateWrapper); return ResponseResult.okResult(); }
写文章
接口
首先获取所有的分类信息接口
获取所有的标签请求接口
上传图片接口
写博文接口
实现
首先获取所有的分类信息接口
//todo 获取所有分类信息 @GetMapping("/category/listAllCategory") public ResponseResult listAllCategory(){ return categoryService.listAllCategory(); }
@Override public ResponseResult listAllCategory() { //查询出所有没有删除的分类 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Category::getStatus, SystemConstants.CATEGORY_STATUS);//没有被禁用的 queryWrapper.eq(Category::getDelFlag,SystemConstants.CATEGORY_NOTDEL);//没有被删除的 List<Category> list = list(queryWrapper); List<CategoryVo> categoryVo1s = BeanCopyUtils.copyBeanList(list, CategoryVo.class); return ResponseResult.okResult(categoryVo1s); }
获取所有的标签信息
//todo 获取所有的标签信息 @GetMapping("/tag/listAllTag") public ResponseResult listAllTag(){ return tagService.listAllTag(); }
//todo 获取所有的标签,不分页的 @Override public ResponseResult listAllTag() { //查询出所有没有删除的标签 LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Tag::getDelFlag, SystemConstants.TAG_NOTDEL); List<Tag> list = list(queryWrapper); List<TagVo> tagVos = BeanCopyUtils.copyBeanList(list, TagVo.class); return ResponseResult.okResult(tagVos); }
上传图片接口
@RestController public class UploadController { @Autowired private UploadService uploadService; @PostMapping("/upload") public ResponseResult uploadImg(MultipartFile img){ return uploadService.uploadImg(img); } }
package com.blog.service.impl; import com.blog.domain.ResponseResult; import com.blog.enums.AppHttpCodeEnum; import com.blog.service.UploadService; import com.google.gson.Gson; import com.qiniu.common.QiniuException; import com.qiniu.http.Response; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.FileInputStream; import java.io.InputStream; /** * 上传文件到七牛云 */ @ConfigurationProperties(prefix = "oss") @Service @Data public class UploadServiceImpl implements UploadService { //todo 实现文件的上传 @Override public ResponseResult uploadImg(MultipartFile img) { //判断文件的大小 //获取原始文件名进行判断 String originalFilename = img.getOriginalFilename(); if(!originalFilename.endsWith(".png") && !originalFilename.endsWith(".jpg")){ return ResponseResult.errorResult(AppHttpCodeEnum.FILE_TYPE_ERROR); } //如果通过,上传文件到oss String url = uploadOSS(img); return ResponseResult.okResult(url); } private String accessKey; private String secretKey; private String bucket; private String uploadOSS(MultipartFile imgFile){ //构造一个带指定 Region 对象的配置类 Configuration cfg = new Configuration(Region.autoRegion()); //...其他参数参考类注释 UploadManager uploadManager = new UploadManager(cfg); //默认不指定key的情况下,以文件内容的hash值作为文件名 //images目录下的文件 String originalFilename = imgFile.getOriginalFilename(); String key = "images/"+originalFilename; try { //将前端传过来的imgFile文件转换成一个inputStream,然后 InputStream inputStream = imgFile.getInputStream(); Auth auth = Auth.create(accessKey, secretKey); String upToken = auth.uploadToken(bucket); try { Response response = uploadManager.put(inputStream,key,upToken,null, null); //解析上传成功的结果 DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); System.out.println(putRet.key); System.out.println(putRet.hash); } catch (QiniuException ex) { Response r = ex.response; System.err.println(r.toString()); try { System.err.println(r.bodyString()); } catch (QiniuException ex2) { //ignore } } } catch (Exception ex) { //ignore } //文件地址 return "http://rrpanx30j.hd-bkt.clouddn.com/images/"+ originalFilename; } }
写文章实现
@PostMapping("/article") public ResponseResult writeArticle(@RequestBody ArticleVo articleVo){ return articleService.writeArticle(articleVo); }
//todo 后台写文章详情 @Override @Transactional //添加事务 public ResponseResult writeArticle(ArticleVo articleVo) { Article article = BeanCopyUtils.copyBean(articleVo, Article.class); save(article); //将标签id的集合存入标签文章集合表中 List<ArticleTag> collect = articleVo.getTags().stream().map(tagId -> new ArticleTag(article.getId(), tagId)).collect(Collectors.toList()); articleTagService.saveBatch(collect); return ResponseResult.okResult(); }
修改文章
需求 :点击修改文章时能够跳转回到写博文页面
回显该文章的全部信息
用户可以在该页面进行修改博文信息,点击更新后可以实现修改文章
接口
根据id获取博文
修改博文
实现
按文章id查询文章回显数据
//todo 获取要更新的博文 @GetMapping("/{id}") public ResponseResult updateBefore(@PathVariable Long id){ return articleService.updateBefore(id); }
/** * 后台更新博文前获取博文所有信息 * @param id 文章id * @return 返回所有信息 */ @Override public ResponseResult updateBefore(Long id) { //1. 首先根据id获取所有信息 AdminArticleVo articleVo = BeanCopyUtils.copyBean(getById(id), AdminArticleVo.class); //2. 获取所有的标签id,然后找出我们需要的 List<Long> ids = articleTagService.selectByArticleId(id); articleVo.setTags(ids); //2. 根据文章id 获取其所有的标签tags System.out.println(articleVo); //3. 封装返回 return ResponseResult.okResult(articleVo); }
修改更新
//todo 更新文章 @PutMapping public ResponseResult updateNow(@RequestBody AdminArticleVo articleVo){ return articleService.updateNow(articleVo); }
/** * 首先更新请求 * @param articleVo 需要更新的文章 * @return */ @Override public ResponseResult updateNow(AdminArticleVo articleVo) { // UpdateWrapper<Article> wrapper = new UpdateWrapper<>(); // wrapper.eq("id",articleVo.getId()); Article article = BeanCopyUtils.copyBean(articleVo, Article.class); // wrapper.set("id",articleVo.getId()); // wrapper.set("title",articleVo.getTitle()); // wrapper.set("content",articleVo.getContent()); // wrapper.set("summary",articleVo.getSummary()); // wrapper.set("category_id",articleVo.getCategoryId()); // wrapper.set("thumbnail",articleVo.getThumbnail()); // wrapper.set("is_top",articleVo.getIsTop()); // wrapper.set("status",articleVo.getStatus()); // wrapper.set("view_count",articleVo.getViewCount()); // wrapper.set("is_comment",articleVo.getIsComment()); // wrapper.set("update_by",articleVo.getUpdateBy()); // wrapper.set("update_time",articleVo.getUpdateTime()); // update(wrapper); updateById(article); //先删除对应的映射关系 articleTagService.deleteByArticleId(articleVo.getId(),articleVo.getTags()); //然后重新添加新增的标签映射关系 List<ArticleTag> collect = articleVo.getTags().stream().map(tagId -> new ArticleTag(article.getId(), tagId)).collect(Collectors.toList()); articleTagService.saveBatch(collect); return ResponseResult.okResult(); }
@Service public class ArticleTagServiceImpl extends ServiceImpl<ArticleTagMapper, ArticleTag> implements ArticleTagService { @Override public List<Long> selectByArticleId(Long id){ List<ArticleTag> list = list(); List<Long> ids = new ArrayList<>(); for(ArticleTag articleTag : list){ if(articleTag.getArticleId().equals(id)){ Long tagId = articleTag.getTagId(); ids.add(tagId); } } return ids; } @Override public void deleteByArticleId(Long id,List<Long> ids){ LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ArticleTag::getArticleId,id); remove(wrapper); } }
删除文章
需求 ,删除文章是指逻辑删除,而不是真的删除
接口
实现
@DeleteMapping("/{id}") public ResponseResult deleteArticleById(@PathVariable Long id){ return articleService.deleteArticleById(id); }
/** * 根据id逻辑删除文章 * @param id 文章id * @return 删除结果 */ @Override public ResponseResult deleteArticleById(Long id) { UpdateWrapper<Article> wrapper = new UpdateWrapper<>(); wrapper.eq("id",id); wrapper.set("del_flag",SystemConstants.DELETE); update(wrapper); return ResponseResult.okResult(); }
文章列表
接口
实现
@GetMapping("/article/list") public ResponseResult articleList(int pageNum, int pageSize, ArticleSummaryDto articleSummary){ return articleService.getAllArticle(pageNum,pageSize,articleSummary); }
//todo 后台博文获取所有博文 @Override public ResponseResult getAllArticle(int pageNum, int pageSize, ArticleSummaryDto articleSummary) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); //如果有categoryId ,那么查询和传入的就需要相同 //状态 : 正式发布 queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_PUT); //置顶的文章(对isTop进行排序) //分页查询 Page<Article> pageN = new Page<>(pageNum,pageSize); Page<Article> page = page(pageN, queryWrapper); //查询categoryName ,因为我们封装的是categoryName,但是查询出来的确实categoryId,所以需要在进行查询 List<Article> articles = page.getRecords(); List<AdminArticleVo> articleVos = BeanCopyUtils.copyBeanList(articles, AdminArticleVo.class); PageVo pageVo = new PageVo(articleVos, page.getTotal()); return ResponseResult.okResult(pageVo); }
导出Excel文件
需求 : 将我们需要的文件 ,比如标签信息等导出成为一个Excel文件
接口
实现
@GetMapping("/export") public void exportExcel(HttpServletResponse response){ //1. 设置下载文件的请求头 try { WebUtils.setDownLoadHeader("分类.xlsx",response); List<CategoryVo> list = categoryService.listAllCategory(); //2. 获取导出的数据 //3. 把数据写道Excel List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(list, ExcelCategoryVo.class); EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出") .doWrite(excelCategoryVos); //4. 如果出现异常响应json格式 } catch (Exception e) { ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR); WebUtils.renderString(response, JSON.toJSONString(result)); } }
权限控制
只针对有权限的用户访问能够访问的信息
操作
1.在springSecurity中添加
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {
2.在需要访问的方法上加
3.封装权限到loginUser中
package com.blog.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.blog.domain.entity.LoginUser; import com.blog.domain.entity.User; import com.blog.mapper.MenuMapper; import com.blog.mapper.UserMapper; import com.blog.utils.SystemConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.Objects; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Resource private MenuMapper menuMapper; @Resource private UserMapper userMapper; /** * 使用该方法来重写用户登录的校验工作 * @param username 传入username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名查询数据库用户信息 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUserName,username); User user = userMapper.selectOne(queryWrapper); //判断是否查询到 : 未查到抛出异常 : 查到然后作比较 if(Objects.isNull(user)){ throw new RuntimeException("用户不存在!!!"); } /** * 还需要做的 : 后台用户查询权限信息封装 * */ //用户是管理员 if(user.getType().equals(SystemConstants.ADMIN)){ //查询用户权限集合 List<String> perms = menuMapper.selectPermsByUserId(user.getId()); //封装到loginUser return new LoginUser(user,perms); } //返回用户信息 return new LoginUser(user,null); } }