6.1 文件与导入导出(改)

导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁

文件的上传下载以及业务数据的导入导出是企业级软件一个比较常规的需求,甚至是巨量的需求。业务有管理需要一般都伴随有导入导出需求,导入导出在一定程度上是企业级软件和效率工具(office工具)的桥梁。oinone的文件模块就提供了通用的导入导出实现方案,以简单、一致、可扩展为目标,简单是快速入门,一致是用户操作感知一致、可扩展是满足用户最大化的自定义需求。

下图为文件导入导出的实现示意图,大家可以做一个整体了解

6.1 文件与导入导出(改)

图6-1-1 文件导入导出实现示意图

一、基础能力

准备工作

  1. pamirs-demo-api的pom文件中引入pamirs-file2-api包依赖
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-file2-api</artifactId>
        </dependency>

图6-1-2 引入pamirs-file2-api包依赖

  1. DemoModule增加对FileModule的依赖
@Module(dependencies = {FileModule.MODULE_MODULE})

图6-1-3 DemoModule增加对FileModule的依赖

  1. pamirs-demo-boot的pom文件中引入pamirs-file2-core包依赖
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-file2-core</artifactId>
        </dependency>

图6-1-4 启动工程引入pamirs-file2-core包依赖

  1. pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加file,即在启动模块中增加file模块
pamirs:
    boot:
    modules:
        - file

图6-1-5 pamirs-demo-boot的application-dev.yml文件中增加配置

  1. pamirs-demo-boot的application-dev.yml文件中增加oss的配置。更多有关文件相关配置详见4.1.1【模块之yml文件结构详解】一文
cdn:
    oss:
    name: 阿里云
    type: OSS
    bucket: demo
    uploadUrl: #换成自己的oss上传服务地址
    downUrl: #换成自己的oss下载服务地址
    accessKeyId: #阿里云oss的accessKeyId
    accessKeySecret: #阿里云oss的accessKeySecret
    mairDir: upload/demo #换成自己的目录
    validTime: 360000
    timeout: 60000
    active: true
    referer: www.shushi.pro

图6-1-6 application-dev.yml文件中增加oss的配置

  1. 其他文件系统支持

a. 文件系统类型

类型 服务
OSS 阿里云
UPYUN 又拍云
MINIO Minio
HUAWEI_OBS 华为云
LOCAL 本地文件存储

表6-1-1 支持的文件系统类型

b. OSS配置示例

ⅰ. 华为云OBS

cdn:
  oss:
    name: 华为云
    type: HUAWEI_OBS
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: obs.cn-east-2.myhuaweicloud.com
    downloadUrl: obs.cn-east-2.myhuaweicloud.com
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    allowedOrigin: http://192.168.95.31:8888,https://xxxx.xxxxx.com
    referer:

图6-1-7 OBS的配置说明

ⅱ. MINIO

cdn:
  oss:
    name: minio
    type: MINIO
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: http://192.168.243.6:32190 #(根据实际情况修改)
    downloadUrl: http://192.168.243.6:9000 #(根据实际情况修改)
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    referer:
    localFolderUrl:

图6-1-8 MINIO的配置说明

ⅲ. 又拍云

cdn:
  oss:
    name: 又拍云
    type: UPYUN
    bucket: pamirs #(根据实际情况修改)
    uploadUrl: v0.api.upyun.com
    downloadUrl: v0.api.upyun.com
    accessKeyId: 你的accessKeyId
    accessKeySecret: 你的accessKeySecret
    # 根据实际情况修改
    mainDir: upload/
    validTime: 3600000
    timeout: 600000
    active: true
    referer: 

图6-1-9 又拍云的配置说明

ⅳ. 本地文件存储

cdn:
  oss:
    name: 本地文件NG系统
    type: LOCAL
    bucket: pamirs #(根据实际情况修改)
    # uploadUrl 这个是Oinone后端服务地址和端口
    uploadUrl: http://127.0.0.1:8091
    # downloadUrl前端地址,即直接映射在nginx的静态资源的路径和端口
    downloadUrl: http://127.0.0.1:8081
    validTime: 3600000
    timeout: 600000
    active: true
    referer:
    # 本地Nginx静态资源目录
    localFolderUrl: /Users/oinone/nginx/html/designer/static

图6-1-10 本地文件的配置说明

文件上传

文件上传下载是常用功能,也是导入导出功能的基础。接下的例子,我们来看看Oinone是如何玩转文件上传的功能的

@Field.String
@Field(displayName = "图片" ,multi = true)
@UxForm.FieldWidget(@UxWidget(widget = "Upload"))
private List<String> imgUrls;

Step1 修改PetTalent模型

为PetTalent模型增加一个字段imgUrls ,用于保存上传后的文件地址。同时定义Form视图的时候改字段的前端展示widget为“Upload”

