feat: 导出和导入权限

This commit is contained in:
bunny 2025-04-27 23:43:02 +08:00
parent ee8e666104
commit cc3d9243d2
15 changed files with 250 additions and 169 deletions

View File

@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
public class IndexController {
@Operation(summary = "访问首页", description = "访问首页")
@GetMapping("")
@GetMapping("/readme")
public String index() {
return "index";
}

View File

@ -16,7 +16,9 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@ -70,6 +72,19 @@ public class PermissionController {
return Result.success(ResultCodeEnum.DELETE_SUCCESS);
}
@Operation(summary = "导出权限", description = "导出权限为Excel")
@GetMapping("file/export")
public ResponseEntity<byte[]> exportPermission() {
return permissionService.exportPermission();
}
@Operation(summary = "导入权限", description = "导入权限")
@PutMapping("file/import")
public Result<String> importPermission(@RequestParam(value = "file") MultipartFile file) {
permissionService.importPermission(file);
return Result.success(ResultCodeEnum.SUCCESS);
}
@Operation(summary = "获取所有权限", description = "获取所有权限")
@GetMapping("getPermissionList")
public Result<List<PermissionVo>> getPermissionList() {

View File

@ -0,0 +1,63 @@
package cn.bunny.services.excel;
import cn.bunny.domain.system.entity.Permission;
import cn.bunny.services.excel.entity.PermissionExcel;
import cn.bunny.services.service.system.PermissionService;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.List;
@Slf4j
public class PermissionExcelListener implements ReadListener<PermissionExcel> {
private static final int BATCH_COUNT = 100;
private final PermissionService permissionService;
private List<PermissionExcel> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
public PermissionExcelListener(PermissionService permissionService) {
this.permissionService = permissionService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context AnalysisContext
*/
@Override
public void invoke(PermissionExcel data, AnalysisContext context) {
cachedDataList.add(data);
// 达到BATCH_COUNT了需要去存储一次数据库防止数据几万条数据在内存容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context AnalysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
/**
* 加上存储数据库
*/
private void saveData() {
List<Permission> permissionExcels = cachedDataList.stream().map(item -> {
Permission permission = new Permission();
BeanUtils.copyProperties(item, permission);
return permission;
}).toList();
permissionService.saveOrUpdateBatch(permissionExcels);
}
}

View File

@ -0,0 +1,58 @@
package cn.bunny.services.excel.entity;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.BooleanEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@HeadFontStyle(fontHeightInPoints = 22, color = 14, bold = BooleanEnum.TRUE)
@HeadStyle(fillForegroundColor = 9, fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND)
@HeadRowHeight(24)
public class PermissionExcel {
@Schema(name = "id", title = "唯一标识")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "id"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 10, bold = BooleanEnum.TRUE)
private Long id;
@Schema(name = "parentId", title = "父级id")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "父级id"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 10, bold = BooleanEnum.TRUE)
private Long parentId;
@Schema(name = "parentId", title = "权限编码")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "权限编码"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 17, bold = BooleanEnum.TRUE)
private String powerCode;
@Schema(name = "powerName", title = "权限名称")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "权限名称"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 17, bold = BooleanEnum.TRUE)
private String powerName;
@Schema(name = "requestUrl", title = "请求路径")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "请求路径"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 17, bold = BooleanEnum.TRUE)
private String requestUrl;
@Schema(name = "requestMethod", title = "请求方法")
@ExcelProperty({"修改时不要修改id列如需更新不填写此列", "请求方法"})
@ColumnWidth(66)
@ContentFontStyle(fontHeightInPoints = 14, color = 17, bold = BooleanEnum.TRUE)
private String requestMethod;
}

View File

@ -10,6 +10,8 @@ import cn.bunny.domain.vo.result.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@ -64,4 +66,18 @@ public interface PermissionService extends IService<Permission> {
* @param dto 批量修改权限表单
*/
void updatePermissionListByParentId(PermissionUpdateBatchByParentIdDto dto);
/**
* 导出权限为Excel
*
* @return Excel 文件
*/
ResponseEntity<byte[]> exportPermission();
/**
* 导入权限
*
* @param file 导入的Excel
*/
void importPermission(MultipartFile file);
}

View File

