新增:新增系统管理/部门管理/导出功能(引入 Easy Excel 依赖用于导出 Excel,详情可见 README 介绍。另请注意:测试导出功能时,前端需要关闭 mockjs,否则 responseType 会被 mockjs 设置为 '',导致导出的文件无法打开)

This commit is contained in:
2023-02-06 23:02:23 +08:00
parent 4bde837649
commit ceba8e9e53
30 changed files with 536 additions and 82 deletions

View File

@@ -115,6 +115,12 @@ limitations under the License.
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Easy Excel一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
<dependency>
<groupId>net.dreamlu</groupId>

View File

@@ -22,6 +22,7 @@ import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
@@ -45,11 +46,13 @@ public class BaseDetailVO extends BaseVO {
* 修改人
*/
@Schema(description = "修改人")
@ExcelProperty(value = "修改人")
private String updateUserString;
/**
* 修改时间
*/
@Schema(description = "修改时间")
@ExcelProperty(value = "修改时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.base;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IEnum;
/**
* 枚举基类
*
* @param <V>
* value 类型
* @param <D>
* description 类型
* @author Charles7c
* @since 2023/2/5 20:44
*/
public interface BaseEnum<V extends Serializable, D extends Serializable> extends IEnum<V> {
/**
* 枚举描述
*/
D getDescription();
}

View File

@@ -23,6 +23,7 @@ import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
@@ -46,11 +47,13 @@ public class BaseVO implements Serializable {
* 创建人
*/
@Schema(description = "创建人")
@ExcelProperty(value = "创建人")
private String createUserString;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -86,6 +86,8 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
corsProperties.getAllowedMethods().forEach(config::addAllowedMethod);
// 配置允许跨域的请求头
corsProperties.getAllowedHeaders().forEach(config::addAllowedHeader);
// 配置允许跨域的响应头
corsProperties.getExposedHeaders().forEach(config::addExposedHeader);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.config.easyexcel;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* Easy Excel 枚举基类转换器
*
* @author Charles7c
* @since 2023/2/5 19:29
*/
public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer, String>> {
@Override
public Class<BaseEnum> supportJavaTypeKey() {
return BaseEnum.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 转换为 Java 数据(读取 Excel
*/
@Override
public BaseEnum convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return this.getEnum(BaseEnum.class, Convert.toStr(cellData.getData()));
}
/**
* 转换为 Excel 数据(写入 Excel
*/
@Override
public WriteCellData<String> convertToExcelData(BaseEnum<Integer, String> value,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNull(value)) {
return new WriteCellData<>("");
}
return new WriteCellData<>(value.getDescription());
}
/**
* 通过 value 获取枚举对象,获取不到时为 {@code null}
*
* @param enumType
* 枚举类型
* @param description
* 描述
* @return 对应枚举 ,获取不到时为 {@code null}
*/
private BaseEnum<Integer, String> getEnum(Class<?> enumType, String description) {
Object[] enumConstants = enumType.getEnumConstants();
for (Object enumConstant : enumConstants) {
if (ClassUtil.isAssignable(BaseEnum.class, enumType)) {
BaseEnum<Integer, String> baseEnum = (BaseEnum<Integer, String>)enumConstant;
if (baseEnum.getDescription().equals(description)) {
return baseEnum;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.config.easyexcel;
import java.math.BigDecimal;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
/**
* Easy Excel 大数值转换器Excel 中对长度超过 15 位的数值输入是有限制的,从 16 位开始无论录入什么数字均会变为 0因此输入时只能以文本的形式进行录入
*
* @author Charles7c
* @since 2023/2/5 19:29
*/
public class ExcelBigNumberConverter implements Converter<Long> {
/**
* Excel 输入数值长度限制
*/
private static final int MAX_LENGTH = 15;
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 转换为 Java 数据(读取 Excel
*/
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
}
/**
* 转换为 Excel 数据(写入 Excel
*/
@Override
public WriteCellData<Object> convertToExcelData(Long value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNotNull(value)) {
String str = Long.toString(value);
if (str.length() > MAX_LENGTH) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> writeCellData = new WriteCellData<>(new BigDecimal(value));
writeCellData.setType(CellDataTypeEnum.NUMBER);
return writeCellData;
}
}

View File

@@ -19,7 +19,6 @@ package top.charles7c.cnadmin.common.config.jackson;
import java.io.IOException;
import java.lang.reflect.Field;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
@@ -28,20 +27,23 @@ import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 通用枚举接口 IEnum 反序列化器
* 通用枚举基类 BaseEnum 反序列化器
*
* @author Charles7c
* @since 2023/1/8 13:56
*/
@JacksonStdImpl
public class IEnumDeserializer extends JsonDeserializer<IEnum> {
public class BaseEnumDeserializer extends JsonDeserializer<BaseEnum> {
/** 静态实例 */
public static final IEnumDeserializer SERIALIZER_INSTANCE = new IEnumDeserializer();
public static final BaseEnumDeserializer SERIALIZER_INSTANCE = new BaseEnumDeserializer();
@Override
public IEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
Class<?> targetClass = jsonParser.getCurrentValue().getClass();
String fieldName = jsonParser.getCurrentName();
String value = jsonParser.getText();
@@ -49,7 +51,7 @@ public class IEnumDeserializer extends JsonDeserializer<IEnum> {
}
/**
* 通过某字段对应值获取枚举获取不到时为 {@code null}
* 通过某字段对应值获取枚举实例获取不到时为 {@code null}
*
* @param targetClass
* 目标类型
@@ -57,17 +59,17 @@ public class IEnumDeserializer extends JsonDeserializer<IEnum> {
* 字段值
* @param fieldName
* 字段名
* @return 对应枚举 获取不到时为 {@code null}
* @return 对应枚举实例 获取不到时为 {@code null}
*/
public IEnum getEnum(Class<?> targetClass, String value, String fieldName) {
private BaseEnum getEnum(Class<?> targetClass, String value, String fieldName) {
Field field = ReflectUtil.getField(targetClass, fieldName);
Class<?> fieldTypeClass = field.getType();
Object[] enumConstants = fieldTypeClass.getEnumConstants();
for (Object enumConstant : enumConstants) {
if (ClassUtil.isAssignable(IEnum.class, fieldTypeClass)) {
IEnum iEnum = (IEnum)enumConstant;
if (iEnum.getValue().equals(Integer.valueOf(value))) {
return iEnum;
if (ClassUtil.isAssignable(BaseEnum.class, fieldTypeClass)) {
BaseEnum baseEnum = (BaseEnum)enumConstant;
if (baseEnum.getValue().equals(Integer.valueOf(value))) {
return baseEnum;
}
}
}

View File

@@ -18,26 +18,27 @@ package top.charles7c.cnadmin.common.config.jackson;
import java.io.IOException;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 通用枚举接口 IEnum 序列化器
* 通用枚举接口 BaseEnum 序列化器
*
* @author Charles7c
* @since 2023/1/8 13:56
*/
@JacksonStdImpl
public class IEnumSerializer extends JsonSerializer<IEnum> {
public class BaseEnumSerializer extends JsonSerializer<BaseEnum> {
/** 静态实例 */
public static final IEnumSerializer SERIALIZER_INSTANCE = new IEnumSerializer();
public static final BaseEnumSerializer SERIALIZER_INSTANCE = new BaseEnumSerializer();
@Override
public void serialize(IEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
public void serialize(BaseEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeObject(value.getValue());
}
}

View File

@@ -31,7 +31,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@@ -43,6 +42,8 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* Jackson 配置
*
@@ -89,15 +90,15 @@ public class JacksonConfiguration {
}
/**
* 针对通用枚举接口 IEnum 的序列化和反序列化
* 针对枚举基类 BaseEnum 的序列化和反序列化
*/
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(IEnum.class, IEnumSerializer.SERIALIZER_INSTANCE);
simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE);
SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper();
deserializers.addDeserializer(IEnum.class, IEnumDeserializer.SERIALIZER_INSTANCE);
deserializers.addDeserializer(BaseEnum.class, BaseEnumDeserializer.SERIALIZER_INSTANCE);
simpleModule.setDeserializers(deserializers);
ObjectMapper objectMapper = builder.createXmlMapper(false).build();

View File

@@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.type.ClassKey;
* 重写增强后:<br>
* 1. 同默认 1<br>
* 2. 同默认 2<br>
* 3. 如果也找不到 Enum 类型所有枚举父类的反序列化器开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型,则是找它的接口 IEnum 的反序列化器);<br>
* 3. 如果也找不到 Enum 类型所有枚举父类的反序列化器开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型,则是找它的接口 BaseEnum 的反序列化器);<br>
* 4. 同默认 3。
* </p>
*
@@ -56,7 +56,7 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers {
return deser;
}
// 重写增强开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型,则是找它的接口 IEnum 的反序列化器)
// 重写增强开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型,则是找它的接口 BaseEnum 的反序列化器)
for (Class<?> typeInterface : type.getInterfaces()) {
deser = this._classMappings.get(new ClassKey(typeInterface));
if (deser != null) {

View File

@@ -49,4 +49,9 @@ public class CorsProperties {
* 允许跨域的请求头
*/
private List<String> allowedHeaders = new ArrayList<>();
/**
* 允许跨域的响应头
*/
private List<String> exposedHeaders = new ArrayList<>();
}

View File

@@ -19,7 +19,7 @@ package top.charles7c.cnadmin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.annotation.IEnum;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 启用/禁用状态枚举
@@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.annotation.IEnum;
*/
@Getter
@RequiredArgsConstructor
public enum DisEnableStatusEnum implements IEnum<Integer> {
public enum DisEnableStatusEnum implements BaseEnum<Integer, String> {
/** 启用 */
ENABLE(1, "启用"),

View File

@@ -19,7 +19,7 @@ package top.charles7c.cnadmin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.annotation.IEnum;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 性别枚举
@@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.annotation.IEnum;
*/
@Getter
@RequiredArgsConstructor
public enum GenderEnum implements IEnum<Integer> {
public enum GenderEnum implements BaseEnum<Integer, String> {
/** 未知 */
UNKNOWN(0, "未知"),

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.util;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.esotericsoftware.minlog.Log;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.URLUtil;
import top.charles7c.cnadmin.common.config.easyexcel.ExcelBigNumberConverter;
import top.charles7c.cnadmin.common.exception.ServiceException;
/**
* Excel 工具类
*
* @author Charles7c
* @since 2023/2/5 18:00
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtils {
/**
* 导出
*
* @param list
* 导出数据集合
* @param fileName
* 文件名
* @param clazz
* 导出数据类型
* @param response
* 响应对象
*/
public static <V> void export(List<V> list, String fileName, Class<V> clazz, HttpServletResponse response) {
export(list, fileName, "Sheet1", clazz, response);
}
/**
* 导出
*
* @param list
* 导出数据集合
* @param fileName
* 文件名
* @param sheetName
* 工作表名称
* @param clazz
* 导出数据类型
* @param response
* 响应对象
*/
public static <V> void export(List<V> list, String fileName, String sheetName, Class<V> clazz,
HttpServletResponse response) {
try {
fileName = String.format("%s_%s.xlsx", fileName, DateUtil.format(new Date(), "yyyyMMddHHmmss"));
fileName = URLUtil.encode(fileName);
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
EasyExcel.write(response.getOutputStream(), clazz).autoCloseStream(false)
// 自动适配宽度
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 自动转换大数值
.registerConverter(new ExcelBigNumberConverter()).sheet(sheetName).doWrite(list);
} catch (Exception e) {
Log.error("Export excel occurred an error.", e);
throw new ServiceException("导出 Excel 出现错误");
}
}
}

View File

@@ -91,6 +91,17 @@ public class ExceptionUtils {
return exToDefault(supplier, null, exConsumer);
}
/**
* 如果有异常,返回空字符串
*
* @param exSupplier
* 可能会出现异常的方法执行
* @return /
*/
public static String exToBlank(ExSupplier<String> exSupplier) {
return exToDefault(exSupplier, "");
}
/**
* 如果有异常,返回默认值
*