<field data="imgUrls" widget="Upload"/>

Step2 修改PetTalent的Form视图

因为前面教程中为PetTalent的Form视图写了自定义视图,所以这里需要手工加上字段配置,不然展示不出来,同时也要加上widget="Upload"的配置,因为自定义视图不会再去读字段上定义的UxWidget,字段上的UxWidget只对默认视图生效。

Step3 重启看效果

image.png

默认导入导出功能

因为前面我们在Demo的启动工程中加入对pamirs-file2-core的jar包依赖同时启动模块列表中也增加了file模块,所以所有的默认表格页面都出现了默认的导入导出按钮。

image.png

但是我们发现宠物达人的表格就没有出现导入导出按钮,首先我们自定义了表格视图,而且在学习自定义Action的时候修改了表格视图的Action的填充方式为手工指定。

image.png

Step1 修改PetTalent的Table视图

修改PetTalent的自定义Table视图,把表格视图的Action的填充方式改为自动填充,或者根据平台默认前端动作增加导入、导出两个前端动作

<template slot="actions" autoFill="true">
  <!--        <action actionType="client" name="demo.doNothing" label="第一个自定义Action" />-->
  <!--        <action name="delete" label="删除" />-->
  <!--        <action name="redirectCreatePage" label="创建" />-->
</template>
<template slot="actions" autoFill="true">
  <action actionType="client" name="demo.doNothing" label="第一个自定义Action" />
  <action name="delete" label="删除" />
  <action name="redirectCreatePage" label="创建" />
<!--   <action name="$$internal_GotoListExportDialog" label="导出" />
  <action name="$$internal_GotoListImportDialog" label="导入" /> -->
    <action name="internalGotoListExportDialog" label="导出" />
    <action name="internalGotoListImportDialog" label="导入" />
</template>

Step2 重启看效果

image.png

Step3 注意点

  1. 系统生成的导入模版以默认的Form视图为准

  2. 系统生成的导出模版以默认的Table视图为准

  3. 有自定义导入导出模版后,默认模版会失效

自定义导入导出模版与逻辑

数据的导出(举例一)

  1. 通常我们只需定义一个导出模版

  2. 默认导出方法是调用模型的queryPage函数(即在日常开发中我们经常会自定义模型的queryPage函数),为了跟页面查询效果保持一致,默认导出方式实现中手工生效了hook,所以类似数据权限的hook都会生效,与页面查询逻辑一致。

  3. 如果不一致可以实现ExcelExportFetchDataExtPoint扩展点来完成,在举例二中介绍

Step1 新建PetTalentExportTemplate

新建PetTalentExportTemplate类实现ExcelTemplateInit接口。

  1. 实现ExcelTemplateInit接口,系统会自动调用generator方法,并注册该方法返回的模版定义列表,例子中定义了一个导出模版“宠物达人导出”

  2. 关联对象为集合,petShops[*].shopName

  3. 关联对象为模型,creater.name

package pro.shushi.pamirs.demo.core.init.exports;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;

import java.util.Collections;
import java.util.List;

@Component
public class PetTalentExportTemplate implements ExcelTemplateInit {
    public static final String TEMPLATE_NAME ="宠物达人导出";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.EXPORT)
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("creater.name","创建者")
                        .build());
    }

}

Step2 重启看效果

  1. 点击【宠物达人】菜单进入宠物达人列表页,选择记录点击【导出】按钮。导出规则为

a. 如果勾选列表记录行,则只导出勾选记录

b. 如果没有勾选,则根据查询条件进行导出

image.png

  1. 在导出弹出框中选择导出模版,点击【导出】按钮提示成功,则表示导出任务被正常创建

image.png

  1. 切换换模块,进入【文件】应用,点击【导出任务】菜单查看导出任务情况。分别对应三种情况:

a. Table视图中带来条件但查询结果没有数据,则会报错

b. Table视图中带来条件或无条件但查询结果有数据,则会成功

c. Table视图中勾选了数据行,则只导出勾选数据行对应数据

image.png

数据的导出(举例二)

如果您有特殊需求,请实现ExcelExportFetchDataExtPoint扩展点来完成,用这种模式请自行控制数据权限,小心小心再小心。因为数据权限是通过hook机制来完成控制,在面向切面-拦截器一文中有说明拦截器只有在前端请求时才会默认生效,如果后端代码自行调用需要生效hook机制需要根据函数之元位指令一文手动去生效。

下面例子中为了复用了ExcelExportSameQueryPageTemplate的逻辑主要是hook的机制,ExcelExportSameQueryPageTemplate手工生效了hook,例子中继承了ExcelExportSameQueryPageTemplate类,只做返回结果的后置处理

