一、RBAC基于角色的控制访问

1、RBAC数据库设计

(1)RBAC概述

RBAC就是基于角色的控制访问,是一种广泛应用与计算机安全领域的访问控制框架。每个用户可以有一个或多个角色,用户通过角色间接的访问资源,RBAC的优点是:易于管理和扩展,提高了系统安全性和可维护性。缺点:不够灵活,难以处理复杂的权限控制需求。

基本概念包括用户、角色、权限和许可:

用户:使用系统的实体

角色:对某些用户进行分类的一组权限集合

权限:对某些资源的访问权力

许可:授权角色的权限使用范围

(2)RBAC系统设计(五张表)

  • Admin(用户):每个用户都有唯一的UID识别,并被授予不同的角色。
  • Role(角色):不同的角色具有不同的权限。
  • Permission(权限):表示访问权限。
  • 用户-角色映射:描述用户和角色之间的关系。
  • 角色-权限映射:描述角色和权限之间的关系。

RBAC系统设计

2、实现RBAC业务功能

在实现RBAC的方案中,需要将用户信息和角色信息与Spring Security的安全框架进行对接。这可以通过实现UserDetailsService接口和UserDetails接口来完成。

下面以添加一个用户并将该用户添加到一个角色中为例,说明这个功能实现的过程。

  • 在业务层中创建IAdminService、IRoleService、IPermissionService等服务,用来对用户、角色、权限进行CRUD操作,并实现相应的业务逻辑。
  • 在数据访问层中使用MyBatis对用户、角色、权限进行数据访问,并实现对中间表的操作。
  • 实现UserDetailsService和UserDetails接口,对接到Spring Security中,用于对用户的认证和授权。
  • 在业务层和数据访问层中,通过使用VO、DTO、Entity等POJO类型的对象进行数据传输和转换。

业务逻辑

3、前后端交互规范

数据传输规则

企业中code表示新的状态码,用原来的Http状态标识请求状态。

前后端传输规范

前端向后端发送数据的标准约定如下:

  • 数据发送采用HTTP的POST和GET方法。
  • 在数据传输时,URL采用RESTful风格。
  • 对于ID等关键数据,使用URL进行传输;其他数据以JSON格式打包,并通过请求的Body部分进行传输。

后端返回数据的规则如下:

  • 为了区分网络错误和业务错误,业务处理结果的HTTP状态码统一为200。业务错误的详细信息则通过JSON返回。
  • 所有返回数据都以JSON格式打包,包含以下四个属性:code、message、data。
  • code:表示业务处理结果,2XXXX表示处理成功,4XXXX、5XXXX等表示业务错误。
  • message:在出现错误时,表示错误的具体消息;处理正确时,该字段可以为空或者只包含"OK"。
  • data:处理正确时,包含业务处理的结果,例如查询结果集或单条查询记录。

设计JsonResult统一返回结果

/**
 * 统一响应结果类型
 * 用于Controller层返回数据
 */
@Data //自动创建getter、setter方法
public class JsonResult<T> implements Serializable {
    /**
     * 状态码:20000表示成功
     */
    private Integer code;
    /**
     * 操作失败时,用于描述错误原因的文本,正确的时候可以返回null
     */
    private String message;
    /**
     * 操作成功时返回数据
     */
    private T data;

    /**
     * 操作成功返回数据的方法
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> JsonResult<T> ok(T data) {
        JsonResult<T> jsonResult = new JsonResult<>();
        //调用三个具体的方法
        jsonResult.setCode(20000); //操作无误
        jsonResult.setData(data); //响应的数据
        jsonResult.setMessage("OK");
        return jsonResult;
    }

    /**
     * 针对增删改等业务的数据返回
     * @return
     */
    public static JsonResult<Void> ok() {
        //直接调用当前的ok
        return ok(null);
    }
}

在控制器中使用JsonResult来封装返回结果:

