一、RBAC基于角色的控制访问
1、RBAC数据库设计
(1)RBAC概述
RBAC就是基于角色的控制访问,是一种广泛应用与计算机安全领域的访问控制框架。每个用户可以有一个或多个角色,用户通过角色间接的访问资源,RBAC的优点是:易于管理和扩展,提高了系统安全性和可维护性。缺点:不够灵活,难以处理复杂的权限控制需求。
基本概念包括用户、角色、权限和许可:
用户:使用系统的实体
角色:对某些用户进行分类的一组权限集合
权限:对某些资源的访问权力
许可:授权角色的权限使用范围
(2)RBAC系统设计(五张表)
- Admin(用户):每个用户都有唯一的UID识别,并被授予不同的角色。
- Role(角色):不同的角色具有不同的权限。
- Permission(权限):表示访问权限。
- 用户-角色映射:描述用户和角色之间的关系。
- 角色-权限映射:描述角色和权限之间的关系。
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的请求
二、管理系统管理员
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);
}
}
管理员列表业务层:
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);
未写完-----------------------------------存在问题