Step1 修改PetTalentExportTemplate

package pro.shushi.pamirs.demo.core.init.exports;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.context.ExcelDefinitionContext;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.extpoint.ExcelExportFetchDataExtPoint;
import pro.shushi.pamirs.file.api.extpoint.impl.ExcelExportSameQueryPageTemplate;
import pro.shushi.pamirs.file.api.model.ExcelExportTask;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;
import pro.shushi.pamirs.meta.annotation.ExtPoint;

import java.util.Collections;
import java.util.List;

@Component
public class PetTalentExportTemplate extends ExcelExportSameQueryPageTemplate implements ExcelTemplateInit , ExcelExportFetchDataExtPoint {
    public static final String TEMPLATE_NAME ="宠物达人导出";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.EXPORT)
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("creater.name","创建者")
                        .build());
    }

    //表达式里的常量需要使用" "包括
    @Override
    @ExtPoint.Implement(expression = "context.model == " + PetTalent.MODEL_MODEL+" && context.name == " +TEMPLATE_NAME+"" )
    public List<Object> fetchExportData(ExcelExportTask exportTask, ExcelDefinitionContext context) {
        //复用父类的权限控制等
        List<Object> result =  super.fetchExportData(exportTask,context);
        //对petTalents做一些操作
        List<PetTalent>  petTalents =  (List<PetTalent>)result.get(0);
        for(PetTalent petTalent:petTalents){
            petTalent.setName(petTalent.getName()+":被修改过");
        }
        return result;
    }
}

Step2 重启看效果

相同的操作,发现导出文件内容被特定修改过

image.png

数据的导入(举例一)

Step1 创建PetTalentImportTemplate

新建PetTalentImportTemplate类实现ExcelImportDataExtPoint和ExcelTemplateInit接口。

  1. 实现ExcelTemplateInit接口,系统会自动调用generator方法,并注册该方法返回的模版定义列表,例子中定义了一个导入模版“宠物达人导入”

a. 通过ExcelCellDefinition自定义单元可选值,并在addColumn时绑定如:.addColumn("petTalentSex",sexExcelCellDefinition)

b. 通过addUnique,设置导入时的唯一key,相同key同时相连行会进行合并操作

  1. 实现ExcelImportDataExtPoint的importData扩展点,实现自定义的导入逻辑