@RestController
@Slf4j
public class DemoController {
    @GetMapping("/demos/{id}/test")
    public JsonResult<List<String>> test(@PathVariable Integer id) {
        log.info("收到{}的请求", id);
        List<String> names = new ArrayList<>();
        names.add("Tom");
        names.add("Jerry");
        names.add("Spike");
        return JsonResult.ok(names);
    }
}

在Spring Security Web配置中放过 /demos/** 请求:

@Configuration
@EnableWebSecurity //开启web资源授权
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .requestMatchers("/demos/**").permitAll();
        //放开资源
    }
}

测试结果类似于如下结果

控制台:收到1的请求

jsonResult

二、管理系统管理员

1、管理员列表功能

管理系统管理员最基本的功能是查询管理员列表。

功能的流程大致如下:

(1)浏览器发起查询请求

(2)服务端控制器将请求传递给业务层

(3)业务层使用MyBatis的Mapper获取数据,并对数据进行适当的加工和处理

(4)控制器将经过处理的数据封装为JsonResult返回给浏览器。

管理员列表功能

管理员列表数据层:

创建pojo分别是Admin和AdminListItemVO:

Admin管理员实体类,对应数据库表ams_admin

/**
 * @document: 管理员实体类
 * @Author:SmallG
 * @CreateTime:2023/9/25+10:37
 */

@Data //生产getter、setter、toString、equals、hashCode方法
@NoArgsConstructor //生成无参构造
@AllArgsConstructor //生成所有带参数的构造方法
public class Admin {
    private Long id;
    private String username;//用户名
    private String password;// 密码
    private String nickname; //昵称
    private String avatar; //图像
    private String phone; //手机号
    private String email; //电子邮箱
    private String description; //描述
    private Integer enable; //是否启用 1表示启动 0表示禁用
    private String lastLoginIp; //最后一次登录ip
    private Integer loginCount; //累计登录次数
    private LocalDateTime gmtLastLogin; //最后一次登录时间
    private LocalDateTime gmtCreate; //数据创建时间
    private LocalDateTime gmtModified; //数据最后一次修改时间

}

AdminListItemVO管理员列表项vo,用于业务层返回前端页面查询所有管理员:

@Data //生产getter、setter、toString、equals、hashCode方法
@NoArgsConstructor //生成无参构造
@AllArgsConstructor //生成所有带参数的构造方法
public class AdminListItemVO {
    private Long id;
    private String username;//用户名
    private String password;// 密码
    private String nickname; //昵称
    private String avatar; //图像
    private String phone; //手机号
    private String email; //电子邮箱
    private String description; //描述
    private Integer enable; //是否启用 1表示启动 0表示禁用
    private String lastLoginIp; //最后一次登录ip
    private Integer loginCount; //累计登录次数
    private LocalDateTime gmtLastLogin; //最后一次登录时间

}

创建mapper接口:

/**
 * 管理员数据持久层接口
 */

@Mapper
public interface AdminMapper {
    /**
     * 查询管理员数据列表
     * @return 返回管理员数据列表
     */
    List<AdminListItemVO> list();
}

sql

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.highedu.csmall.mapper.AdminMapper">
    <sql id="load">
        SELECT id,
               username,
               password,
               nickname,
               avatar,
               phone,
               email,
               description,
               enable,
               last_login_ip,
               login_count,
               gmt_last_login
        from ams_admin
    </sql>
    <!--查询管理员数据列表-->
    <select id="list" resultType="cn.highedu.csmall.pojo.vo.AdminListItemVO">
        <include refid="load">

        </include>
        order by
    </select>
</mapper>

编写测试类:

@SpringBootTest
@Slf4j
class AdminMapperTest {
    @Autowired
    private AdminMapper adminMapper;

    @Test
    void list() {
        adminMapper.list().forEach(System.out::println);
    }
}

mapper层测试

管理员列表业务层:
public interface IAdminService {

    List<AdminListItemVO> list();
}

实现类:

@Service
@Slf4j
public class AdminServiceImpl implements IAdminService {
    @Autowired
    private AdminMapper adminMapper;