@ -8,10 +8,14 @@ import cn.bunny.domain.system.entity.Permission;
import cn.bunny.domain.system.vo.PermissionVo;
import cn.bunny.domain.vo.result.PageResult;
import cn.bunny.domain.vo.result.ResultCodeEnum;
import cn.bunny.services.excel.PermissionExcelListener;
import cn.bunny.services.excel.entity.PermissionExcel;
import cn.bunny.services.exception.AuthCustomerException;
import cn.bunny.services.mapper.system.PermissionMapper;
import cn.bunny.services.mapper.system.RolePermissionMapper;
import cn.bunny.services.service.system.PermissionService;
import cn.bunny.services.utils.FileUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -21,10 +25,22 @@ import jakarta.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* <p>
@ -143,4 +159,70 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
updateBatchById(permissionList);
}
/**
* 导出权限为Excel
*
* @return Excel 文件
*/
@Override
public ResponseEntity<byte[]> exportPermission() {
String timeFormat = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss").format(new Date());
String zipFilename = "permission-" + timeFormat + ".zip";
String dateFormat = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String filename = "permission-" + dateFormat + ".xlsx";
// 创建btye输出流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// 权限列表
List<Permission> permissionList = list();
List<PermissionExcel> permissionExcelList = permissionList.stream().map(permission -> {
PermissionExcel permissionExcel = new PermissionExcel();
BeanUtils.copyProperties(permission, permissionExcel);
return permissionExcel;
}).toList();
// Zip写入流
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
ByteArrayOutputStream excelOutputStream = new ByteArrayOutputStream();
EasyExcel.write(excelOutputStream, PermissionExcel.class).sheet(dateFormat).doWrite(permissionExcelList);
// 将Excel写入到Zip中
ZipEntry zipEntry = new ZipEntry(filename);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(excelOutputStream.toByteArray());
zipOutputStream.closeEntry();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 设置响应头
HttpHeaders headers = FileUtil.buildHttpHeadersByBinary(zipFilename);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
}
/**
* 导入权限
*
* @param file 导入的Excel
*/
@Override
public void importPermission(MultipartFile file) {
if (file == null) {
throw new AuthCustomerException(ResultCodeEnum.REQUEST_IS_EMPTY);
}
InputStream fileInputStream;
try {
fileInputStream = file.getInputStream();
EasyExcel.read(fileInputStream, PermissionExcel.class, new PermissionExcelListener(this)).sheet().doRead();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -39,6 +39,8 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -108,7 +110,11 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
*/
@Override
public ResponseEntity<byte[]> exportByExcel() {
String filename = FileUtil.buildFilenameBefore("role-");
String timeFormat = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss").format(new Date());
String zipFilename = "role-" + timeFormat + ".zip";
String dateFormat = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String filename = "role-" + dateFormat + ".xlsx";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
@ -124,7 +130,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
// 创建临时ByteArrayOutputStream
ByteArrayOutputStream excelOutputStream = new ByteArrayOutputStream();
ZipEntry zipEntry = new ZipEntry(filename + ".xlsx");
ZipEntry zipEntry = new ZipEntry(filename);
zipOutputStream.putNextEntry(zipEntry);
// 先写入到临时流
@ -137,7 +143,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
}
// 设置响应头
HttpHeaders headers = FileUtil.buildHttpHeadersByBinary(filename + ".zip");
HttpHeaders headers = FileUtil.buildHttpHeadersByBinary(zipFilename);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);

View File

@ -38,3 +38,4 @@ bunny:
bucket-name: auth-admin # 指定哪个桶
backPath: "D:\\MyData\\backup"

View File

@ -1,5 +1,6 @@
server:
port: 8000
spring:
profiles:
active: @profiles.active@
@ -8,7 +9,6 @@ spring:
servlet:
multipart:
max-file-size: 6MB
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
@ -37,10 +37,10 @@ spring:
password: ${bunny.redis.password}
lettuce:
pool:
max-active: 20 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没限制)
max-idle: 5 #最大空闲
min-idle: 0 #最小空闲
max-active: 20 # 最大连接数
max-wait: -1 # 最大阻塞等待时间(负数表示没限制)
max-idle: 5 # 最大空闲
min-idle: 0 # 最小空闲
quartz:
job-store-type: jdbc
@ -99,7 +99,6 @@ management:
enabled: true
os:
enabled: true
endpoint:
health:
show-details: always

View File

@ -1,25 +0,0 @@
package cn.bunny.domain.system.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "UserUpdateWithPasswordDto对象", title = "管理员用户修改密码", description = "管理员用户修改密码")
public class AdminUserUpdateUserStatusDto {
@Schema(name = "userId", title = "用户ID")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(name = "status", title = "用户状态")
@NotNull(message = "用户状态不能为空")
private Boolean status;
}

View File

@ -1,31 +0,0 @@
package cn.bunny.domain.system.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "AdminUserUpdateUserinfoDto对象", title = "管理员用户修改用户信息", description = "管理员用户修改密码")
public class AdminUserUpdateUserinfoDto {
@Schema(name = "userId", title = "用户ID")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(name = "password", title = "用户密码")
@NotBlank(message = "密码不能为空")
private String password;
@Schema(name = "avatar", title = "用户头像")
@NotNull(message = "用户头像不能为空")
private MultipartFile avatar;
}

View File

@ -1,23 +0,0 @@
package cn.bunny.domain.system.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "UserUpdateWithPasswordDto对象", title = "管理员用户修改密码", description = "管理员用户修改密码")
public class AdminUserUpdateWithPasswordDto {
@Schema(name = "userId", title = "用户ID")
@NotNull(message = "用户ID不能为空")
private Long userId;
}

View File

@ -1,22 +0,0 @@
package cn.bunny.domain.system.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "UserUpdateWithAvatarDto对象", title = "管理员用户修改头像", description = "管理员用户修改头像")
public class UserUpdateWithAvatarDto {
@Schema(name = "userId", title = "用户ID")
@NotNull(message = "用户ID不能为空")
private Long userId;
}

View File

@ -1,38 +0,0 @@
package cn.bunny.domain.system.vo.user;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "AdminUserVo对象", title = "用户信息", description = "用户信息")
public class SearchUserinfoVo {
@Schema(name = "id", title = "主键")
@JsonProperty("id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "nickname", title = "昵称")
private String nickname;
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "phone", title = "手机号")
private String phone;
}

View File

@ -1,20 +0,0 @@
package cn.bunny.domain.system.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "验证码响应结果实体类")
public class ValidateCodeVo {
@Schema(description = "验证码key")
private String codeKey;
@Schema(description = "验证码value")
private String codeValue;
}