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低代码应用平台体验

Like (0)
史, 昂's avatar史, 昂数式管理员
Previous 2024年5月23日 am8:13
Next 2024年5月23日

相关推荐

  • 陈浩

    自2017年中国推进数字建设以来,数字经济规模持续增长,“十四五”规划和2035远景目标纲要中明确强调企业和政府需大力推动数字化转型,中国正在迈进一个崭新的数字经济时代。 在这个过程中,软件已经从工具变成信息化的基础设施,如何有效应对该变化所带来的一系列新的核心技术挑战,是整个软件行业发展遇到的另一难题。我认为,开源创新是解决这些难题的有效手段之一,也是未来软件发展的重要方向。如果说,数字化转型是时代趋势,那么开源创新也已成为时代主流。十四五规划纲要首提开源,2021年11月工信部印发《十四五软件和信息技术服务业发展规划》中提到开源重塑软件发展新生态,并将开源重塑软件发展新生态作为十四五期间我国软件产业的四大发展形势之一进行重点阐述。支持国产化开源创新体系发展,建设自己的开源社区和开源平台,其所具有的大众协同、开放共享、持续创新等特点,可有效推动各行业自主可控的数字化转型。 Oinone所倡导的开源理念和生态共建,与国家开源战略不谋而合:将开源作为一种合作手段,通过完善社区注重开源治理,吸引更多的企业和个体参与其中。湖南大学作为首批国家示范性软件学院的双一流建设高校,一直致力于推进和引导国产化开源软件体系的建设,并为此开展多种形式的产学研研究和实践。基于Oinone微服务分布式的设计理念和面向生态的开源特性,湖南大学结合自身在大数据分布式存储、多元异构数据汇聚融合和大数据智能分析等方面的研究成果,与Oinone展开了深度的技术创新合作,并在多个大中型企业数字化应用和数字政府应用中取得了良好的效果。 随着Oinone的开源,相信能激发更多的开发者参与到国产软件建设中,通过开源模式实现更广泛参与方的共享、共创、共生、共赢,构建价值驱动的数字创新生态平台,为我国数字经济发展贡献科技力量。 湖南大学教授:陈浩

    Oinone 7天入门到精通 2024年5月23日
    2.3K00
  • 3.4 Oinone以函数为内在

    函数(Function):是oinone可管理的执行逻辑单元,跟模型绑定则对应模型的方法 描述满足数学领域函数定义,含有三个要素:定义域A、值域C{f(x),x属于A}和对应法则f。其中核心是对应法则f,它是函数关系的本质特征 满足面向对象原则,可设置不同开放级别,本地与远程智能切换。 本章会带大家更加详细地了解Function的方方面面,主要以几下几个维度 构建第一个Function 函数的开放级别与类型 函数的相关特性 函数元数据详解

    Oinone 7天入门到精通 2024年5月23日
    1.7K00
  • 5.1 CDM的背景介绍

    如果说低代码开发框架输出技术标准,CDM则是结合oinone技术特性和软件工程设计,让输出数据标准变成可能。 一、背景介绍 无法照搬的最佳实践 要了解引入CDM的初衷,得从互联网架构的演进开始,了解其过程,就知道为什么说Oinone的CDM是中台架构的最佳技术实践的核心!我们在2.2【互联架构做为最佳实践为何失效】一文中介绍过互联网技术发展的四个阶段,特别平台化到中台化的阶段,目的是在一套规范下让听的见炮火声音的团队自行决定业务系统发展,适用多业务线(或多场景应用)独立发展。 互联网架构在演进过程中碰到的问题跟企业数字化转型过程中碰到的问题是非常类似: 随着企业业务在线化后对系统性能、稳定都提出了更高的要求,而且大部分企业的内部很多系统相互割裂导致,导致很多重复建设,所以我们需要服务化、平台化。 同时没有一个供应商能解决企业所有商业场景问题,又需要多个供应商共同参与,所以把供应商类类比成各个业务线,在一套规范下让供应商自行决定业务系统发展 既然跟阿里当初在架构演进过程中碰到的问题非常类似,那么是不是照搬阿里中台架构方案到企业就好了?当然不是,因为历史原因阿里的中台架构是采用的平台共建模式:“让业务线研发以平台设计好的规范进来共同开发”,其本质还是平台主导模式,它是有非常大的历史包袱。我们想象各个供应商的共建一个交易平台或商品平台,那是多么荒唐的事情,平台化已经足够的复杂了,还让不同背景、不同企业的研发一起共建,最后往往导致企业架构负载过重,这时对企业来说便不再是赋能而是“内耗”。 那么如果没有历史包袱,我们重新设计,站在上帝视角去看有没有更好的方式呢?当然有 借鉴微软的CDM 这里我们借鉴微软的CDM理念,CDM这个概念最早是2016年微软宣布“以Dynamics 365的形式改造其CRM和ERP”战略时提出的。微软给它的定义是“用于存储和管理业务实体的业务数据库,而且是开箱即用的”。CDM不仅仅提供标准实体,它还允许用户建立个性化的实体,用户可以扩展标准实体也可以增加和标准实体相关的新实体。 CDM可能并不性感,但绝对是非常必要的。它成为了微软的很多产品的基础,是构建了无数业务领域的原型。同时微软也期望它能成为快速实现数据交换和迁移的标准,这个有点像菜鸟网络推出的奇门,让所有TMS、OMS、WMS都基于一套数据接口API进行互通,一套标准是为了解决一个行业问题,而不是具体某一个企业一个集团的问题。 我们发现CDM的理念跟我们想要的“企业级的数据标准”是非常吻合的。但是我们也不能照搬照抄,虽然微软的CDM很好的解决了数据割裂问题,但就模型来说就够大家喝一壶了,模型库非常庞大而且复杂,学习成本巨高。 数字化时代软件会产生新的技术流派 我们知道传统软件的设计理念:侧重在模型对业务支撑全面性上。优点体现为配置丰富,缺点模型设计过于复杂,刚开始有前瞻性,但在理解、维护都非常困难,随着业务发展系统原先的设计逐渐腐化,异常笨重。 而Oinone的CDM设计理念:侧重在简单、灵活、统一上,体现为在上层应用开发时,每一业务领域保持独立,模型简单易懂,并结合Oinone的低代码开发机制进行快速开发,灵活应对业务变化。 所以我更想说Oinone的CDM是微软CDM的在原有基础上,与互联网架构结合,利用Oinone低代码开发平台特性形成新的工程化建议。Oinone-CDM不以把模型抽象到极致,支撑“所有业务可能性”为目标,而是抽象80%通用的设计,保持模型简单可复用,来解决数据割裂问题,并保持业务线独立自主性,快速创新的能力。 图5-1-1 Oinone-CDM要解决的问题 二、Oinone的CDM本质是创新的工程化建议 引入CDM以后系统工程结构会有什么变化,跟大家认知的互联网架构有什么区别。 原本上层的业务线系统,需要调用各个业务平台提供的功能,增加CDM以后也就是我们右的图,每个业务线就像一个独立右边。看上去复杂了,其实对业务线来说更加简单了。 互联网整体平台化带来的问题: 业务线每次业务调整都需要给各个平台提需求 业务平台研发需要了解所有业务线的知识再做设计,对研发要求非常高 各个业务域的不同需求相互影响包括系统稳定性、研发对需求响应的及时性 结合oinone特性提出的新工程建议: 一些通用性模块继续以平台化的方式存在,能力完全复用。 业务线自建业务平台,保持业务线的独立性和敏捷性 业务线以CDM为原型,保证核心数据不割裂,形成一致的数据规范 图5-1-2 引入CDM概念后的工程结构对比 三、CDM思路示意图 该示例中OinoneCDM的商品域不仅仅提供标准实体,保证各个业务系统的对商品的通用需求、简单易懂,在我们星空系列业务产品中如全渠道运营、B2B交易等系统以此为基础建立属于自身个性化的实体,可以扩展标准实体也可以增加和标准实体相关的新实体。 带来的好处: 通过多种继承方式,继承后的模型可扩展模型本身、模型行为等,从而解决业务独立性问题。 通过CDM层统一数据模型,从而解决多应用数据割裂问题 图5-1-3 Oinone-CDM思路示意图

    2024年5月23日
    1.3K00
  • 6.6 消息

    在我们系统研发过程中经常需要发送短信、邮件、站内信等,笔者在本文给大家介绍下如何使用Oinone的消息模块。 准备工作 如果通过我们工程脚手架工具生成的则已经引入了无需做更多的配置,如果不是则需要按以下步骤先配置依赖和增加启动模块 pamirs-demo-boot的pom文件中引入pamirs-message-core包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-message-core</artifactId> </dependency> pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加message,即在启动应用中增加message模块 pamirs: boot: modules: – message 消息参数设置 发送邮件和短信需要设置对应的发送邮箱服务器和短信云,短信目前默认阿里云短信。我们通过代码示例来完成对应邮箱和短信的参数设置 Step1 增加pamirs-message-api依赖 pamirs-demo-core的pom文件中引入pamirs-message-api包依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-message-api</artifactId> </dependency> Step2 消息参数设置 请自行替换邮箱服务器和短信通道的账号信息 package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.api.init.InstallDataInit; import pro.shushi.pamirs.boot.common.api.init.UpgradeDataInit; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.message.enmu.EmailSendSecurityEnum; import pro.shushi.pamirs.message.enmu.MessageEngineTypeEnum; import pro.shushi.pamirs.message.enmu.SMSChannelEnum; import pro.shushi.pamirs.message.model.EmailSenderSource; import pro.shushi.pamirs.message.model.SmsChannelConfig; import java.util.Collections; import java.util.List; @Component public class DemoMessageInit implements InstallDataInit, UpgradeDataInit { private void initEmail(){ EmailSenderSource emailSenderSource = new EmailSenderSource(); emailSenderSource.setName("邮件发送服务"); emailSenderSource.setType(MessageEngineTypeEnum.EMAIL_SEND); //优先级 emailSenderSource.setSequence(10); //发送账号 FIXME 自行替换 emailSenderSource.setSmtpUser(""); //发送密码 FIXME 自行替换 emailSenderSource.setSmtpPassword(""); //发送服务器地址和端口 emailSenderSource.setSmtpHost("smtp.exmail.qq.com"); emailSenderSource.setSmtpPort(465); //" None: SMTP 对话用明文完成。" + //" TLS (STARTTLS): SMTP对话的开始时要求TLS 加密 (建议)" + //" SSL/TLS: SMTP对话通过专用端口用 SSL/TLS 加密 (默认是: 465)") emailSenderSource.setSmtpSecurity(EmailSendSecurityEnum.SSL); emailSenderSource.createOrUpdate(); } private void initSms(){ SmsChannelConfig smsChannelConfig = new SmsChannelConfig(); smsChannelConfig.setType(MessageEngineTypeEnum.SMS_SEND); smsChannelConfig.setChannel(SMSChannelEnum.ALIYUN); //短信签名名称 smsChannelConfig.setSignName("oinone"); //阿里云账号信息 FIXME 自行替换 smsChannelConfig.setAccessKeyId(""); smsChannelConfig.setAccessKeySecret(""); smsChannelConfig.setEndpoint("https://dysmsapi.aliyuncs.com"); smsChannelConfig.setRegionId("cn-hangzhou"); smsChannelConfig.setTimeZone("GMT"); smsChannelConfig.setSignatureMethod("HMAC-SHA1"); smsChannelConfig.setSignatureVersion("1.0"); smsChannelConfig.setVersion("2017-05-25"); smsChannelConfig.createOrUpdate(); //初始化短信模版 //目前支持阿里云短信通道:获取短信模板,如没有短信模板,需要先创建模板,并审核通过 SmsTemplate smsTemplate = new SmsTemplate(); smsTemplate.setName("通知短信"); smsTemplate.setTemplateType(SMSTemplateTypeEnum.NOTIFY); smsTemplate.setTemplateCode("SMS_244595482");//从阿里云获取,自行提供 FIXME smsTemplate.setTemplateContent("尊敬的&{name},你的&{itemName}库存为&{quantity}"); smsTemplate.setChannel(SMSChannelEnum.ALIYUN); smsTemplate.setStatus(SMSTemplateStatusEnum.SUCCESS); smsTemplate.createOrUpdate(); } @Override public boolean init(AppLifecycleCommand command, String version) { initEmail(); initSms(); return Boolean.TRUE; } @Override public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) { initEmail(); initSms(); return Boolean.TRUE; } @Override public List<String> modules() { return Collections.singletonList(DemoModule.MODULE_MODULE);…

    2024年5月23日
    2.2K00

Leave a Reply

Please Login to Comment