【Excel导入/导出】多Sheet导入导出示例(4.7.x及以上版本)

代码示例

示例仅供参考

点击下载代码示例

业务场景

准备工作:两个模型,物料(Material)和物料类别(MaterialCategory)。
目标:在一个Excel模板中同时导入和导出两个模型的数据。

Material模型

@Model.model(Material.MODEL_MODEL)
@Model.Advanced(unique = {"code"})
@Model(displayName = "物料", labelFields = {"name"})
public class Material extends IdModel {

    private static final long serialVersionUID = -2594216864389636135L;

    public static final String MODEL_MODEL = "maas.Material";

    @Field.String
    @Field(displayName = "物料编码", required = true)
    private String code;

    @Field.String
    @Field(displayName = "物料名称", required = true)
    private String name;
}

MaterialCategory模型

@Model.model(MaterialCategory.MODEL_MODEL)
@Model.Advanced(unique = {"code"})
@Model(displayName = "物料类别", labelFields = {"name"})
public class MaterialCategory extends IdModel {

    private static final long serialVersionUID = 6300896634558908349L;

    public static final String MODEL_MODEL = "maas.MaterialCategory";

    @Field.String
    @Field(displayName = "类别编码", required = true)
    private String code;

    @Field.String
    @Field(displayName = "类别名称", required = true)
    private String name;
}

模板定义

MaterialTemplate

@Component
public class MaterialTemplate implements ExcelTemplateInit {

    public static final String TEMPLATE_NAME = "materialTemplate";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        WorkbookDefinitionBuilder builder = WorkbookDefinitionBuilder.newInstance(Material.MODEL_MODEL, TEMPLATE_NAME)
                .setDisplayName("物料和物料类别")
                .setEachImport(Boolean.FALSE);//设置importData的入参为 (ExcelImportContext importContext, List<MaterialCategory> data),如入参是单个对象,请删除setEachImport(Boolean.FALSE)

        createMaterialSheet(builder);

        createMaterialCategorySheet(builder);

        return Collections.singletonList(builder.build());
    }

    private static void createMaterialSheet(WorkbookDefinitionBuilder builder) {
        builder.createSheet().setName("物料")
                .createBlock(Material.MODEL_MODEL, ExcelAnalysisTypeEnum.FIXED_HEADER, ExcelDirectionEnum.HORIZONTAL, "A1:B2")
                .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle()).setIsConfig(Boolean.TRUE)
                .createCell().setField("code").setAutoSizeColumn(Boolean.TRUE).and()
                .createCell().setField("name").setAutoSizeColumn(Boolean.TRUE).and()
                .and()
                .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle(v -> v.setBold(Boolean.TRUE)).setHorizontalAlignment(ExcelHorizontalAlignmentEnum.CENTER))
                .createCell().setValue("物料编码").and()
                .createCell().setValue("物料名称");
    }

    private static void createMaterialCategorySheet(WorkbookDefinitionBuilder builder) {
        builder.createSheet().setName("物料类别")
                .createBlock(MaterialCategory.MODEL_MODEL, ExcelAnalysisTypeEnum.FIXED_HEADER, ExcelDirectionEnum.HORIZONTAL, "A1:B2")
                .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle()).setIsConfig(Boolean.TRUE)
                .createCell().setField("code").setAutoSizeColumn(Boolean.TRUE).and()
                .createCell().setField("name").setAutoSizeColumn(Boolean.TRUE).and()
                .and()
                .createHeader().setStyleBuilder(ExcelHelper.createDefaultStyle(v -> v.setBold(Boolean.TRUE)).setHorizontalAlignment(ExcelHorizontalAlignmentEnum.CENTER))
                .createCell().setValue("物料类别编码").and()
                .createCell().setValue("物料类别名称");
    }
}

上述模板定义了一个工作簿(Workbook),使用createrSheet()创建了两个工作表(Sheet),其名称分别为物料物料类别

导入扩展点

MaterialImportExtPoint

@Component
@Ext(ExcelImportTask.class)
public class MaterialImportExtPoint implements ExcelImportDataExtPoint<List<Material>> {