    @Override
    public List<AdminListItemVO> list() {
        log.debug("开始处理【查询管理员列表】的业务,参数:无");
        List<AdminListItemVO> list = adminMapper.list();
        return list;
    }
}
管理员列表控制器:
@RestController
@RequestMapping("/admin")
@Slf4j
public class AdminController {

    @Autowired
    private IAdminService adminService;

    @GetMapping("/list")
    public JsonResult<List<AdminListItemVO>> list(){
        log.info("开始处理【查询所有管理员】的请求");
        List<AdminListItemVO> adminListItemVOS = adminService.list();
        log.info("查询所有管理员成功,返回结果:{}",adminListItemVOS);
        return JsonResult.ok(adminListItemVOS);
    }
}

测试:

查询管理员列表

导入前端项目:

设置跨域访问,主要解决前后端分离项目中,前端到后端的访问:

@Configuration
@Slf4j
public class CorsConfig implements WebMvcConfigurer {
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilterFilterRegistrationBean() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); //允许跨域访问
        config.addAllowedOrigin("http://localhost:9528"); //允许请求的来源
        config.addAllowedHeader("*"); //允许所有的请求头
        config.addAllowedMethod("*"); //允许所有请求方法
        source.registerCorsConfiguration("/**", config); //开放所有的请求路径
        //注册过滤器
        FilterRegistrationBean<CorsFilter> bean =
                new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(-100); //设置过滤器的优先级 数越小优先级越高
        return bean;
    }
}

写个控制器模拟用户登录:

/**
 * @document: 处理前端登录认证的相关请求控制器
 * @Author:SmallG
 * @CreateTime:2023/9/25+14:04
 */

@RestController
@Slf4j
@RequestMapping("/auth")
public class AuthController {

    @PostMapping("/login")
    public JsonResult<Map<String, String>> login() {
        log.info("开始处理【管理员登录】的请求");
        Map<String, String> token = new HashMap<>();
        token.put("token", "TestToken");
        return JsonResult.ok(token);
    }

    /**
     * 获取当前登录管理员的信息(用户名、头像)
     *
     * @return
     */
    @GetMapping("/info")
    public JsonResult<Map<String, String>> getInfo() {
        log.info("开始处理【根据token来查询管理员信息】的请求");
        Map<String, String> userInfo = new HashMap<>();
        userInfo.put("username", "Test");
        userInfo.put("avatar", "https://z1.ax1x.com/2023/09/12/pP2m00O.png");
        return JsonResult.ok(userInfo);
    }

    /**
     * 退出登录 拉黑token
     */
    @PostMapping("/logout")
    public JsonResult<Void> logout() {
        log.info("开始处理【登出操作】的请求");
        return JsonResult.ok();
    }
}
管理员列表整合到前端:

src/api/admin.js 文件中,更新接口以访问后端的管理员列表

export function list() {
  return request({
    url: '/admins/list',
    method: 'get'
  })
}

引入接口 - src/views/system/admin/index.vue,在管理员列表页面文件 src/views/system/admin/index.vue 中引入刚刚更新的管理员列表接口

import * as api from '@/api/admin'

调用加载列表方法 - src/views/system/admin/index.vue

