做网站用哪个预装系统,网站编程技术有哪些,展厅布局设计平面图,网店美工有什么重要作用四、后台模块-动态路由
实现了这个动态路由功能之后#xff0c;就能在浏览器web页面登录进博客管理后台了
1. 接口分析
后台系统需要能实现不同的用户权限可以看到不同的功能#xff0c;即左侧的导航栏 请求方式 请求地址 请求头 GET /getRouters 需要token请求头
…四、后台模块-动态路由
实现了这个动态路由功能之后就能在浏览器web页面登录进博客管理后台了
1. 接口分析
后台系统需要能实现不同的用户权限可以看到不同的功能即左侧的导航栏 请求方式 请求地址 请求头 GET /getRouters 需要token请求头
响应格式如下: 前端为了实现动态路由的效果需要后端有接口能返回用户所能访问的菜单数据。注意: 返回的菜单数据需要体现父子菜单的层级关系
如果用户id为1代表管理员menus中需要有所有菜单类型为C或者M的C表示菜单M表示目录状态为正常的未被删除的权限
注意这里不返回F的原因是按钮不属于菜单管理所以接口不用返回
响应体如下
{code:200,data:{menus:[{children:[],component:content/article/write/index,createTime:2022-01-08 11:39:58,icon:build,id:2023,menuName:写博文,menuType:C,orderNum:0,parentId:0,path:write,perms:content:article:writer,status:0,visible:0},{children:[{children:[],component:system/user/index,createTime:2021-11-12 18:46:19,icon:user,id:100,menuName:用户管理,menuType:C,orderNum:1,parentId:1,path:user,perms:system:user:list,status:0,visible:0},{children:[],component:system/role/index,createTime:2021-11-12 18:46:19,icon:peoples,id:101,menuName:角色管理,menuType:C,orderNum:2,parentId:1,path:role,perms:system:role:list,status:0,visible:0},{children:[],component:system/menu/index,createTime:2021-11-12 18:46:19,icon:tree-table,id:102,menuName:菜单管理,menuType:C,orderNum:3,parentId:1,path:menu,perms:system:menu:list,status:0,visible:0}],createTime:2021-11-12 18:46:19,icon:system,id:1,menuName:系统管理,menuType:M,orderNum:1,parentId:0,path:system,perms:,status:0,visible:0}]},msg:操作成功
}
2. 代码实现
第一步: 在huanf-framework工程的vo目录新建RoutersVo类写入如下负责把指定字段返回给前端
package com.keke.domain.vo;import com.keke.domain.entity.Menu;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;
import java.util.List;Data
AllArgsConstructor
NoArgsConstructor
public class RoutersVo {ListMenu menus;
}第二步: 把keke-framework工程的Menu类修改为如下增加了children字段(成员变量)、增加了
Accessors(chain true)注解
package com.keke.domain.entity;import java.util.Date;
import java.io.Serializable;
import java.util.List;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.experimental.Accessors;/*** 菜单权限表(Menu)表实体类** author makejava* since 2023-10-18 20:55:24*/
SuppressWarnings(serial)
Data
NoArgsConstructor
AllArgsConstructor
Accessors(chain true)
TableName(sys_menu)
public class Menu {//菜单IDprivate Long id;//菜单名称private String menuName;//父菜单IDprivate Long parentId;//显示顺序private Integer orderNum;//路由地址private String path;//组件路径private String component;//是否为外链0是 1否private Integer isFrame;//菜单类型M目录 C菜单 F按钮private String menuType;//菜单状态0显示 1隐藏private String visible;//菜单状态0正常 1停用private String status;//权限标识private String perms;//菜单图标private String icon;//创建者private Long createBy;//创建时间private Date createTime;//更新者private Long updateBy;//更新时间private Date updateTime;//备注private String remark;private String delFlag;private ListMenu children;}第三步: 把keke-framework工程的MenuService接口修改为如下增加了查询用户的路由信息(权限菜单)的接口
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.Menu;import java.util.List;/*** 菜单权限表(Menu)表服务接口** author makejava* since 2023-10-18 20:55:48*/
public interface MenuService extends IServiceMenu {//查询用户权限信息ListString selectPermsByUserId(Long userId);//查询用户的路由信息也就是权限菜单ListMenu selectRouterMenuTreeByUserId(Long userId);
}第四步: 把keke-framework工程的MenuServiceImpl类修改为如下增加了查询用户的路由信息(权限菜单)的具体代码
package com.keke.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.entity.Menu;
import com.keke.mapper.MenuMapper;
import com.keke.service.MenuService;
import com.keke.utils.SecurityUtils;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;/*** 菜单权限表(Menu)表服务实现类** author makejava* since 2023-10-18 20:55:48*/
Service(menuService)
public class MenuServiceImpl extends ServiceImplMenuMapper, Menu implements MenuService {//根据用户id查询权限关键字Overridepublic ListString selectPermsByUserId(Long userId) {//如果用户id为1代表管理员roles 中只需要有admin// permissions中需要有所有菜单类型为C(菜单)或者F(按钮)的状态为正常的未被删除的权限if(SecurityUtils.isAdmin()) {LambdaQueryWrapperMenu lambdaQueryWrapper new LambdaQueryWrapper();lambdaQueryWrapper.in(Menu::getMenuType, SystemConstants.MENU, SystemConstants.BUTTON);lambdaQueryWrapper.eq(Menu::getStatus, SystemConstants.STATUS_NORMAL);//由于我们的逻辑删除字段已经配置了所以无需封装lambdaQueryWrapperListMenu menuList list(lambdaQueryWrapper);//我们需要的是String类型的集合这里我们要进行数据的处理这里采用流的方式ListString permissions menuList.stream().map(new FunctionMenu, String() {Overridepublic String apply(Menu menu) {String perms menu.getPerms();return perms;}}).collect(Collectors.toList());return permissions;}//否则返回这个用户所具有的权限//这里我们需要进行连表查询因为我们的用户先和角色关联然后角色才跟权限关联MenuMapper menuMapper getBaseMapper();//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑这里直接返回return menuMapper.selectPermsByUserId(userId);}Overridepublic ListMenu selectRouterMenuTreeByUserId(Long userId) {MenuMapper menuMapper getBaseMapper();ListMenu menus null;//如果是管理员返回所有if(SecurityUtils.isAdmin()){menus menuMapper.selectAllRoutersMenu();}else {//如果不是管理员返回对应用户的菜单menus menuMapper.selectRoutersMenuTreeByUserId(userId);}//因为上面的查询都是从数据库进行查询所以无法封装children这里构建TreeListMenu menuTree buildMenuTree(menus,0L);return menuTree;}/*** 构建MenuTree* 思路先找第一层级的菜单就是找到id于parentId的对应关系然后把parentId设置为Id的children* param menus* return*/private ListMenu buildMenuTree(ListMenu menus,Long parentId) {//转化流处理ListMenu menuTree menus.stream()//过滤掉除一级菜单之外的菜单.filter(menu - menu.getParentId().equals(parentId))//然后将获取其子菜单设置到children字段并返回.map(m - m.setChildren(gerChildren(m, menus))).collect(Collectors.toList());return menuTree;}//获取当前菜单的子菜单private ListMenu gerChildren(Menu menu, ListMenu menus) {//流处理遍历每一个流对象筛选出流对象的parentIdmenu的id即过滤ListMenu children menus.stream().filter(m - m.getParentId().equals(menu.getId()))//这里其实不必要写这一步的逻辑是如果有三级//可以把流对象中再过筛选出子菜单设置给对应的children并返回.map(m - m.setChildren(gerChildren(m,menus))).collect(Collectors.toList());return children;}
}第五步: 把keke-framework工程的MenuMapper接口修改为如下增加了2个(一个查超级管理员另一个查普通用户)查询权限菜单的接口
package com.keke.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.Menu;import java.util.List;/*** 菜单权限表(Menu)表数据库访问层** author makejava* since 2023-10-18 20:55:48*/
public interface MenuMapper extends BaseMapperMenu {//Mapper的实现类对应xml映射文件ListString selectPermsByUserId(Long userId);ListMenu selectAllRoutersMenu();ListMenu selectRoutersMenuTreeByUserId(Long userId);}第六步: 把keke-framework工程的resources/mapper目录下的MenuMapper.xml文件修改为如下是查询权限菜单的具体代码
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.keke.mapper.MenuMapperselect idselectPermsByUserId resultTypejava.lang.String
-- 这里的逻辑是先用userId连表查询roleId再用roleId连表查询menuId再根据menuId
-- 查询对应的用户权限selectDISTINCT m.permsfrom sys_user_role urleft join sys_role_menu rm on ur.role_idrm.role_idleft join sys_menu m on m.idrm.menu_idwhereur.user_id#{userId} andm.menu_type in (C,F) andm.status0 andm.del_flag0/selectselect idselectAllRoutersMenu resultTypecom.keke.domain.entity.Menu
-- 这里与上面的sql差不多只是menu_type有差别还有查询的字段个数SELECTDISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,IFNULL(m.perms,) AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_timeFROMsys_menu mWHERE
-- 查询所有的所以不需要加userId的条件m.menu_type IN (C,M) ANDm.status 0 ANDm.del_flag 0ORDER BYm.parent_id,m.order_num/selectselect idselectRoutersMenuTreeByUserId resultTypecom.keke.domain.entity.Menu
-- 这里与上面的sql差不多SELECTDISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,IFNULL(m.perms,) AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_timeFROMsys_user_role urLEFT JOIN sys_role_menu rm ON ur.role_id rm.role_idLEFT JOIN sys_menu m ON m.id rm.menu_idWHEREur.user_id #{userId} ANDm.menu_type IN (C,M) ANDm.status 0 ANDm.del_flag 0ORDER BYm.parent_id,m.order_num/select/mapper
第七步: 把keke-admin工程的LoginController类修改为如下增加了查询路由信息(权限菜单)的接口
package com.keke.controller;import com.keke.annotation.KekeSystemLog;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.domain.entity.Menu;
import com.keke.domain.entity.User;
import com.keke.domain.vo.AdminUserInfoVo;
import com.keke.domain.vo.RoutersVo;
import com.keke.domain.vo.UserInfoVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.service.MenuService;
import com.keke.service.RoleService;
import com.keke.service.SystemLoginService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.List;RestController
Api(tags 后台用户登录相关接口)
public class LoginController {Autowiredprivate SystemLoginService systemLoginService;Autowiredprivate MenuService menuService;Autowiredprivate RoleService roleService;PostMapping(/user/login)KekeSystemLog(businessName 后台用户登录)public ResponseResult login(RequestBody User user){if(!StringUtils.hasText(user.getUserName())){//提示必须要传用户名throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);}return systemLoginService.login(user);}GetMapping(getInfo)public ResponseResultAdminUserInfoVo getInfo(){//获取当前登录用户用我们封装的SecurityUtilsLoginUser loginUser SecurityUtils.getLoginUser();//根据用户id查询权限信息Long userId loginUser.getUser().getId();ListString permissions menuService.selectPermsByUserId(userId);//根据用户id查询角色信息ListString roles roleService.selectRoleKeyByUserId(userId);//获取userInfo信息User user loginUser.getUser();UserInfoVo userInfoVo BeanCopyUtils.copyBean(user, UserInfoVo.class);//创建Vo封装返回AdminUserInfoVo adminUserInfoVo new AdminUserInfoVo(permissions,roles,userInfoVo);return ResponseResult.okResult(adminUserInfoVo);}GetMapping(/getRouters)public ResponseResultRoutersVo getRouters(){//获取用户idLong userId SecurityUtils.getUserId();//查询menu结果是tree的形式ListMenu menus menuService.selectRouterMenuTreeByUserId(userId);//封装返回RoutersVo routersVo new RoutersVo(menus);return ResponseResult.okResult(routersVo);}}第八步: 测试
打开redis启动前端工程登录后可以看到左侧菜单路由展示出来了以及一些权限按钮 这里启动前台工程测试可能是不通过的可以参见以下问题
AdminUserInfoVo的变量名是否与要求返回的字段一致
前端工程中store目录下的modules目录下的permission.js文件中处理子路由的时候push写成了psuh导致路由不能渲染。看看你浏览器控制台有没有报psuh的错误有的话就是这里的问题。
补充
如果测试失误可以把token删除然后重新登录测试 五、后台模块-退出登录
1. 接口分析
删除redis中的用户信息 请求方式 请求地址 请求头 POST /user/logout 需要token请求头
{code: 200,msg: 操作成功
}
2. 代码实现
第一步: 把keke-framework工程的SystemLoginService接口修改为如下增加了退出登录的接口
package com.keke.service;import com.keke.domain.ResponseResult;
import com.keke.domain.entity.User;public interface SystemLoginService {//后台用户登录ResponseResult login(User user);//后台用户退出登录ResponseResult logout();}第二步: 把keke-framework工程的SystemLoginServiceImpl类修改为如下增加了退出登录的具体代码
package com.keke.service.impl;import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.domain.entity.User;
import com.keke.service.BlogLoginService;
import com.keke.service.SystemLoginService;
import com.keke.utils.JwtUtil;
import com.keke.utils.RedisCache;
import com.keke.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;
import java.util.Objects;Service
public class SystemLoginServiceImpl implements SystemLoginService {Autowiredprivate AuthenticationManager authenticationManager;Autowiredprivate RedisCache redisCache;Overridepublic ResponseResult login(User user) {UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());Authentication authenticate authenticationManager.authenticate(authenticationToken);//authenticationManager会默认调用UserDetailsService从内存中进行用户认证我们实际需求是从数据库因此我们要重新创建一个UserDetailsService的实现类//判断是否认证通过if(Objects.isNull(authenticate)){throw new RuntimeException(用户名或者密码错误);}//获取Userid生成tokenLoginUser loginUser (LoginUser) authenticate.getPrincipal();String userId loginUser.getUser().getId().toString();String jwt JwtUtil.createJWT(userId);//把用户信息存入redisredisCache.setCacheObject(login: userId,loginUser);//把token和userInfo封装返回因为响应回去的data有这两个属性所以要封装VoMapString,String systemLoginVo new HashMap();systemLoginVo.put(token,jwt);return ResponseResult.okResult(systemLoginVo);}Overridepublic ResponseResult logout() {//删除redis中的登录信息Long userId SecurityUtils.getUserId();redisCache.deleteObject(login: userId);return ResponseResult.okResult();}
}第三步: 那keke-admin工程的LoginController类修改为如下增加了退出登录的访问接口
package com.keke.controller;import com.keke.annotation.KekeSystemLog;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.LoginUser;
import com.keke.domain.entity.Menu;
import com.keke.domain.entity.User;
import com.keke.domain.vo.AdminUserInfoVo;
import com.keke.domain.vo.RoutersVo;
import com.keke.domain.vo.UserInfoVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.service.MenuService;
import com.keke.service.RoleService;
import com.keke.service.SystemLoginService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.List;RestController
Api(tags 后台用户登录相关接口)
public class LoginController {Autowiredprivate SystemLoginService systemLoginService;Autowiredprivate MenuService menuService;Autowiredprivate RoleService roleService;PostMapping(/user/login)KekeSystemLog(businessName 后台用户登录)public ResponseResult login(RequestBody User user){if(!StringUtils.hasText(user.getUserName())){//提示必须要传用户名throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);}return systemLoginService.login(user);}GetMapping(getInfo)public ResponseResultAdminUserInfoVo getInfo(){//获取当前登录用户用我们封装的SecurityUtilsLoginUser loginUser SecurityUtils.getLoginUser();//根据用户id查询权限信息Long userId loginUser.getUser().getId();ListString permissions menuService.selectPermsByUserId(userId);//根据用户id查询角色信息ListString roles roleService.selectRoleKeyByUserId(userId);//获取userInfo信息User user loginUser.getUser();UserInfoVo userInfoVo BeanCopyUtils.copyBean(user, UserInfoVo.class);//创建Vo封装返回AdminUserInfoVo adminUserInfoVo new AdminUserInfoVo(permissions,roles,userInfoVo);return ResponseResult.okResult(adminUserInfoVo);}GetMapping(/getRouters)public ResponseResultRoutersVo getRouters(){//获取用户idLong userId SecurityUtils.getUserId();//查询menu结果是tree的形式ListMenu menus menuService.selectRouterMenuTreeByUserId(userId);//封装返回RoutersVo routersVo new RoutersVo(menus);return ResponseResult.okResult(routersVo);}PostMapping(/user/logout)public ResponseResult logout(){return systemLoginService.logout();}}第四步: 测试
本地打开你的redis
本地打开postman
运行后台工程
postman测试 启动前端工程在前端工程中测试如下 退出登录成功回到登录页面
六、后台模块-标签列表
1. 查询标签
1.1 标签表的字段 1.2 接口分析
为了方便后期对文章进行管理需要提供标签的功能一个文章可以有多个标签。在后台需要分页查询标签功能要求能根据标签名进行分页查询对应的文章
注意不能把删除了的标签查询出来。除了可以根据标签名查询文章后期还要添加根据备注名查询文章 请求方式 请求路径 Get content/tag/list
请求参数
Query格式请求参数pageNum: 页码pageSize: 每页条数name标签名remark备注
响应格式
{code:200,data:{rows:[{id:4,name:Java,remark:sdad}],total:1},msg:操作成功
}
1.3 代码实现
第一步: 在keke-framework工程的src/main/java目录新建com.huanf.dto.TagListDto类写入如下
package com.keke.domain.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;Data
NoArgsConstructor
AllArgsConstructor
public class TagListDto {//标签名private String name;//备注private String remark;
}第二步 在keke-framework工程的domain/vo目录下创建TagVo写入如下主要作用是响应正确
package com.keke.domain.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;Data
AllArgsConstructor
NoArgsConstructor
public class TagVo {//idprivate Long id;//标签名private String name;//备注private String remark;
}第三步: 在keke-framework工程的service目录新建TagService接口写入如下用于查询标签
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;/*** 标签(Tag)表服务接口** author makejava* since 2023-10-18 10:21:06*/
public interface TagService extends IServiceTag {ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);}第四步: 在huanf-framework工程的service目录新建TagServiceImpl类写入如下是查询标签的具体代码模糊分页查询标签代码
package com.keke.service.impl;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.keke.domain.ResponseResult;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.TagVo;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;/*** 标签(Tag)表服务实现类** author makejava* since 2023-10-18 10:21:07*/
Service(tagService)
public class TagServiceImpl extends ServiceImplTagMapper, Tag implements TagService {Overridepublic ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {//封装lambdaQueryWrapperLambdaQueryWrapperTag lambdaQueryWrapper new LambdaQueryWrapper();//根据标签名(如果有)模糊查询lambdaQueryWrapper.eq(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());//根据备注(如果有)模糊查询lambdaQueryWrapper.eq(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());//创建mp分页器设置参数PageTag page new Page(pageNum,pageSize);//lambdaQueryWrapper条件和page传入pagepage(page,lambdaQueryWrapper);//得到分页数据ListTag tags page.getRecords();//得到页数据总数long total page.getTotal();//bean拷贝为标准响应格式ListTagVo tagVos BeanCopyUtils.copyBeanList(tags, TagVo.class);//封装PageVoPageVo pageVo new PageVo(tagVos,total);//响应返回return ResponseResult.okResult(pageVo);}
}第四步: 在keke-admin工程的controller目录新建TagController类写入如下是查询标签的访问接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.service.TagService;
import io.swagger.annotations.Api;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;RestController
Api(tags 后台标签相关接口)
public class TagController {Autowiredprivate TagService tagService;GetMapping(/test)public ResponseResult test(){ListTag list tagService.list();return ResponseResult.okResult(list);}GetMapping(/content/tag/list)public ResponseResultPageVo list(Integer pageNum, Integer pageSize, TagListDto tagListDto){return tagService.pageTagList(pageNum,pageSize,tagListDto);}}第五步:测试 本地打开你的redis
本地打开postman
运行后台启动类先登录拿token去测接口如下 前端工程 2. 新增标签
1.1 接口分析
点击标签管理的新增按钮可以实现新增标签的功能 请求方式 请求地址 请求头 POST /content/tag 需要token请求头
请求体
{
name:标签名,
remark:标签的备注名
}
响应体
{code:200,msg:操作成功
}
2.2 代码实现
第一步: 确保你在keke-framework工程handler/mybatisplus有MyMetaObjectHandler类并且写入了如下作用是配置是mybatisplus的字段自增(这步我们在前台工程中已经写过)
package com.keke.handler.mybatisplus;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.keke.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;Component
//这个类是用来配置mybatis的字段自动填充。用于发送评论功能由于我们在评论表无法对下面这四个字段进行插入数据(原因是前端在发送评论时没有在
//请求体提供下面四个参数所以后端在往数据库插入数据时下面四个字段是空值)所有就需要这个类来帮助我们往下面这四个字段自动的插入值
//只要我们更新了评论表的字段那么无法插入值的字段就自动有值了
public class MyMetaObjectHandler implements MetaObjectHandler {Override//只要对数据库执行了插入语句那么就会执行到这个方法public void insertFill(MetaObject metaObject) {Long userId null;try {//获取用户iduserId SecurityUtils.getUserId();} catch (Exception e) {e.printStackTrace();userId -1L;//如果异常了就说明该用户还没注册我们就把该用户的userid字段赋值d为-1}//自动把下面四个字段新增了值。this.setFieldValByName(createTime, new Date(), metaObject);this.setFieldValByName(createBy,userId , metaObject);this.setFieldValByName(updateTime, new Date(), metaObject);this.setFieldValByName(updateBy, userId, metaObject);}Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName(updateTime, new Date(), metaObject);this.setFieldValByName( , SecurityUtils.getUserId(), metaObject);}
}
第二步: 在keke-framework工程的domain目录修改Tag类写入如下注意有四个字段是使用了mybatisplus的字段自增
package com.keke.domain.entity;import java.util.Date;
import java.io.Serializable;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;/*** 标签(Tag)表实体类** author makejava* since 2023-10-18 10:20:44*/
SuppressWarnings(serial)
Data
NoArgsConstructor
AllArgsConstructor
TableName(ke_tag)
public class Tag {private Long id;//标签名private String name;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT)private Long createBy;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT)private Date createTime;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT_UPDATE)private Long updateBy;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT)private Date updateTime;//删除标志0代表未删除1代表已删除private Integer delFlag;//备注private String remark;}第三步: 在keke-framework工程新建AddTagDto类写入如下用于接收前端传过来的参数
package com.keke.domain.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;Data
NoArgsConstructor
AllArgsConstructor
public class AddTagDto {//标签名private String name;//备注private String remark;
}第四步: 把keke-admin工程的TagController类添加如下作用是新增标签功能的访问接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.service.TagService;
import io.swagger.annotations.Api;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;import java.util.List;RestController
Api(tags 后台标签相关接口)
RequestMapping(/content/tag)
public class TagController {Autowiredprivate TagService tagService;GetMapping(/test)public ResponseResult test(){ListTag list tagService.list();return ResponseResult.okResult(list);}GetMapping(/list)public ResponseResultPageVo list(Integer pageNum, Integer pageSize, TagListDto tagListDto){return tagService.pageTagList(pageNum,pageSize,tagListDto);}PostMappingpublic ResponseResult addTag(RequestBody AddTagDto addTagDto){return tagService.addTag(addTagDto);}
}第五步在keke-framework的service/TagService中新增如下
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;/*** 标签(Tag)表服务接口** author makejava* since 2023-10-18 10:21:06*/
public interface TagService extends IServiceTag {ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);ResponseResult addTag(AddTagDto addTagDto);
}第六步在keke-framework的service/impl/TagServiceImpl中实现具体逻辑
package com.keke.service.impl;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.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.TagVo;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;/*** 标签(Tag)表服务实现类** author makejava* since 2023-10-18 10:21:07*/
Service(tagService)
public class TagServiceImpl extends ServiceImplTagMapper, Tag implements TagService {Autowiredprivate TagService tagService;Overridepublic ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {//封装lambdaQueryWrapperLambdaQueryWrapperTag lambdaQueryWrapper new LambdaQueryWrapper();//根据标签名(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());//根据备注(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());//创建mp分页器设置参数PageTag page new Page(pageNum,pageSize);//lambdaQueryWrapper条件和page传入pagepage(page,lambdaQueryWrapper);//得到分页数据ListTag tags page.getRecords();//得到页数据总数long total page.getTotal();//bean拷贝为标准响应格式ListTagVo tagVos BeanCopyUtils.copyBeanList(tags, TagVo.class);//封装PageVoPageVo pageVo new PageVo(tagVos,total);//响应返回return ResponseResult.okResult(pageVo);}Overridepublic ResponseResult addTag(AddTagDto addTagDto) {Tag tag BeanCopyUtils.copyBean(addTagDto, Tag.class);tagService.save(tag);return ResponseResult.okResult();}
}第七步测试
打开前端工程
新增一条标签点击确定 可以看到新增成功
3. 删除标签
3.1 接口分析
例如content/tag/6 代表删除id为6的标签数据。删除后在列表中是否查看不到该条数据但是数据库中该条数据还是存在的只是修改了逻辑删除字段的值 请求方式 请求地址 请求头 DELETE /content/tag/{id} 需要token请求头
请求方式可以看到是PathVariable形式
响应体
{code:200,msg:操作成功
}
3.2 代码实现
第一步在keke-admin的controller包下TagController新增删除标签的接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.service.TagService;
import io.swagger.annotations.Api;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;import java.util.List;RestController
Api(tags 后台标签相关接口)
RequestMapping(/content/tag)
public class TagController {Autowiredprivate TagService tagService;GetMapping(/test)public ResponseResult test(){ListTag list tagService.list();return ResponseResult.okResult(list);}GetMapping(/list)public ResponseResultPageVo list(Integer pageNum, Integer pageSize, TagListDto tagListDto){return tagService.pageTagList(pageNum,pageSize,tagListDto);}PostMappingpublic ResponseResult addTag(RequestBody AddTagDto addTagDto){return tagService.addTag(addTagDto);}DeleteMapping(/{id})public ResponseResult deleteTagById(PathVariable(id) Long id){return tagService.deleteTagById(id);}
}第二步在keke-framework的service包下TagService新增如下
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;/*** 标签(Tag)表服务接口** author makejava* since 2023-10-18 10:21:06*/
public interface TagService extends IServiceTag {ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);ResponseResult addTag(AddTagDto addTagDto);ResponseResult deleteTagById(Long id);
}第三步在keke-framework的service/impl包下TagServiceImpl写具体的删除标签逻辑
package com.keke.service.impl;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.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.TagVo;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;/*** 标签(Tag)表服务实现类** author makejava* since 2023-10-18 10:21:07*/
Service(tagService)
public class TagServiceImpl extends ServiceImplTagMapper, Tag implements TagService {Autowiredprivate TagService tagService;Overridepublic ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {//封装lambdaQueryWrapperLambdaQueryWrapperTag lambdaQueryWrapper new LambdaQueryWrapper();//根据标签名(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());//根据备注(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());//创建mp分页器设置参数PageTag page new Page(pageNum,pageSize);//lambdaQueryWrapper条件和page传入pagepage(page,lambdaQueryWrapper);//得到分页数据ListTag tags page.getRecords();//得到页数据总数long total page.getTotal();//bean拷贝为标准响应格式ListTagVo tagVos BeanCopyUtils.copyBeanList(tags, TagVo.class);//封装PageVoPageVo pageVo new PageVo(tagVos,total);//响应返回return ResponseResult.okResult(pageVo);}Overridepublic ResponseResult addTag(AddTagDto addTagDto) {Tag tag BeanCopyUtils.copyBean(addTagDto, Tag.class);tagService.save(tag);return ResponseResult.okResult();}Overridepublic ResponseResult deleteTagById(Long id) {tagService.removeById(id);return ResponseResult.okResult();}}第四步测试打开前端工程删除一个标签删除成功 4. 修改标签
4.1 接口分析
4.1.1 获取标签信息接口
①根据标签id来获取某一条标签的信息当用户点击修改按钮时触发展示在弹框里面。例如content/tag/6 代表获取id为6的标签数据 请求方式 请求地址 请求头 GET /content/tag/{id} 需要token请求头
请求方式是PathVariable形式
响应体
{code:200,data:{id:4,name:标签名,remark:标签的备注名},msg:操作成功
}
4.1.2 修改标签接口 请求方式 请求地址 请求头 PUT /content/tag 需要token请求头
请求体
{
id:7,name:标签名,
remark:标签的备注名
}
响应体
{code:200,msg:操作成功
}
4.2 代码实现
第一步在keke-admin的controller包下的TagController新增两个接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.service.TagService;
import io.swagger.annotations.Api;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;import java.util.List;RestController
RequestMapping(/content/tag)
Api(tags 后台标签相关接口)
public class TagController {Autowiredprivate TagService tagService;GetMapping(/test)public ResponseResult test(){ListTag list tagService.list();return ResponseResult.okResult(list);}GetMapping(/list)public ResponseResultPageVo list(Integer pageNum, Integer pageSize, TagListDto tagListDto){return tagService.pageTagList(pageNum,pageSize,tagListDto);}PostMappingpublic ResponseResult addTag(RequestBody AddTagDto addTagDto){return tagService.addTag(addTagDto);}DeleteMapping(/{id})public ResponseResult deleteTagById(PathVariable(id) Long id){return tagService.deleteTagById(id);}GetMapping(/{id})public ResponseResult getTagInformation(PathVariable(id) Long id){return tagService.getTagInformation(id);}PutMappingpublic ResponseResult editTag(RequestBody EditTagDto editTagDto){return tagService.editTag(editTagDto);}
}第二步在keke-framework的domain/dto下新增editTagDto
package com.keke.domain.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;Data
NoArgsConstructor
AllArgsConstructor
public class EditTagDto {//idprivate Long id;//标签名private String name;//备注private String remark;
}第三步在keke-framework的service/TagService下新增两个方法
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;/*** 标签(Tag)表服务接口** author makejava* since 2023-10-18 10:21:06*/
public interface TagService extends IServiceTag {ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);ResponseResult addTag(AddTagDto addTagDto);ResponseResult deleteTagById(Long id);ResponseResult getTagInformation(Long id);ResponseResult editTag(EditTagDto editTagDto);
}第四步在keke-framework的service/impl/TagServiceImpl下添加具体逻辑
package com.keke.service.impl;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.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.TagVo;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;/*** 标签(Tag)表服务实现类** author makejava* since 2023-10-18 10:21:07*/
Service(tagService)
public class TagServiceImpl extends ServiceImplTagMapper, Tag implements TagService {Autowiredprivate TagService tagService;Overridepublic ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {//封装lambdaQueryWrapperLambdaQueryWrapperTag lambdaQueryWrapper new LambdaQueryWrapper();//根据标签名(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());//根据备注(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());//创建mp分页器设置参数PageTag page new Page(pageNum,pageSize);//lambdaQueryWrapper条件和page传入pagepage(page,lambdaQueryWrapper);//得到分页数据ListTag tags page.getRecords();//得到页数据总数long total page.getTotal();//bean拷贝为标准响应格式ListTagVo tagVos BeanCopyUtils.copyBeanList(tags, TagVo.class);//封装PageVoPageVo pageVo new PageVo(tagVos,total);//响应返回return ResponseResult.okResult(pageVo);}Overridepublic ResponseResult addTag(AddTagDto addTagDto) {Tag tag BeanCopyUtils.copyBean(addTagDto, Tag.class);tagService.save(tag);return ResponseResult.okResult();}Overridepublic ResponseResult deleteTagById(Long id) {tagService.removeById(id);return ResponseResult.okResult();}Overridepublic ResponseResult getTagInformation(Long id) {Tag tag tagService.getById(id);TagVo tagVo BeanCopyUtils.copyBean(tag, TagVo.class);return ResponseResult.okResult(tagVo);}Overridepublic ResponseResult editTag(EditTagDto editTagDto) {Tag tag BeanCopyUtils.copyBean(editTagDto, Tag.class);tagService.updateById(tag);return ResponseResult.okResult();}
}第五步测试打开前端工程点击修改展示出标签的信息填入修改后的信息点击确定标签修改成功 七、后台模块-发布文章
需要提供写博文的功能写博文时需要关联分类和标签。可以上传缩略图也可以在正文中添加图片。文章可以直接发布也可以保存到草稿箱
分析下来这个发布文章的页面总共有四个接口
查询所有分类的接口
查询所有标签的接口
图片上传的接口
新增文章的接口
下面我们一一实现
1. 查询分类接口
1.1 接口分析
请求方式如下注意: 无请求参数 请求方式 请求地址 请求头 GET /content/category/listAllCategory 需要token请求头
响应体
{code:200,data:[{description:wsd,id:1,name:java},{description:wsd,id:2,name:PHP}],msg:操作成功
}
2.2 代码实现
第一步: 把keke-framework工程的CategoryService接口修改为如下增加了分页查询分类列表的接口
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Category;
import org.springframework.web.bind.annotation.RequestMapping;/*** 分类表(Category)表服务接口** author makejava* since 2023-10-10 20:42:22*/
public interface CategoryService extends IServiceCategory {ResponseResult getCategoryList();//后台接口查询所有文章分类ResponseResult listAllCategory();}第二步: 把keke-framework工程的CategoryServiceImpl类修改为如下增加了分页查询分类列表接口的具体代码实现
package com.keke.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Article;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.AdminCategoryVo;
import com.keke.domain.vo.CategoryVo;
import com.keke.mapper.CategoryMapper;
import com.keke.service.ArticleService;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;/*** 分类表(Category)表服务实现类** author makejava* since 2023-10-10 20:42:22*/
Service(categoryService)
public class CategoryServiceImpl extends ServiceImplCategoryMapper, Category implements CategoryService {Autowiredprivate ArticleService articleService;Overridepublic ResponseResult getCategoryList() {//查询文章表状态已发布的文章但是在CategoryService下查询文章表就要注入ArticleServiceLambdaQueryWrapperArticle articleWrapper new LambdaQueryWrapper();articleWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);ListArticle articleList articleService.list(articleWrapper);//获取文章的分类id并去重SetLong categoryIds articleList.stream().map(article - article.getCategoryId())//toSet可以去除重复的id.collect(Collectors.toSet());//查询分类表ListCategory categories listByIds(categoryIds);//分类表中只获取正常状态非禁用的分类用stream流过滤categories categories.stream().filter(category - SystemConstants.STATUS_NORMAL.equals(category.getStatus())).collect(Collectors.toList());//封装VoListCategoryVo categoryVos BeanCopyUtils.copyBeanList(categories, CategoryVo.class);//封装到响应体中因为有数据所以要调用有参okResult()把参数传进去return ResponseResult.okResult(categoryVos);}Overridepublic ResponseResult listAllCategory() {LambdaQueryWrapperCategory lambdaQueryWrapper new LambdaQueryWrapper();lambdaQueryWrapper.eq(Category::getStatus,SystemConstants.STATUS_NORMAL);ListCategory categoryList list(lambdaQueryWrapper);ListAdminCategoryVo adminCategoryVos BeanCopyUtils.copyBeanList(categoryList, AdminCategoryVo.class);return ResponseResult.okResult(adminCategoryVos);}
}第三步 在keke-framework的domain/vo包下创建AdminCategoryVo这个是后台中要用到的响应体
package com.keke.domain.vo;import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;Data
NoArgsConstructor
AllArgsConstructor
public class AdminCategoryVo {private Long id;//分类名private String name;//描述private String description;}第四步: 把keke-admin工程的CategoryController类修改为如下增加了分页查询分类功能的访问接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.service.CategoryService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RestController
RequestMapping(/content/category)
Api(tags 后台标签相关接口)
public class CategoryController {Autowiredprivate CategoryService categoryService;GetMapping(/listAllCategory)public ResponseResult listAllCategory(){return categoryService.listAllCategory();}
}这里我们不测试到4个接口写完统一测试
2. 查询标签接口
2.1 接口分析
请求方式如下注意: 无请求参数 请求方式 请求地址 请求头 GET /content/tag/listAllTag 需要token请求头
响应体
{code:200,data:[{id:1,name:Mybatis},{id:4,name:Java}],msg:操作成功
}
2.2 代码实现
第一步在keke-framework的domain/vo包下创建AdminTagVo类用于专门在后台返回接口关于标签的字段
package com.keke.domain.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;Data
AllArgsConstructor
NoArgsConstructor
public class AdminTagVo {//idprivate Long id;//标签名private String name;
}第二步在keke-blog的TagController中新增查询所有标签的接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;
import com.keke.service.TagService;
import io.swagger.annotations.Api;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;import java.util.List;RestController
RequestMapping(/content/tag)
Api(tags 后台标签相关接口)
public class TagController {Autowiredprivate TagService tagService;GetMapping(/test)public ResponseResult test(){ListTag list tagService.list();return ResponseResult.okResult(list);}GetMapping(/list)public ResponseResultPageVo list(Integer pageNum, Integer pageSize, TagListDto tagListDto){return tagService.pageTagList(pageNum,pageSize,tagListDto);}PostMappingpublic ResponseResult addTag(RequestBody AddTagDto addTagDto){return tagService.addTag(addTagDto);}DeleteMapping(/{id})public ResponseResult deleteTagById(PathVariable(id) Long id){return tagService.deleteTagById(id);}GetMapping(/{id})public ResponseResult getTagInformation(PathVariable(id) Long id){return tagService.getTagInformation(id);}PutMappingpublic ResponseResult editTag(RequestBody EditTagDto editTagDto){return tagService.editTag(editTagDto);}//发布文章-查询标签接口GetMapping(/listAllTag)public ResponseResult selectAllTag(){return tagService.selectAllTag();}
}第三步在keke-framework的service/TagService中新增方法
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.PageVo;/*** 标签(Tag)表服务接口** author makejava* since 2023-10-18 10:21:06*/
public interface TagService extends IServiceTag {ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto);ResponseResult addTag(AddTagDto addTagDto);ResponseResult deleteTagById(Long id);ResponseResult getTagInformation(Long id);ResponseResult editTag(EditTagDto editTagDto);//写文章-查询所有标签接口ResponseResult selectAllTag();}第四步在keke-framework的service/impl包下TagServiceImpl中写具体逻辑
package com.keke.service.impl;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.keke.domain.ResponseResult;
import com.keke.domain.dto.AddTagDto;
import com.keke.domain.dto.EditTagDto;
import com.keke.domain.dto.TagListDto;
import com.keke.domain.entity.Tag;
import com.keke.domain.vo.AdminTagVo;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.TagVo;
import com.keke.mapper.TagMapper;
import com.keke.service.TagService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;/*** 标签(Tag)表服务实现类** author makejava* since 2023-10-18 10:21:07*/
Service(tagService)
public class TagServiceImpl extends ServiceImplTagMapper, Tag implements TagService {Autowiredprivate TagService tagService;Overridepublic ResponseResultPageVo pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {//封装lambdaQueryWrapperLambdaQueryWrapperTag lambdaQueryWrapper new LambdaQueryWrapper();//根据标签名(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());//根据备注(如果有)模糊查询lambdaQueryWrapper.like(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());//创建mp分页器设置参数PageTag page new Page(pageNum,pageSize);//lambdaQueryWrapper条件和page传入pagepage(page,lambdaQueryWrapper);//得到分页数据ListTag tags page.getRecords();//得到页数据总数long total page.getTotal();//bean拷贝为标准响应格式ListTagVo tagVos BeanCopyUtils.copyBeanList(tags, TagVo.class);//封装PageVoPageVo pageVo new PageVo(tagVos,total);//响应返回return ResponseResult.okResult(pageVo);}Overridepublic ResponseResult addTag(AddTagDto addTagDto) {Tag tag BeanCopyUtils.copyBean(addTagDto, Tag.class);tagService.save(tag);return ResponseResult.okResult();}Overridepublic ResponseResult deleteTagById(Long id) {tagService.removeById(id);return ResponseResult.okResult();}Overridepublic ResponseResult getTagInformation(Long id) {Tag tag tagService.getById(id);TagVo tagVo BeanCopyUtils.copyBean(tag, TagVo.class);return ResponseResult.okResult(tagVo);}Overridepublic ResponseResult editTag(EditTagDto editTagDto) {Tag tag BeanCopyUtils.copyBean(editTagDto, Tag.class);tagService.updateById(tag);return ResponseResult.okResult();}Overridepublic ResponseResult selectAllTag() {//这里我们创建专门返回给前端的AdminTagVoListTag tagList list();ListAdminTagVo adminTagVos BeanCopyUtils.copyBeanList(tagList, AdminTagVo.class);return ResponseResult.okResult(adminTagVos);}
}3. 图片上传
3.1 接口分析
请求方式如下。请求参数是img值为要上传的文件 请求方式 请求地址 请求头 POST /upload 需要token请求头
请求头
Content-Type multipart/form-data;
响应体
{code: 200,data: 文件访问链接,msg: 操作成功
}
3.2 代码实现
第一步: 把keke-admin工程的application.yml修改为如下增加了OSS的相关配置
server:port: 8989spring:# 数据库连接信息datasource:url: jdbc:mysql://localhost:3306/keke_blog?characterEncodingutf-8serverTimezoneAsia/Shanghaiusername: rootpassword:driver-class-name: com.mysql.cj.jdbc.Driverservlet:# 文件上传multipart:# 单个上传文件的最大允许大小max-file-size: 20MB# HTTP请求中包含的所有文件的总大小的最大允许值max-request-size: 20MBmybatis-plus:# configuration:# # 日志# log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:# 逻辑删除的字段logic-delete-field: delFlag# 代表已删除的值logic-delete-value: 1# 代表未删除的值logic-not-delete-value: 0# 主键自增策略以mysql数据库为准id-type: autoOSS:accessKey: 这里填写你自己的accessKeysecretKey: 这里填写你自己的secretKeybucket: keke-blog
第二步: 在keke-admin工程的controller目录新建UploadController类写入如下
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.handler.exception.exception.SystemException;
import com.keke.service.OssUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;RestControllerpublic class UploadController {Autowiredprivate OssUploadService ossUploadService;PostMapping(/upload)public ResponseResult uploadImg(RequestParam(img) MultipartFile multipartFile){try {return ossUploadService.uploadImg(multipartFile);}catch (Exception e){e.printStackTrace();throw new RuntimeException(文件上传失败);}}}4. 新增文章
4.1 接口分析
请求方式如下: 请求方式 请求地址 请求头 POST /content/article 需要token请求头
请求体
{title:测试新增博文,thumbnail:https://sg-blog-oss.oss-cn-beijing.aliyuncs.com/2022/08/21/4ceebc07e7484beba732f12b0d2c43a9.png,isTop:0,isComment:0,content:# 一级标题\n## 二级标题\n\n正文,tags:[1,4],categoryId:1,summary:哈哈,status:1
}
响应体
{code:200,msg:操作成功
}
4.2 代码实现
第一步在keke-blog的controller中新建ArticleController写入写文章接口
package com.keke.controller;import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddArticleDto;
import com.keke.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;RestController
public class ArticleController {Autowiredprivate ArticleService articleService;PostMapping(/content/article)public ResponseResult add(RequestBody AddArticleDto addArticleDto){return addArticleDto.add(addArticleDto);}
}第二步: 在keke-framework工程的domain/dto目录新建AddArticleDto类写入如下用来接受前端传过来的参数最重要的是tags属性
package com.keke.domain.dto;import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;
import java.util.List;Data
NoArgsConstructor
AllArgsConstructor
public class AddArticleDto {//标题private String title;//文章内容private String content;//文章摘要private String summary;//所属分类idprivate Long categoryId;//缩略图private String thumbnail;//是否置顶0否1是private String isTop;//状态0已发布1草稿private String status;//是否允许评论 1是0否private String isComment;//关联标签id们ListLong tags;
}第三步: 在keke-framework工程的domain/entity目录新建ArticleTag类写入如下文章表标签表的中间表对应的实体类并建立ke_article_tag表对应的service mapper serviceImpl类
package com.keke.domain.entity;import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;/*** 文章标签关联表(ArticleTag)表实体类** author makejava* since 2023-10-20 19:37:32*/
SuppressWarnings(serial)
Data
NoArgsConstructor
AllArgsConstructor
TableName(ke_article_tag)
public class ArticleTag {//文章idprivate Long articleId;//标签idprivate Long tagId;
}package com.keke.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.ArticleTag;/*** 文章标签关联表(ArticleTag)表数据库访问层** author makejava* since 2023-10-20 20:05:17*/
public interface ArticleTagMapper extends BaseMapperArticleTag {}package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.ArticleTag;/*** 文章标签关联表(ArticleTag)表服务接口** author makejava* since 2023-10-20 20:05:17*/
public interface ArticleTagService extends IServiceArticleTag {}package com.keke.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.ArticleTag;
import com.keke.mapper.ArticleTagMapper;
import com.keke.service.ArticleTagService;
import org.springframework.stereotype.Service;/*** 文章标签关联表(ArticleTag)表服务实现类** author makejava* since 2023-10-20 20:05:17*/
Service(articleTagService)
public class ArticleTagServiceImpl extends ServiceImplArticleTagMapper, ArticleTag implements ArticleTagService {}第四步: 在keke-framework工程的domain/entity的Article类修改为如下注意有4个字段是使用了mybatisplus属性值自增的注解
package com.keke.domain.entity;import java.util.Date;
import java.io.Serializable;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;/*** 文章表(Article)表实体类** author makejava* since 2023-10-10 10:06:59*/
SuppressWarnings(serial)
Data
NoArgsConstructor
AllArgsConstructor
TableName(ke_article)
Accessors(chain true)
public class Article {private Long id;//标题private String title;//文章内容private String content;//文章摘要private String summary;//所属分类idprivate Long categoryId;//分页查询文章列表时新增的一个字段为的是更好的封装但是数据库中没有该字段为了避免mp//在查询的时候查询这一列可以添加如下注解TableField(exist false)//意思是这个字段在数据库表中实际上是不存在的private String categoryName;//缩略图private String thumbnail;//是否置顶0否1是private String isTop;//状态0已发布1草稿private String status;//访问量private Long viewCount;//是否允许评论 1是0否private String isComment;TableField(fill FieldFill.INSERT)private Long createBy;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT)private Date createTime;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT_UPDATE)private Long updateBy;//由于我们在MyMetaObjectHandler类配置了mybatisplus的字段自动填充//所以就能指定哪个字段在什么时候进行自动填充例如该类其它字段新增了数据那么createBy字段就会自动填充值TableField(fill FieldFill.INSERT)private Date updateTime;//删除标志0代表未删除1代表已删除private Integer delFlag;public Article(Long id,Long viewCount){this.id id;this.viewCount viewCount;}
}第五步在keke-framework的service的ArticleService接口新增方法
package com.keke.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddArticleDto;
import com.keke.domain.entity.Article;/*** 文章表(Article)表服务接口** author makejava* since 2023-10-10 09:59:37*/
public interface ArticleService extends IServiceArticle {ResponseResult hotArticleList();ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId);ResponseResult getArticleDetail(Long id);ResponseResult updateViewCount(Long id);ResponseResult add(AddArticleDto addArticleDto);
}第五步在keke-framework的service/impl的ArticleServiceImpl写入具体逻辑代码
package com.keke.service.impl;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.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AddArticleDto;
import com.keke.domain.entity.Article;
import com.keke.domain.entity.ArticleTag;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.ArticleDetailVo;
import com.keke.domain.vo.ArticleListVo;
import com.keke.domain.vo.HotArticleVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.ArticleMapper;
import com.keke.service.ArticleService;
import com.keke.service.ArticleTagService;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;/*** 文章表(Article)表服务实现类** author makejava* since 2023-10-10 09:59:39*/
Service(articleService)
public class ArticleServiceImpl extends ServiceImplArticleMapper, Article implements ArticleService {Autowiredprivate CategoryService categoryService;Autowiredprivate ArticleService articleService;//显然redisCache是对redisTemplate的封装Autowiredprivate RedisCache redisCache;Autowiredprivate ArticleTagService articleTagService;//查询热门文章Overridepublic ResponseResult hotArticleList() {//---------------------------------------每调用这个方法就从redis查询文章的浏览量展示在热门文章列表--------------------------------------------------------------MapString, Integer viewCountMap redisCache.getCacheMap(SystemConstants.REDIS_ARTICLE_KEY);ListArticle articleList viewCountMap.entrySet().stream().map(entry - new Article(Long.valueOf(entry.getKey()), entry.getValue().longValue())).collect(Collectors.toList());articleService.updateBatchById(articleList);//-----------------------------------------------------------------------------------------------------//查询热门文章 封装成ResponseResult返回LambdaQueryWrapperArticle lambdaQueryWrapper new LambdaQueryWrapper();//必须是正式文章lambdaQueryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);//按照浏览量进行排序lambdaQueryWrapper.orderByDesc(Article::getViewCount);//最多查询10条,设置mp分页对象的参数分别为1和10PageArticle page new Page(SystemConstants.ARTICLE_STATUS_CURRENT,SystemConstants.ARTICLE_STATUS_SIZE);//将page对象和lambdaQueryWrapper查询条件封装成pagepage(page,lambdaQueryWrapper);//page.getRecords()获取到所有符合条件的数据(也就是文章)ListArticle articles page.getRecords();//BeanCopyListHotArticleVo hotArticleVos BeanCopyUtils.copyBeanList(articles, HotArticleVo.class);//返回ResponseResult对象return ResponseResult.okResult(hotArticleVos);}//分页查询文章列表包含首页和分类页面的文章列表分页查询Overridepublic ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {//查询条件LambdaQueryWrapperArticle lambdaQueryWrapper new LambdaQueryWrapper();//首先明确categoryId在首页中不传在分类文章页面中会传所以我们要判断/*这里是lambdaQueryWrapper.eq三个参数的写法第一个参数返回值是boolean类型如果判断为true那么后续就会把后面的判断加入sql语句当中*/lambdaQueryWrapper.eq(Objects.nonNull(categoryId)categoryId0,Article::getCategoryId,categoryId);//状态是正式发布的置顶的文章要显示在最前面对isTop进行降序lambdaQueryWrapper.eq(Article::getStatus,SystemConstants.ARTICLE_STATUS_NORMAL);lambdaQueryWrapper.orderByDesc(Article::getIsTop);//分页查询PageArticle page new Page(pageNum,pageSize);page(page,lambdaQueryWrapper);//查询categoryNameListArticle articles page.getRecords();//有categoryId但无categoryName//拿着categoryId去查询categoryName然后封装到article中/*for (Article article : articles) {Category category categoryService.getById(article.getCategoryId());article.setCategoryName(category.getName());}*/articles.stream()//setter返回的是对象.map(article - article.setCategoryName(categoryService.getById(article.getCategoryId()).getName())).collect(Collectors.toList());//封装VoListArticleListVo articleListVos BeanCopyUtils.copyBeanList(page.getRecords(), ArticleListVo.class);//封装PageVoPageVo pageVo new PageVo(articleListVos,page.getTotal());return ResponseResult.okResult(pageVo);}//查询文章详情Overridepublic ResponseResult getArticleDetail(Long id) {//根据id查询文章Article article getById(id);
// //--------------------------------------从redis中获取viewCount--------------------------------------------------------------
// //获取到的是redis当中的Integer类型的viewCount
// Integer viewCount redisCache.getCacheMapValue(SystemConstants.REDIS_ARTICLE_KEY, id.toString());
// //设置article的viewCount为从redis中查出来的数据
// article.setViewCount(viewCount.longValue());//转化称VoArticleDetailVo articleDetailVo BeanCopyUtils.copyBean(article, ArticleDetailVo.class);//根据分类id查询分类名称Long categoryId articleDetailVo.getCategoryId();Category category categoryService.getById(categoryId);//如果没有获取到id就不设置if(categoryId!null){articleDetailVo.setCategoryName(category.getName());}//封装响应体return ResponseResult.okResult(articleDetailVo);}Overridepublic ResponseResult updateViewCount(Long id) {//更新浏览量(自增)redisCache.incrementCacheMapValue(SystemConstants.REDIS_ARTICLE_KEY,id.toString(),SystemConstants.REDIS_ARTICLE_VIEW_COUNT_INCREMENT);return ResponseResult.okResult();}Override//因为这里做了两次数据库插入操作必须保证两个操作同时失败或者成功public ResponseResult add(AddArticleDto addArticleDto) {//添加到文章表Article article BeanCopyUtils.copyBean(addArticleDto, Article.class);save(article);//添加到文章标签关联表ListLong tags addArticleDto.getTags();//拿到ArticleTag对象集合ListArticleTag articleTags tags.stream().map(new FunctionLong, ArticleTag() {Overridepublic ArticleTag apply(Long tagId) {ArticleTag articleTag new ArticleTag(article.getId(), tagId);return articleTag;}}).collect(Collectors.toList());//然后批量插入数据库articleTagService.saveBatch(articleTags);return ResponseResult.okResult();}
}5. 四接口测试 点击发布发布成功后跳转至文章管理但这个页面的接口我们还没有写所以我们需要去数据库中查看刚才的操作是否成功
文章表新增成功 文章标签表新增成功