    @ExtPoint.Implement(expression = "importContext.definitionContext.name==\"" + MaterialTemplate.TEMPLATE_NAME + "\" && importContext.currentSheetNumber == 0")
    @Override
    public Boolean importData(ExcelImportContext importContext, List<Material> data) {
        Models.directive().runWithoutResult(() -> Models.data().createOrUpdateWithFieldBatch(data),
                SystemDirectiveEnum.EXT_POINT,
                SystemDirectiveEnum.FROM_CLIENT,
                SystemDirectiveEnum.BUILT_ACTION);
        return Boolean.TRUE;
    }
}

上述示例使用了平台内置的批量创建或更新的方法,业务使用时可根据业务逻辑自行定义导入逻辑。

MaterialCategoryImportExtPoint

@Component
@Ext(ExcelImportTask.class)
public class MaterialCategoryImportExtPoint implements ExcelImportDataExtPoint<List<MaterialCategory>> {

    @ExtPoint.Implement(expression = "importContext.definitionContext.name==\"" + MaterialTemplate.TEMPLATE_NAME + "\" && importContext.currentSheetNumber == 1")
    @Override
    public Boolean importData(ExcelImportContext importContext, List<MaterialCategory> data) {
        Models.directive().runWithoutResult(() -> Models.data().createOrUpdateWithFieldBatch(data),
                SystemDirectiveEnum.EXT_POINT,
                SystemDirectiveEnum.FROM_CLIENT,
                SystemDirectiveEnum.BUILT_ACTION);
        return Boolean.TRUE;
    }
}

上述示例使用了平台内置的批量创建或更新的方法,业务使用时可根据业务逻辑自行定义导入逻辑。

在定义导入扩展点时,我们通过importContext.definitionContext.name来确定导入扩展点对应的工作簿(Workbook),用importContext.currentSheetNumber来判断当前导入的是第几个工作表(Sheet)

综上,上述通过模板定义和导入扩展点实现了多Sheet导入的功能。

导出模板

在上述模板定义例子中,我们无需做任何修改即可在导出中使用。

特殊情况下,我们可以通过setType方法设置模板的使用范围。

  • ExcelTemplateTypeEnum#IMPORT:仅导入使用
  • ExcelTemplateTypeEnum#EXPORT:仅导出使用
  • ExcelTemplateTypeEnum#IMPORT_EXPORT:导入和导入都可以使用

导出扩展点

MaterialExportExtPoint

@Component
@Ext(ExcelExportTask.class)
public class MaterialExportExtPoint extends ExcelExportSameQueryPageTemplate<Object> implements ExcelExportFetchDataExtPoint {

    @Resource
    private HookApi hookApi;

    @ExtPoint.Implement(expression = "context.name==\"" + MaterialTemplate.TEMPLATE_NAME + "\"")
    @Override
    public List<Object> fetchExportData(ExcelExportTask exportTask, ExcelDefinitionContext context) {
        // 第一个Sheet使用默认查询即可
        List<Object> results = super.fetchExportData(exportTask, context);
        // 自定义查询第二个Sheet的数据
        results.add(queryList(Pops.<MaterialCategory>lambdaQuery()
                .from(MaterialCategory.MODEL_MODEL)
                .ge(MaterialCategory::getId, 0L)));
        return results;
    }

    protected <T extends IdModel> List<T> queryList(IWrapper<T> wrapper) {
        return Models.directive().run(() -> {
            String modelModel = wrapper.getModel();
            Pagination<T> pagination = new Pagination<>();
            pagination.setModel(modelModel);
            hookApi.before(modelModel, FunctionConstants.queryPage, pagination, wrapper);
            Pagination<T> firstPage = queryFirstPage(pagination, wrapper);
            List<T> results = queryAllPages(modelModel, firstPage, wrapper);
            hookApi.after(modelModel, FunctionConstants.queryPage, new Pagination<T>().setContent(results));
            return results;
        });
    }

    protected <T extends IdModel> Pagination<T> queryFirstPage(Pagination<T> pagination, IWrapper<T> wrapper) {
        return queryPage(pagination, wrapper);
    }