loadList() {
  const param = {
    name: this.keyword,
    pageNum: this.pageNum,
    pageSize: this.pageSize
  }
  api.list().then(res => {
    console.log(res)
    this.adminList = res.data
  })
显示管理员的角色信息:

用户和角色是多对多关系

在entity里面创建角色实体类:

/**
 * 角色实体类 对应数据库表 ams_role
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    private Long id;
    private String name;
    private String description;
    private Integer sort;
    private LocalDateTime gmtCreate;
    private LocalDateTime gmtModified;
}

创建角色列表:

/**
 * 角色列表vo
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleListItemVO {
    private Long id;
    private String name;
    private String description;
    private Integer sort;
}

在AdminListItemVO中添加属性:

/**
 * 用户的角色列表
 */
private List<RoleListItemVO> roles;
/**
 * 用户所有的角色字符串 用逗号分割
 */
private String roleNames;

创建AdminRoleMapper,主要用于管理员与角色的关联,数据访问层接口

@Mapper
public interface AdminRoleMapper {

    /**
     * 根据管理员ID查询一个管理员定义的所有角色
     * @param adminId 管理员ID
     * @return
     */
    List<RoleListItemVO> selectRoleByAdminId(Long adminId);
}

编写sql代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.highedu.csmall.mapper.AdminRoleMapper">
    <!--根据id查询管理员定义的所有角色-->
    <select id="selectRoleByAdminId" resultType="cn.highedu.csmall.pojo.vo.RoleListItemVO">
        SELECT r.id, r.name, r.description,r.sort
        FROM ams_admin_role ar
                 left join ams_role r on ar.role_id = r.id
        where ar.admin_id = #{adminId}
    </select>
</mapper>

service层:

@Service
@Slf4j
public class AdminServiceImpl implements IAdminService {
    @Autowired
    private AdminMapper adminMapper;

    @Autowired
    private AdminRoleMapper adminRoleMapper;

    @Override
    public List<AdminListItemVO> list() {
        log.debug("开始处理【查询管理员列表】的业务,参数:无");
        List<AdminListItemVO> list = adminMapper.list();
        log.info("查询每个管理员的角色");
        //循环遍历所有管理员 获取每个管理员的ID 查询响应的角色列表
        for (AdminListItemVO adminListItemVO : list) {
            //根据管理员id查询角色信息
            List<RoleListItemVO> roleListItemVOS = adminRoleMapper
                    .selectRoleByAdminId(adminListItemVO.getId());
            //把所有的角色赋给管理员
            adminListItemVO.setRoles(roleListItemVOS);
            //拼接管理员的角色名称属性
            StringBuilder roleNames = new StringBuilder();
            //遍历所有的角色
            for (RoleListItemVO roleListItemVO : roleListItemVOS) {
                roleNames.append(roleListItemVO.getName()).append(",");
            }
            //去掉最后的逗号
            adminListItemVO.setRoleNames(roleNames.substring(0, roleNames.lastIndexOf(",")));
        }
        return list;
    }
}

2、分页查询管理员

如果管理员数量增多,将不方便浏览。因此,需要实现带有参数的分页查询控制,分页查询的原理是将前端传递的参数(如页号和每页查询行数)传递到服务器端,在服务器端,根据这些参数计算出查询范围,然后使用SQL语句查询所需数据。

分页查询管理员

定义分页POJO:

向下传递数据,创建分页DTO类PageDTO:

/**
 * @document: 分页DTO类
 * @Author:SmallG
 * @CreateTime:2023/9/26+9:21
 */

@Data
public class PageDTO implements Serializable {
    //当前页码 从1开始
    private Integer pageNum;
    //每页多少条数据
    private Integer pageSize;

    /**
     * 开始查询的位置 从0开始
     *
     * @return 返回offset
     */
    public Integer getOffset() {
        return (pageNum - 1) * pageSize;
    }
}

向上传递数据,创建VO类PageVO:

/**
 * @document: 分页VO类 用于返回查询分页结果
 * @Author:SmallG
 * @CreateTime:2023/9/26+9:28
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageVO<T> implements Serializable {
    //返回当前第几页 从1开始
    private Integer pageNum;
    //每页多少条
    private Integer pageSize;
    //总条数
    private Long total;
    //分页数据 从数据库查询的数据
    private List<T> data;

    /**
     * 计算总页数
     */
    public Integer getPages() {
        return Math.toIntExact((total - 1) / pageSize + 1);
    }
}

创建DTO,实现管理员列表查询,用于前端页面的模糊查询:

@Data
public class AdminListParamPagedDTO extends PageDTO{
    private String name;

}
实现分页查询管理员:

AdminMapper中添加方法