a. 声明扩展点的生效规则:@ExtPoint.Implement(expression = "importContext.definitionContext.model == \" + PetTalent.MODEL_MODEL+"\ && importContext.definitionContext.name == \" +TEMPLATE_NAME+"\" ) 。可以用表达式的上下文就是方法入参

b. 记录错误:importTask.addTaskMessage(TaskMessageLevelEnum.ERROR,"达人名称不能为空")。

c. importData方法返回true,代表继续下一行的导入。返回false则表示中断导入

package pro.shushi.pamirs.demo.core.init.imports;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import pro.shushi.pamirs.demo.api.enumeration.PetTalentSexEnum;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.demo.api.model.PetTalent;
import pro.shushi.pamirs.file.api.context.ExcelImportContext;
import pro.shushi.pamirs.file.api.enmu.ExcelTemplateTypeEnum;
import pro.shushi.pamirs.file.api.enmu.ExcelValueTypeEnum;
import pro.shushi.pamirs.file.api.enmu.TaskMessageLevelEnum;
import pro.shushi.pamirs.file.api.extpoint.AbstractExcelImportDataExtPointImpl;
import pro.shushi.pamirs.file.api.extpoint.ExcelImportDataExtPoint;
import pro.shushi.pamirs.file.api.model.ExcelCellDefinition;
import pro.shushi.pamirs.file.api.model.ExcelImportTask;
import pro.shushi.pamirs.file.api.model.ExcelWorkbookDefinition;
import pro.shushi.pamirs.file.api.util.ExcelHelper;
import pro.shushi.pamirs.file.api.util.ExcelTemplateInit;
import pro.shushi.pamirs.framework.connectors.data.sql.Pops;
import pro.shushi.pamirs.framework.connectors.data.tx.transaction.Tx;
import pro.shushi.pamirs.meta.annotation.ExtPoint;
import pro.shushi.pamirs.meta.api.dto.config.TxConfig;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;

import java.util.*;

@Component
public class PetTalentImportTemplate extends AbstractExcelImportDataExtPointImpl<PetTalent> implements ExcelTemplateInit , ExcelImportDataExtPoint<PetTalent> {
    public static final String TEMPLATE_NAME ="宠物达人导入";

    @Override
    public List<ExcelWorkbookDefinition> generator() {
        //自定义定义Excel单元格,设置单元可选值
        Map<String,String> sexType = new HashMap<>();
        sexType.put(String.valueOf(PetTalentSexEnum.FEMAL.value()),PetTalentSexEnum.FEMAL.displayName());
        sexType.put(String.valueOf(PetTalentSexEnum.MAN.value()),PetTalentSexEnum.MAN.displayName());
        ExcelCellDefinition sexExcelCellDefinition = new ExcelCellDefinition();
        sexExcelCellDefinition.setType(ExcelValueTypeEnum.ENUMERATION).setValue("性别").setFormat(JSON.toJSONString(sexType));

        //可以返回多个模版,导出的时候页面上由用户选择导出模版
        return Collections.singletonList(
                ExcelHelper.fixedHeader(PetTalent.MODEL_MODEL,TEMPLATE_NAME)
                        .createBlock(TEMPLATE_NAME,PetTalent.MODEL_MODEL)
                        .setType(ExcelTemplateTypeEnum.IMPORT)
                        //相同key,相连行会进行合并操作
                        .addUnique(PetTalent.MODEL_MODEL,"name")
                        .addColumn("name","名称")
                        .addColumn("annualIncome","年收入")
                        .addColumn("petShops[*].shopName","店铺名")
                        .addColumn("petTalentSex",sexExcelCellDefinition)
                        .build());
    }

    //表达式里的常量需要使用" "包括
    @Override
    @ExtPoint.Implement(expression = "importContext.definitionContext.model == \"" + PetTalent.MODEL_MODEL+"\" && importContext.definitionContext.name == \"" +TEMPLATE_NAME+"\"" )
    public Boolean importData(ExcelImportContext importContext, PetTalent data) {
        ExcelImportTask importTask = importContext.getImportTask();
        if(StringUtils.isBlank(data.getName())){
            //返回true表示继续读下一行。返回false表示中断
            importTask.addTaskMessage(TaskMessageLevelEnum.ERROR,"达人名称不能为空");
            return Boolean.TRUE;
        }
        if(CollectionUtils.isNotEmpty(data.getPetShops())){
            List<String > petShopNams = new ArrayList<>();
            for(PetShop petShop:data.getPetShops()){
                petShopNams.add(petShop.getShopName());
            }
            //FIXME 抽到Service中去,散落的查询逻辑不好维护。
            IWrapper<PetShop> queryWrapper = Pops.<PetShop>lambdaQuery().from(PetShop.MODEL_MODEL).in(PetShop::getShopName, petShopNams);
            List<PetShop> shops = new PetShop().queryList(queryWrapper);
            data.setPetShops(shops);
        }
        Tx.build(new TxConfig().setPropagation(Propagation.REQUIRED.value())).executeWithoutResult(status -> {
            data.create();
            data.fieldSave(PetTalent::getPetShops);
        });
        return Boolean.TRUE;
    }

}

Step2 重启看效果

  1. 点击【宠物达人】菜单进入宠物达人列表页面,点击【导入】。

  2. 在导入弹出框中选择导入模版后点击下载模版,并提按模版填写数据。

  3. 在导入弹出框中点击【点击上传】按钮,上传填写好的文件,并点击【导入】按钮

image.png

image.png

  1. 列表页增加对应数据行,点击详情可以看到所有相关数据

image.png

这个例子中用了ExcelHelper这个简单工具类来定义模版,基本能满足日常需求。复杂的可以直接用WorkbookDefinitionBuilder来完成。

导入导出的更多配置项

基础设计

以POI和EasyExcel共有的Excel处理能力为基础进行设计。

  1. 以Workbook为核心的模板定义

  2. 支持多个Sheet定义。

  3. 支持【固定表头】形式的模板定义。(陆续支持【固定格式】和【混合格式】)

  4. 支持三个维度的定义方式:表头定义(列定义),行定义,单元格定义。

  5. 支持单元格样式、字体样式等Excel样式定义。

  6. 支持合并单元格。

  7. 支持数字、字符串、时间、日期、公式等Excel的数据处理能力。

  8. 支持直接存数据库和获取导入数据两种方式。

Excel功能

模板定义,使用数据初始化或页面操作的方式创建Excel模板。

导出任务的创建,页面选择导出模板、导出条件后,点击确定后,创建导出任务。导出方式为:异步,在导出任务页面查看导出任务,并允许多次下载。

导入任务的创建,页面选择模板,并点击下载后,获取对应Excel模板。按照指定规则(平台默认规则或业务自定义规则)填写完成后,上传Excel文件进行异步导入操作。

Excel工作簿(ExcelWorkbookDefinition)

属性 类型 名称 必填 默认值 说明
name String 名称 Excel工作簿的定义名称
filename String 文件名 导出时使用的文件名,不指定则默认使用名称作为文件名
version Enum Office版本 AUTO OfficeVersionEnum
sheetList List<ExcelSheetDefinition>(JSONArray) 工作表定义列表 对多个Sheet进行定义

Excel工作表(ExcelSheetDefinition)

属性 类型 名称 必填 默认值 说明
name String 工作表名称 指定工作表名称,当未指定工作表名称时,默认使用模型的显示名称,未绑定模型生成时,默认使用「Sheet + ${index}」作为工作表名称
blockDefinitionList List<ExcelBlockDefinition> 区块列表 至少一个区块
mergeRangeList List<ExcelCellRangeDefinition> 单元格合并范围 工作表的合并范围相对于A1单元格行列索引,绝对定义
uniqueDefinitions List<ExcelUniqueDefinition> 唯一定义 全局模型唯一定义,当区块中未定义时,使用该定义

Excel区块(ExcelBlockDefinition)

属性 类型 名称 必填 默认值 说明
designRegion ExcelCellRangeDefinition 设计区域 设计区域是区块的唯一标识,不允许重叠。定义时应示意扩展方向,如下图所示。
analysisType Enum 解析类型 FIXED_HEADER ExcelAnalysisTypeEnum
usingCascadingStyle Boolean 是否使用层叠样式 false 将样式覆盖变为样式层叠;单个工作表有效;优先级顺序为:列样式 < 行样式 < 单元格样式三个属性共同定义一个Sheet的样式
headerList List<ExcelHeaderDefinition> 表头定义 【固定表头】类型下,至少一行,不显示的表头行使用配置行定义。
rowList List<ExcelRowDefinition> 行定义 【固定格式】类型下,至少一行。
bindingModel String 绑定模型 绑定任意模型,允许使用类的全限定名。
mergeRangeList List<ExcelCellRangeDefinition> 单元格合并范围 区块内的单元格合并范围相对与区块起始行列索引。当合并范围指定到最后一行的表头定义时,属于相对定义,即扩展时会自动填充合并样式。否则与工作表合并相同。
uniqueDefinitions List<ExcelUniqueDefinition> 唯一定义 默认使用模型定义的unique属性

Excel行(ExcelRowDefinition)

属性 类型 名称 必填 默认值 说明
cellList List<ExcelCellDefinition> 单元格列表
style ExcelStyleDefinition 样式 行样式

Excel表头(ExcelHeaderDefinition继承ExcelRowDefinition)

属性 类型 名称 必填 默认值 说明
cellList List<ExcelCellDefinition> 单元格列表
style ExcelStyleDefinition 样式 行样式
direction Enum 排列方向 HORIZONTAL ExcelDirectionEnum
selectRange ExcelCellRangeDefinition 应用范围 仅起始属性有效
isConfig Boolean 是否配置表头
autoSizeColumn Boolean 自动列宽 true 多行表头需使用配置表头进行定义
isFrozen Boolean 是否冻结 Excel冻结功能

Excel单元格范围(ExcelCellRangeDefinition)

属性 类型 名称 必填 默认值 说明
beginRowIndex Integer 起始行索引
endRowIndex Integer 结束行索引
beginColumnIndex Integer 起始列索引
endColumnIndex Integer 结束列索引
fixedBeginRowIndex Boolean 固定起始行索引
fixedEndRowIndex Boolean 固定结束行索引
fixedBeginColumnIndex Boolean 固定起始列索引
fixedEndColumnIndex Boolean 固定结束列索引

Excel单元格(ExcelCellDefinition)

属性 类型 名称 必填 默认值 说明
field String 属性 当且仅当单元格在表头中有效;多行表头需使用配置表头进行定义;多个配置表头定义属性时,仅首个属性定义生效;
value String
type Enum 值类型 STRING ExcelValueTypeEnum
formatContextString String(JSONObject) 格式化上下文 针对不同类型的值设置不同的参数内容(类型待定)
isStatic Boolean 是否是静态值 false 静态值在数据解析时将使用配置值,不读取单元格的值
style ExcelStyleDefinition 样式 单元格样式

Excel单元格样式(ExcelStyleDefinition)

属性 类型 名称 必填 默认值 说明
horizontalAlignment Enum 水平对齐 GENERAL ExcelHorizontalAlignmentEnum
verticalAlignment Enum 垂直对齐 ExcelVerticalAlignmentEnum
fillBorderStyle Enum 全边框样式 NONE ExcelBorderStyleEnum
topBorderStyle Enum 上边框样式 ExcelBorderStyleEnum
rightBorderStyle Enum 右边框样式 ExcelBorderStyleEnum
bottomBorderStyle Enum 下边框样式 ExcelBorderStyleEnum
leftBorderStyle Enum 左边框样式 ExcelBorderStyleEnum
wrapText Boolean 是否自动换行 true
typefaceDefinition ExcelTypefaceDefinition 字体
width Integer 首列样式中有效
height Integer 首列样式中有效

Excel字体(ExcelTypefaceDefinition)

属性 类型 名称 必填 默认值 说明
typeface Enum 字体 ExcelTypefaceEnum

注意事项

如果是在分布式模式下,启动工程中没有加入File模块,则需要手工调用ExcelTemplateInitHelper .init方法

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9325.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am8:13
下一篇 2024年5月23日

相关推荐

  • 4.1.25 框架之搜索引擎

    一、使用场景 在碰到大数据量并且需要全文检索的场景,我们在分布式架构中基本会架设ElasticSearch来作为一个常规解决方案。在oinone体系中增强模型就是应对这类场景,其背后也是整合了ElasticSearch。 二、整体介绍 oinone与es整合设计图 图4-1-25-1 Oinone与es整合设计图 基础环境安装 Canal安装 详见4.1.10【函数之触发与定时】一文 修改Canal配置并重启 新增Canal的实例【destinaion: pamirs】,监听分表模型的binlog【filter: demo.demo_core_sharding_model……】用于增量同步 pamirs: middleware: data-source: jdbc-url: jdbc:mysql://localhost:3306/canal_tsdb?useUnicode=true&characterEncoding=utf-8&verifyServerCertificate=false&useSSL=false&requireSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: oinone canal: ip: 127.0.0.1 port: 1111 metricsPort: 1112 zkClusters: – 127.0.0.1:2181 destinations: – destinaion: pamirschangedata name: pamirschangedata desc: pamirschangedata slaveId: 1235 filter: demo\.demo_core_pet_talent dbUserName: root dbPassword: oinone memoryStorageBufferSize: 65536 topic: CHANGE_DATA_EVENT_TOPIC dynamicTopic: false dbs: – { address: 127.0.0.1, port: 3306 } – destinaion: pamirs id: 1234 name: pamirs desc: pamirs slaveId: 1234 filter: demo\.demo_core_sharding_model_0,demo\.demo_core_sharding_model_1,demo\.demo_core_sharding_model_2,demo\.demo_core_sharding_model_3,demo\.demo_core_sharding_model_4,demo\.demo_core_sharding_model_5,demo\.demo_core_sharding_model_6,demo\.demo_core_sharding_model_7 dbUserName: root dbPassword: oinone memoryStorageBufferSize: 65536 topic: BINLOG_EVENT_TOPIC dynamicTopic: false dbs: – { address: 127.0.0.1, port: 3306 } tsdb: enable: true jdbcUrl: "jdbc:mysql://127.0.0.1:3306/canal_tsdb" userName: root password: oinone mq: rocketmq rocketmq: namesrv: 127.0.0.1:9876 retryTimesWhenSendFailed: 5 dubbo: application: name: canal-server version: 1.0.0 registry: address: zookeeper://127.0.0.1:2181 protocol: name: dubbo port: 20881 scan: base-packages: pro.shushi server: address: 0.0.0.0 port: 10010 sessionTimeout: 3600 图4-1-25-2 修改Canal配置并重启 ES安装 下载安装包官方下载地址,也可以直接下载elasticsearch-8.4.1-darwin-x86_64.tar.gz.txt(361.7 MB),下载后去除后缀.txt,然后解压文件 替换安装目录/config下的[elasticsearch.yml](elasticsearch)(1 KB),主要是文件中追加了三个配置 xpack.security.enabled: false xpack.security.http.ssl.enabled: false xpack.security.transport.ssl.enabled: false 图4-1-25-3 elasticsearc.yml追加三个配置 启动 a. 导入环境变量(ES运行时需要JDK18及以上版本JDK运行环境, ES安装包中包含了一个JDK18版本) # export JAVA_HOME=/Users/oinone/Documents/oinone/es/elasticsearch-8.4.1/jdk.app/Contents/Home/ export JAVA_HOME=ES解压安装目录/jdk.app/Contents/Home/ 图4-1-25-4 导入环境变量 b. 运行ES ## nohup /Users/oinone/Documents/oinone/es/elasticsearch-8.4.1/bin/elasticsearch >> $TMPDIR/elastic.log 2>&1 & nohup ES安装目录/bin/elasticsearch >> $TMPDIR/elastic.log 2>&1 & 图4-1-25-5 运行ES 停止ES lsof…

    2024年5月23日
    1.1K00
  • 4.1.5 模型之持久层配置

    一、批量操作 批量操作包括批量创建与批量更新。批量操作的提交类型系统默认值为batchCommit。 批量提交类型: useAffectRows,循环单次单条脚本提交,返回实际影响行数 useAndJudgeAffectRows,循环单次单条脚本提交,返回实际影响行数,若实际影响行数与输入不一致,抛出异常 collectionCommit,将多个单条更新脚本拼接成一个脚本提交,不能返回实际影响行数 batchCommit,使用单条更新脚本批量提交,不能返回实际影响行数。 全局配置 pamirs: mapper: batch: batchCommit 图4-1-5-1 全局配置 运行时配置 非乐观锁模型系统默认采用batchCommit提交更新操作;乐观锁模型默认采用useAndJudgeAffectRows提交更新操作。也可以使用以下方式在运行时改变批量提交方式。 Spider.getDefaultExtension(BatchApi.class).run(() -> { 更新逻辑 }, 批量提交类型枚举); 图4-1-5-2 运行时配置 运行时校正 如果模型配置了数据库自增主键,而批量新增的批量提交类型为batchCommit,则系统将批量提交类型变更为collectionCommit(如果使用batchCommit,则需要单条提交以获得正确的主键返回值,性能有所损失)。 如果模型配置了乐观锁,而批量更新的批量提交类型为collectionCommit或者batchCommit,则系统将批量提交类型变更为useAndJudgeAffectRows。也可以失效乐观锁,让系统不做批量提交类型变更处理。 二、乐观锁(举例) 在一些会碰到并发修改的数据,往往需要进行并发控制,一般数据库层面有两种一种是悲观锁、一种是乐观锁。oinone对乐观锁进行了良好支持 定义方式 乐观锁的两种定义方式: 通过快捷继承VersionModel,构建带有乐观锁,唯一编码code且主键为id的模型。 可以在字段上使用@Field.Version注解来标识该模型更新数据时使用乐观锁 如果更新的实际影响行数与入参数量不一致,则会抛出异常,错误码为10150024。如果是批量更新数据,为了返回准确的实际影响行数,批量更新由批量提交改为循环单条数据提交更新,性能有所损失。 失效乐观锁 一个模型在某些场景下需要使用乐观锁来更新数据,而另一些场景不需要使用乐观锁来更新数据,则可以使用以下方式在一些场景下失效乐观锁。更多元位指令用法详见4.1.9【函数之元位指令】一文。 PamirsSession.directive().disableOptimisticLocker(); try{ 更新逻辑 } finally { PamirsSession.directive().enableOptimisticLocker(); } 图4-1-5-3 失效乐观锁 不抛乐观锁异常 将批量提交类型设置为useAffectRows即可,这样可改由外层逻辑对返回的实际影响行数进行自主判断。 Spider.getDefaultExtension(BatchApi.class).run(() -> { 更新逻辑,返回实际影响行数 }, BatchCommitTypeEnum.useAffectRows); 图4-1-5-4 将批量提交类型设置为useAffectRows 构建第一个VersionModel Step1 新建PetItemInventroy模型,继承快捷模型VersionModel package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.common.VersionModel; import java.math.BigDecimal; @Model.model(PetItemInventroy.MODEL_MODEL) @Model(displayName = "宠物商品库存",summary="宠物商品库存",labelFields = {"itemName"}) public class PetItemInventroy extends VersionModel { public static final String MODEL_MODEL="demo.PetItemInventroy"; @Field(displayName = "商品名称") private String itemName; @Field(displayName = "库存数量") private BigDecimal quantity; } 图4-1-5-5 新建PetItemInventroy模型 Step2 修改DemoMenu,增加访问入口 @UxMenu("商品库存")@UxRoute(PetItemInventroy.MODEL_MODEL) class PetItemInventroyMenu{} 图4-1-5-6 修改DemoMenu Step3 重启看效果 体验一:页面上新增、修改数据库字段中的opt_version会自动加一 图4-1-5-7 示例效果一 图4-1-5-8 示例效果二 图4-1-5-9 示例效果三 图4-1-5-10 示例效果四 体验二:同时打两个页面,依次点击,您会发现一个改成功,一个没有改成功。但页面都没有报错,只是update返回影响行数一个为1,另一个为0而已。 图4-1-5-11 编辑宠物商品库存 图4-1-5-12 宠物商品库存列表 注:增加了乐观锁,我们在写代码的时候一定要注意,单记录更新操作的时候要去判断返回结果(影响行数),不然没改成功,程序是不会抛错的。不像batch接口默认会报错 Step4 预留任务:重写PetItemInventroy的update函数 留个任务,请各位小伙伴自行测试玩玩,这样会更有体感 package pro.shushi.pamirs.demo.core.action; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.enumeration.DemoExpEnumerate; import pro.shushi.pamirs.demo.api.model.PetItemInventroy; import pro.shushi.pamirs.demo.api.model.PetTalent; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; import java.util.ArrayList; import java.util.List; @Model.model(PetItemInventroy.MODEL_MODEL) @Component public class PetItemInventroyAction { @Function.Advanced(type= FunctionTypeEnum.UPDATE) @Function.fun(FunctionConstants.update) @Function(openLevel = {FunctionOpenEnum.API}) public PetItemInventroy update(PetItemInventroy data){ List<PetItemInventroy> inventroys = new ArrayList<>(); inventroys.add(data); //批量更新会,自动抛错 int i =…

    2024年5月23日
    87000
  • 特别说明

    教材进度 目前还在编写整改中,特别是目录上带(改)字样的 本版本教材针对4.0,相对3.0增加的特性有: 通用能力部分:比如权限、审计、国际化、系统配置、集成可视化等 新版权限:交互优化,同时支持子管理员 新版审计,支持在线配置审计规则 系统配置,提供多种风格,让伙伴基础风格不在需要自定义主题和组件 新增集成设计器,通过连接器让应用api和数据库集成资产化管理,通过数据流进行接口逻辑编排,并提供统一的开放平台 设计器部分: 加强界面设计器部分对移动端、主题、自定义组件等支持,同时完善对数据管理操作、增加各类常用业务组件,加强对树形与及联的支持,表格视图搜索优化 流程设计器增加:审批员选择自定义逻辑、转交人选择自定义逻辑,支持手工触发、支持数据事务、支持调用模型函数 可视化设计器:增加自定义图表功能、增强下钻能力 模块管理:增加新增、多端配置等支持 内核机制: 性能优化:通过gql层面解决业务研发过程中1+N性能问题,权限性能优化,分布式缓存性能优化,整体性能提升2倍以上 针对多租户应用无状态的支持,低无一体支持本地调试 5.0的核心新特性预告,特性会在开发过程中陆续推出供大家体验,但完整体验时间为2024年6月份 新增打印设计功能 导出的默认交互增强以及导入导出模版设计 默认导入导出在交互时可以指定字段 导入导出模版可图形化设计 多模型AI集成设计器器 自定义组件、自定义图表交互友好性增强 自定义组件,属性面板设计模版化,解决设计组件属性面板的时候不知道有哪些默认属性。 增强组件与动作的结合灵活性 组件增加是否有子组件的通用属性,有子组件的组件具备组件拖拽区 增强UX注解能力,原本只有设计器支持的,后端UX注解也进行对应支持 后端低无一体功能开放(原本该功能只在saas版本上提供),方便咱们做应急和简单应用的时,以无代码为主,低代码为辅的模式。通过低无一体可以通过低代码的方式来解决无代码无法支撑的功能。 工作流增强 增加循环审批节点 增加分支汇集节点增强并行流程能力,比如分支汇集节点可以设置:所有并行节点处理完或只要一条处理完就继续执行。

    2024年5月23日
    1.5K00
  • 4.1.13 Action之校验

    在3.5.3【Action的类型】一文中有涉及到“ServerAction之校验”部分,本文介绍一个特殊的写法,当内置函数和表达式不够用的时候,怎么扩展。还是拿PetShopProxyAction举例,修改如下: package pro.shushi.pamirs.demo.core.action; ……引依赖类 @Model.model(PetShopProxy.MODEL_MODEL) @Component public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> { ……其他代码 // @Validation(ruleWithTips = { // @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"), // @Validation.Rule(value = "LEN(data.shopName) < 128", error = "名称过长,不能超过128位"), // }) @Validation(check = "checkName") @Action(displayName = "启用") @Action.Advanced(rule="activeRecord.code !== undefined && !IS_BLANK(activeRecord.code)") public PetShopProxy dataStatusEnable(PetShopProxy data){ data = super.dataStatusEnable(data); data.updateById(); return data; } @Function public Boolean checkName(PetShopProxy data) { String field = "name"; String name = data.getShopName(); boolean success = true; if (StringUtils.isBlank(name)) { PamirsSession.getMessageHub() .msg(Message.init() .setLevel(InformationLevelEnum.ERROR) .setField(field) .setMessage("名称为必填项")); success = false; } if (name.length() > 128) { PamirsSession.getMessageHub() .msg(Message.init() .setLevel(InformationLevelEnum.ERROR) .setField(field) .setMessage("名称过长,不能超过128位")); success = false; } return success; } ……其他代码 } 图4-1-13-1 PetShopProxyAction扩展配置 注: check属性指定了校验函数名称,命名空间必须与服务器动作一致。 校验函数的入参必须与服务器动作一致 使用PamirsSession#getMessageHub方法可通知前端错误的属性及需要展示的提示信息,允许多个。

  • 3.4.3 函数的相关特性

    本小章会从oinone的函数拥有三方面特性,展开介绍 面向对象,继承与多态 面向切面编程,拦截器 SPI机制,扩展点

Leave a Reply

登录后才能评论