    protected <T extends IdModel> List<T> queryAllPages(String model, Pagination<T> firstPage, IWrapper<T> wrapper) {
        List<T> results = firstPage.getContent();
        Integer totalPages = firstPage.getTotalPages();
        if (totalPages == null || totalPages <= 1) {
            return results;
        }
        for (int i = 2; i <= totalPages; i++) {
            Pagination<T> pagination = new Pagination<>();
            pagination.setModel(model);
            pagination.setCurrentPage(i);
            pagination.setSize(firstPage.getSize());
            wrapper.setModel(model);
            pagination = queryPage(pagination, wrapper);
            results.addAll(pagination.getContent());
        }
        return results;
    }

    protected <T extends IdModel> Pagination<T> queryPage(Pagination<T> pagination, IWrapper<T> wrapper) {
        return Models.origin().queryPage(pagination, wrapper);
    }
}

上述示例使用了平台内置的HookApi进行权限控制,导出时可自动根据当前用户的数据权限添加过滤条件。

本文来自投稿,不代表Oinone社区立场,如若转载,请注明出处:https://doc.oinone.top/backend/6851.html

(2)
张博昊的头像张博昊数式管理员
上一篇 2024年4月22日 pm2:34
下一篇 2024年4月24日 pm4:45

相关推荐

  • Excel导入扩展点-整体导入(批量导入)

    1、【导入】在有些场景,需要获取Excel导入的整体数据,进行批量的操作或者校验 可以通过实现导入扩展点的方式实现,入参data是导入Excel的数据列表;业务可以根据实际情况进行数据校验 1)Excel模板定义,需要设置setEachImport(false) 2)导入扩展点API定义 pro.shushi.pamirs.file.api.extpoint.ExcelImportDataExtPoint#importData 3)示例代码参考: pro.shushi.pamirs.translate.extpoint.ResourceTranslationImportExtPoint#importData @Slf4j @Component @Ext(ExcelImportTask.class) public class ResourceTranslationImportExtPoint extends AbstractExcelImportDataExtPointImpl<List<ResourceTranslationItem>> { @Override //TODO 表达式,可以自定义,比如可以支持1个模型的多个【导入名称】的不同模板 @ExtPoint.Implement(expression = "importContext.definitionContext.model==\"" + ResourceTranslation.MODEL_MODEL + "\"") public Boolean importData(ExcelImportContext importContext, List<ResourceTranslationItem> dataList) { //TODO dataList就是excel导入那个sheet的所有内容 return true; } } 2、【导入】逐行导入的时候做事务控制 在模板中定义中增加事务的定义,并设置异常后回滚。参加示例代码: excel模板定义 @Component public class DemoItemImportTemplate implements ExcelTemplateInit { public static final String TEMPLATE_NAME = "商品导入模板"; @Override public List<ExcelWorkbookDefinition> generator() { //定义事务(导入处理中,只操作单个表的不需要事务定义。) //是否定义事务根据实际业务逻辑确定。比如:有些场景在导入前需要删除数据后在进行导入就需要定义事务 InitializationUtil.addTxConfig(DemoItem.MODEL_MODEL, ExcelDefinitionContext.EXCEL_TX_CONFIG_PREFIX + TEMPLATE_NAME); return Collections.singletonList( ExcelHelper.fixedHeader(DemoItem.MODEL_MODEL, TEMPLATE_NAME) .setType(ExcelTemplateTypeEnum.IMPORT) .createSheet("商品导入-sheet1") .createBlock(DemoItem.MODEL_MODEL) .addUnique(DemoItem.MODEL_MODEL,"name") .addColumn("name","名称") .addColumn("description","描述") .addColumn("itemPrice","单价") .addColumn("inventoryQuantity","库存") .build().setEachImport(true) //TODO 设置异常后回滚的标识,这个地方会回滚事务 .setHasErrorRollback(true) .setExcelImportMode(ExcelImportModeEnum.SINGLE_MODEL) ); } } 导入逻辑处理 @Slf4j @Component @Ext(ExcelImportTask.class) public class DemoItemImportExtPoint extends AbstractExcelImportDataExtPointImpl<DemoItem> implements ExcelImportDataExtPoint<DemoItem> { @Autowired private DemoItemService demoItemService; @Override @ExtPoint.Implement(expression = "importContext.definitionContext.model == \"" + DemoItem.MODEL_MODEL + "\"") public Boolean importData(ExcelImportContext importContext, DemoItem data) { ExcelImportTask importTask = importContext.getImportTask(); try { DemoItemImportTask hrExcelImportTask = new DemoItemImportTask().queryById(importTask.getId()); String publishUserName = Optional.ofNullable(hrExcelImportTask).map(DemoItemImportTask::getPublishUserName).orElse(null); data.setPublishUserName(publishUserName); demoItemService.create(data); } catch(PamirsException e) { log.error("导入异常", e); } catch (Exception e) { log.error("导入异常", e); } return Boolean.TRUE; } }

    2023年12月7日
    1.3K00
  • Oinone请求调用链路

    Oinone请求调用链路 请求格式与简单流程 在Oinone中请求数据存储在请求体中,以GQL的方式进行表示,也就是GQL格式的请求。 当我们发送一个GQL格式的请求,后端会对GQL进行解析,确定想要执行的方法,并对这个方法执行过程中所用到的模型进行构建,最后返回响应。 请求 # 请求路径 pamirs/base http://127.0.0.1:8090/pamirs/base # 请求体内容 query{ petShopProxyBQuery{ sayHello(shop:{shopName:"cpc"}){ shopName } } } 解析 # 简单理解 query 操作类型 petShopProxyBQuery 模块名称 + Query sayHello 方法 fun sayHello() 可以传入参数,参数名为 shop shopName 需要得到的值 响应 # data中的内容 "data": { "petShopQuery": { "hello": { "shopName": "cpc" } } } 具体流程 Oinone是基于SpringBoot的,在Controller中处理请求 会接收所有以 /pamirs 开始的POST请求,/pamirs/后携带的是模块名 @RequestMapping( value = "/pamirs/{moduleName:^[a-zA-Z][a-zA-Z0-9_]+[a-zA-Z0-9]$}", method = RequestMethod.POST ) public String pamirsPost(@PathVariable("moduleName") String moduleName, @RequestBody PamirsClientRequestParam gql, HttpServletRequest request, HttpServletResponse response) { …….. } 整体脉络 第四步执行中有两大重要的步骤,一步是动态构建GQL,一步是执行请求。 动态构建GQL 请求执行

    2024年12月1日
    94600
  • 自定义RSQL占位符(placeholder)及在权限中使用

    1 自定义RSQL占位符常用场景 统一的数据权限配置 查询表达式的上下文变量扩展 2 自定义RSQL的模板 /** * 演示Placeholder占位符基本定义 * * @author Adamancy Zhang at 13:53 on 2024-03-24 */ @Component public class DemoPlaceHolder extends AbstractPlaceHolderParser { private static final String PLACEHOLDER_KEY = "${thisPlaceholder}"; /** * 占位符 * * @return placeholder */ @Override public String namespace() { return PLACEHOLDER_KEY; } /** * 占位符替换值 * * @return the placeholder replace to the value */ @Override protected String value() { return PamirsSession.getUserId().toString(); } /** * 优先级 * * @return execution order of placeholders, ascending order. */ @Override public Integer priority() { return 0; } /** * 是否激活 * * @return the placeholder is activated */ @Override public Boolean active() { return true; } } 注意事项 在一些旧版本中,priority和active可能不起作用,为保证升级时不受影响,请保证该属性配置正确。 PLACEHOLDER_KEY变量表示自定义占位符使用的关键字,需按照所需业务场景的具体功能并根据上下文语义正确定义。 为保证占位符可以被正确替换并执行,所有占位符都不应该出现重复,尤其是不能与系统内置的重复。 3 占位符使用时的优先级问题 多个占位符在进行替换时,会根据优先级按升序顺序执行,如需要指定替换顺序,可使用Spring的Order注解对其进行排序。 import org.springframework.core.annotation.Order; @Order(0) 4 Oinone平台内置的占位符 占位符 数据类型 含义 备注 ${currentUser} String 当前用户ID 未登录时无法使用 ${currentRoles} Set<String> 当前用户的角色ID集合 未登录时无法使用 5 如何覆盖平台内置的占位符? 通过指定占位符的优先级,并定义相同的namespace可优先替换。 6 如何定义会话级别的上下文变量? 在上述模板中,我们使用的是Oinone平台内置的上下文变量进行演示,通常情况下,我们需要根据实际业务场景增加上下文变量,以此来实现所需功能。 下面,我们将根据当前用户获取当前员工ID定义该上下文变量进行演示。 /** * 员工Session * * @author Adamancy Zhang at 14:33 on 2024-03-24 */ @Component public class EmployeeSession implements HookBefore { private static final String SESSION_KEY = "CUSTOM_EMPLOYEE_ID"; @Autowired private DemoEmployeeService demoEmployeeService; public static String getEmployeeId() { return PamirsSession.getTransmittableExtend().get(SESSION_KEY);…

    2024年3月24日
    1.5K00
  • Maven Setting 配置详解

    常用标签概览 servers/server:配置私有仓库用户名和密码进行认证,以 id 进行关联。 mirrors/mirror:配置镜像仓库拉取时的地址源和额外配置。 profiles/profile:配置多个可能使用的镜像仓库。 activeProfiles/activeProfile:配置默认激活的 profile,以 id 进行关联。 Oinone 私有仓库配置 以下配置可以在使用 Oinone 私有仓库的同时,也可以正常使用 aliyun 镜像源。 <servers> <server> <id>shushi</id> <username>${username}</username> <password>${password}</password> </server> </servers> <mirrors> <mirror> <id>shushi</id> <mirrorOf>shushi</mirrorOf> <url>http://ss.nexus.ixtx.fun/repository/public</url> <!– 忽略 https 认证,maven 版本过高时需要配置 –> <blocked>false</blocked> </mirror> </mirrors> <profiles> <profile> <id>shushi</id> <repositories> <repository> <!– 对应 server.id –> <id>shushi</id> <url>http://ss.nexus.ixtx.fun/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <!– 对应 server.id –> <id>shushi</id> <url>http://ss.nexus.ixtx.fun/repository/snapshots</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> <profile> <id>aliyun</id> <repositories> <repository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <!– 使用 shushi 私有仓库 –> <activeProfile>shushi</activeProfile> <!– 使用 aliyun 镜像仓库 –> <activeProfile>aliyun</activeProfile> </activeProfiles> 常见问题 使用 mvn 时无法拉取 Oinone 最新版镜像,提示找不到对应的包 原因:在 Oinone 开源后,oinone-pamirs 内核相关包都被部署到 maven 中央仓库,但由于其他镜像仓库的同步存在延时,在未正确同步的其他镜像源拉取时会出现找不到对应的包相关异常。 解决方案:检查 mirrors 中是否配置了 aliyun 镜像源,如果配置了,使用上述 Oinone 私有仓库配置重新配置后,再进行拉取。这一问题是由于 mirrors 配置不当,拦截了所有从 maven 中央仓库拉取的地址替换为了 aliyun 镜像源导致的。

    2025年11月10日
    41100
  • 重写QueryPage时,增加额外的条件

    在需要对QueryPage增加额外的查询条件,比如DemoItem增加只展示创建人为当前用户的数据 @Function.Advanced(type = FunctionTypeEnum.QUERY, displayName = "查询列表") @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API}) public Pagination<DemoItem> queryPage(Pagination<DemoItem> page, IWrapper<DemoItem> queryWrapper) { LambdaQueryWrapper<DemoItem> qw = ((QueryWrapper<DemoItem>) queryWrapper).lambda(); qw.eq(DemoItem::getCreateUid, PamirsSession.getUserId()); return demoItemService.queryPage(page, qw); }

    2023年11月1日
    83100

Leave a Reply

登录后才能评论