/**
 * 根据管理员姓名模糊查询管理员列表
 * @param adminListParamPagedDTO
 * @return
 */
List<AdminListItemVO> listByName(AdminListParamPagedDTO adminListParamPagedDTO);

/**
 * 根据管理员姓名统计数据的数量,主要用于分页数据
 * @param adminListParamPagedDTO
 * @return
 */
int countByName(AdminListParamPagedDTO adminListParamPagedDTO);

写sql语句:

<!--List<AdminListItemVO> listByName-->
<select id="listByName" resultType="cn.highedu.csmall.pojo.vo.PageVO">
    <include refid="load"></include>
    where username like CONCAT('%',#{name},'%')
    OR nickname like CONCAT('%',#{name},'%')
    ORDER BY identity
    LIMIT #{offset},#{pageSize}
</select>
<select id="countByName" resultType="int">
    SELECT COUNT(*)
    FROM ams_admin
    where username like CONCAT('%', #{name}, '%')
       OR nickname like CONCAT('%', #{name}, '%')
</select>

service层

接口:

/**
 * 分页查询管理员列表
 * @param adminListParamPagedDTO
 * @return
 */
PageVO<AdminListItemVO> list(AdminListParamPagedDTO adminListParamPagedDTO);

实现类:

@Override
public PageVO<AdminListItemVO> list(AdminListParamPagedDTO adminListParamPagedDTO) {
    log.info("开始处理分页查询管理员列表,参数{}", adminListParamPagedDTO);
    //调用方法查询数据

    List<AdminListItemVO> adminListItemVOS = adminMapper.listByName(adminListParamPagedDTO);
    for (AdminListItemVO adminListItemVO : adminListItemVOS) {
        //根据管理员id查询角色信息
        List<RoleListItemVO> roleListItemVOS = adminRoleMapper
                .selectRoleByAdminId(adminListItemVO.getId());
        //把所有的角色赋给管理员
        adminListItemVO.setRoles(roleListItemVOS);
        //拼接管理员的角色名称属性
        StringBuilder roleNames = new StringBuilder();
        //遍历所有的角色
        for (RoleListItemVO roleListItemVO : roleListItemVOS) {
            roleNames.append(roleListItemVO.getName()).append(",");
        }
        //去掉最后的逗号
        adminListItemVO.setRoleNames(roleNames.substring(0, roleNames.lastIndexOf(",")));
    }
    //查询符合条件的总记录数
    int count = adminMapper.countByName(adminListParamPagedDTO);
    //获取pageSize(第几页)
    int pageNum = adminListParamPagedDTO.getPageNum();
    //获取每页显示多少数据
    int pageSize = adminListParamPagedDTO.getPageSize();
    PageVO<AdminListItemVO> pageVO = new PageVO<AdminListItemVO>(pageNum, pageSize, (long) count, adminListItemVOS);
    return pageVO;
}

控制层:

@PostMapping("/list")
public JsonResult<PageVO<AdminListItemVO>> list(
        @RequestBody AdminListParamPagedDTO adminListParamPagedDTO) {
    log.info("开始处理【查询所有管理员】的请求");
    PageVO<AdminListItemVO> pageVO = adminService.list(adminListParamPagedDTO);
    log.info("查询所有管理员成功,返回结果:{}", pageVO);
    return JsonResult.ok(pageVO);

}
将分页查询与前端整合:

admin.js中修改:

/**
 * 查询管理员列表
 * @param {*} data,查询条件,分页参数,查询关键字
 * @returns 分页数据
 */
export function pagedList(data) {
  return request({
    url: '/admins/list',
    method: 'post',
    data
  })
}

admin/index中修改方法名:

loadList() {
  const param = {
    name: this.keyword, // 查询关键字
    pageNum: this.pageNum, // 当前页码
    pageSize: this.pageSize // 每页多少条
  }
  api.pagedList(param).then(res => {
    console.log(res)
    const page = res.data
    // 将roles拼接为roleNames
    console.log(page)
    page.data.forEach(admin => {
      const roleNames = []
      const roles = admin.roles
      console.log(roles)
      roles.forEach(role => {
        roleNames.push(role.name)
      })
      admin.roleNames = roleNames.join(',')
    })
    this.adminList = page.data
    this.pageNum = page.pageNum
    this.pageSize = page.pageSize
    this.total = page.total
  })
}

3、添加管理员功能

创建DTO用于新增管理员传输数据:

/**
 * @document: 添加管理员的DTO对象,用于新增管理员传输数据
 * @Author:SmallG
 * @CreateTime:2023/9/26+11:13
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminAddNewDTO {
    private String username;//用户名
    private String password;// 密码
    private String nickname; //昵称
    private String avatar; //图像
    private String phone; //手机号
    private String email; //电子邮箱
    private String description; //描述
    private Integer enable; //是否启用 1表示启动 0表示禁用
}

创建AdminMapper添加方法:

/**
 * 保存管理员数据,返回受影响的行数
 */
int insert(Admin admin);

编写SQL语句执行添加操作:

<!--存储管理员信息,useGeneratedKeys="true" keyProperty="id"返回生成的id-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into ams_admin(username, password, nickname, avatar, phone, email, description, enable, last_login_ip,
                          login_count, gmt_last_login, gmt_create, gmt_modified)
    values (#{username},
            #{password},
            #{nickname},
            #{avatar},
            #{phone},
            #{email},
            #{description},
            #{enable},
            #{lastLoginIp},
            #{loginCount},
            #{gmtLastLogin},
            #{gmtCreate},
            #{gmtModified})
</insert>

实现业务层代码:

/**
 * 添加管理员
 * @param adminAddNewDTO
 */
void addNew(AdminAddNewDTO adminAddNewDTO);

实现类:

@Override
public void addNew(AdminAddNewDTO adminAddNewDTO) {
    //创建admin对象
    Admin admin = new Admin();
    //复制属性 第一个是源对象,第二个是目标对象
    BeanUtils.copyProperties(adminAddNewDTO, admin);
    //补全信息
    admin.setLoginCount(0);
    admin.setGmtCreate(LocalDateTime.now());
    //对密码进行加密
    String rawPassword = admin.getPassword();
    String encodedPassword = passwordEncoder.encode(rawPassword);
    admin.setPassword(encodedPassword);
    //手机号是一个唯一约束 如果重复抛出异常
    try {
        int rows = adminMapper.insert(admin);
        if (rows < 1) {
            String message = "添加管理员失败,服务器忙,请稍后再试";
            log.warn(message);
            //抛出异常
            throw new ServiceException(ServiceCode.ERR_SAVE_FAILED, message);
        }
    } catch (DuplicateKeyException e) {
        log.error("添加管理员失败,发生异常:{}", e.getClass());
        String message = "添加管理员失败,手机号码被占用";
        log.warn(message);
        //抛出异常
        throw new ServiceException(ServiceCode.ERR_EXISTS,message);
    }
}

添加控制器:

@PostMapping
public JsonResult<Void> addNew(@RequestBody AdminAddNewDTO adminAddNewDTO) {
    log.info("开始处理【添加管理员】的请求,参数:{}", adminAddNewDTO);
    adminService.addNew(adminAddNewDTO);
    return JsonResult.ok();
}

完善添加管理员功能:

jsonRsult添加返回失败的方法:

/**
 * 操作失败返回的信息
 */
public static JsonResult<Void> fail(ServiceCode serviceCode, String message) {
    JsonResult<Void> jsonResult = new JsonResult<>();
    jsonResult.setMessage(message);
    jsonResult.setCode(serviceCode.getValue());
    return jsonResult;
}

public static JsonResult<Void> fail(ServiceException e) {
    return fail(e.getServiceCode(), e.getMessage());
}

对业务异常进行统一处理:

/**
 * @document: 全局统一异常处理
 * @Author:SmallG
 * @CreateTime:2023/9/26+14:44
 */
@RestControllerAdvice //声明这个是全局异常处理器
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler //声明处理的异常类型
    public JsonResult<Void> handleServiceException(ServiceException e) {
        log.warn("程序运行出现ServiceException异常,将统一处理");
        log.warn("异常信息:{}", e.getMessage());
        return JsonResult.fail(e);
    }
}

管理员列表效果展示

4、整合创建管理员界面

(1)头像上传功能

创建记录类型

创建VO

/**
 * @document: 存储文件上传的URL
 * @Author:SmallG
 * @CreateTime:2023/9/26+15:25
 */
@Data
public class UrlVO {
    private String url;
}

创建业务层接口

/**
 * @document: 文件上传接口
 * @Author:SmallG
 * @CreateTime:2023/9/26+15:26
 */

public interface IUploadService {
    /**
     * 文件上传
     * @param path 文件存储地址
     * @param file 文件对象
     * @return 文件的访问地址
     */
    UrlVO uploadFile(String path, MultipartFile file);
}

实现业务层接口

@Service
@Slf4j
public class UploadServiceImpl implements IUploadService {
    //定义日期格式化对象 用于生成保存文件的目录
    private static final DateTimeFormatter DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy/MM");

    //定义保存文件的路径
    @Value("${upload.dir}") //属性注入
    private File uploadDir;

    @Override
    public UrlVO uploadFile(String path, MultipartFile file) {
        //定义保存文件的目录
        File dir = new File(uploadDir, path);
        //获取当前的日期
        String dateDir = LocalDate.now().format(DATE_TIME_FORMATTER);
        // 包含日期的文件保存路径
        File dateDirFile = new File(dir, dateDir);
        log.info("文件保存路径:{}", dateDirFile.getAbsolutePath());
        //创建目录
        if (!dateDirFile.exists()) {
            dateDirFile.mkdirs(); //创建多级目录
        }
        //获取文件的原始名字
        String originalFilename = file.getOriginalFilename();
        //获取文件的扩展名
        String suffix = originalFilename.substring(originalFilename.lastIndexOf('.'));
        //重新生成文件名
        String fileName = UUID.randomUUID() + suffix;
        //创建文件
        File dest = new File(dateDirFile, fileName);
        log.info("文件保存路径:{}", dest.getAbsolutePath());
        //保存文件

        try {
            file.transferTo(dest);
            //返回文件的访问地址
            String url = "/" + dir.getName() + "/" + dateDir + "/" + fileName;
            return new UrlVO(url);
        } catch (Exception e) {
            log.error("保存文件失败:{}", e);
            return null;
        }
    }
}

创建控制器

@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
    @Autowired
    private IUploadService iUploadService;

    @PostMapping("/{path}")
    public JsonResult<UrlVO> uploadFile(@PathVariable String path, MultipartFile file) {
        log.info("开始处理【文件上传】的请求,参数:{}", path);
        log.info("开始处理【文件上传】的请求,参数:{}", file);
        UrlVO urlVO = iUploadService.uploadFile(path, file);
        return JsonResult.ok(urlVO);
    }
}

测试上传功能

图片上传功能

(2)删除管理员功能

mapper层:

/**
 * 根据管理员id删除管理员
 * @param id 管理员id
 * @return 返回受影响的行数
 */
int deleteById(Long id);

xml对应的sql语句:

<delete id="deleteById">
    DELETE
    FROM ams_admin
    WHERE id = #{id}
</delete>

业务层:

接口省略,直接写实现类:

@Override
public void delete(Long id) {
    log.info("开始处理【根据id删除删除管理员】的业务,参数:{}", id);
    // 系统管理员不能删除
    if (id == 1) {
        //系统管理员
        String message = "删除管理员失败,不能删除系统管理员";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
    }
    log.info("即将执行删除数据操作,参数:{}", id);
    int rows = adminMapper.deleteById(id);
    if (rows < 1) {
        String message = "删除管理员失败";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_DELETE_FAILED, message);
    }
}

控制器:

@PostMapping("/{id}/delete")
public JsonResult<Void> delete(@PathVariable Long id) {
    log.info("开始处理【根据id删除管理员】的请求,参数:{}", id);
    adminService.delete(id);
    return JsonResult.ok();
}

删除管理员效果

(3)更新管理员功能

创建DTO类AdminUpdateDTO:

/**
 * @document: 修改管理员Dto类不能修改用户名
 * @Author:SmallG
 * @CreateTime:2023/9/26+17:17
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminUpdateDTO {
    private String nickname;
    private String avatar;
    private String phone;
    private String email;
    private String description;
    private Integer enable;
}

mapper层:

/**
 * 修改管理员数据
 * @param admin 管理员对象
 * @return 受影响的行数
 */
int update(Admin admin);

xml的sql语句:

<update id="update">
    UPDATE ams_admin
    <set>
        <if test="username!=null">
            username = #{username},
        </if>
        <if test="password!=null">
            password = #{password},
        </if>
        <if test="nickname!=null">
            nickname = #{nickname},
        </if>
        <if test="avatar!=null">
            avatar = #{avatar},
        </if>
        <if test="phone!=null">
            phone = #{phone},
        </if>
        <if test="email!=null">
            email = #{email},
        </if>
        <if test="description!=null">
            description = #{description},
        </if>
        <if test="enable!=null">
            enable = #{enable},
        </if>
        <if test="lastLoginIp!=null">
            last_login_ip = #{lastLoginIp},
        </if>
        <if test="loginCount!=null">
            login_count = #{loginCount},
        </if>

        <if test="gmtLastLogin!=null">
            gmt_last_login = #{gmtLastLogin},
        </if>
        <if test="gmtModified!=null">
            gmt_modified = #{gmtModified},
        </if>
    </set>
    where id = #{id}
</update>

业务层:

@Override
public void update(AdminUpdateDTO adminUpdateDTO) {
    //创建admin对象
    Admin admin = new Admin();
    //复制属性
    BeanUtils.copyProperties(adminUpdateDTO, admin);
    //补全信息
    admin.setUsername(null);
    admin.setPassword(null);
    admin.setLastLoginIp(null);
    admin.setLoginCount(null);
    admin.setGmtLastLogin(null);
    admin.setGmtModified(null);
    try {
        int update = adminMapper.update(admin);
        if (update < 1) {
            String message = "修改管理员失败,服务器忙,请稍后再试";
            log.warn(message);
            //抛出异常
            throw new ServiceException(ServiceCode.ERR_SAVE_FAILED, message);
        }
    } catch (ServiceException e) {
        log.error("修改管理员失败,发生异常:{}", e.getClass());
        String message = "添加管理员失败,手机号码被占用";
        log.warn(message);
        //抛出异常
        throw new ServiceException(ServiceCode.ERR_EXISTS, message);
    }
}

控制器:

@PostMapping("/{id}/update")
public JsonResult<Void> update(@RequestBody AdminUpdateDTO adminUpdateDTO, @PathVariable Long id) {
    log.info("开始处理【修改管理员】请求,参数:{}", id);
    adminService.update(adminUpdateDTO, id);
    return JsonResult.ok();
}

(4)为管理员指定角色

创建AdminRole实体类:

/**
 * @document: 角色实体类
 * @Author:SmallG
 * @CreateTime:2023/9/26+17:55
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminRole {
    private Long id;
    private String name;
    private String description;
    private Integer sort;
    private LocalDateTime gmtCreate;
    private LocalDateTime gmtModified;
}

mapper层接口:

/**
 * 批量插入管理员与角色的关联数据
 */
int insertBatch(List<AdminRole> adminRoleList);

/**
 * 根据管理员id删除管理员与角色的关联数据
 * @param adminId 管理员id
 * @return
 */
int deleteByAdminId(Long adminId);

未写完-----------------------------------存在问题

最后修改:2023 年 09 月 28 日
如果觉得我的文章对你有用,请随意赞赏