E10 开发培训

发布于 2026-08-05

E10 开发培训

学习资料获取

可访问我的个人网站获取泛微开发的学习知识,地址:yaolilin.com

访问我的开源主页,可查看泛微的开发知识,代码工具,和泛微可复用应用:

泛微官方学习资料:E10开发指引

前端

E10 前端架构

框架:React 17 组件:泛微自研组件

E-Code 平台介绍

E-code 平台的功能和 E9 的 E-Code 平台差不多,它是一个在线的前端代码编辑器,可编写 React 组件以及注册事件,可实现修改标准页面的组件和数据,以及创建自定义页面、进行接口拦截等。

E-Code 页面进入链接: /ecode

进入页面后是一个项目列表,你可以在这里创建项目

进入项目后就可以在此页面创建 E-Code 应用,发布该应用后就可以让该应用生效。

组件开发

实现效果:在前端页面显示创建的组件

创建 E-Code 应用

创建应用选择 React 类型,创建后应用下会自动创建 entry.js 文件,相当于 E9 的 register.js 文件,它是一个前置加载文件,意味着它会在页面加载前执行,可以在此文件上注册组件参数修改、组件替换、接口拦截、新页面注册等。

创建组件

创建一个父组件和一个子组件,可创建 components 文件夹,在里面存放组件。

组件的编写与常规 React 应用的编写一致,这点与 E9 不同,E10 的组件导入和导出也是用标准的 JS 模块导入导出方法。

**子组件 childCom.js** 使用 `export default` 进行组件导出,这样可以在本应用内获取到本组件

export default function (){
  return <div>子组件</div>
}

**父组件 index.js** 使用 `import` 导入子组件,与 React 写法相同

import ChildCom from './childCom';
    
export default function(){
  return <div>
    父组件
    <ChildCom />
  </div>
}

将组件添加到前端页面

将创建的组件渲染到前端

entry.js 是一个前置加载文件,会在页面加载前执行,可以在 entry.js 内获刚才创建的组件,然后渲染到页面指定元素。

要在 entry.js 文件获取创建的组件,必需使用异步方式获取组件,也就是使用 `asyncImport` 进行异步导入。

const MyCom = lazy(() => asyncImport('${appId}', 'component/index'));

异步获取的组件必需放在 `Suspense` 组件中,此组件的作用是等待子组件加载完成,加载完成后显示子组件,在未加载完成时显示 `fallback` 中的组件。

<Suspense fallback={<span>waiting</span>}>
      <div style={style}>
        <MyCom />
      </div>
    </Suspense>

最后使用 `render(<App />, root);` 将组件渲染到指定页面元素。

entry.js

import {render} from "react-dom";
import { useState, Suspense, lazy } from 'react';
import { asyncImport } from '@weapp/ecodesdk';
    
// 创建 App 挂载元素
const root = document.createElement('div');
document.body.appendChild(root);
    
// 样式处理,也可以新建 css 文件
const style = {
  position: 'absolute',
  zIndex: 9999,
  top: 100,
  left: '50%',
  transform: 'translate(-50%, 0)',
  fontSize: 60,
  fontWeight: 600,
  backgroundColor: 'rgba(255, 255, 255, 0.8)',
  boxShadow: '0 0 20px rgba(0, 0, 0, 0.5)'
};
    
const MyCom = lazy(() => asyncImport('${appId}', 'component/index'));
    
function App () {
  return (
    <Suspense fallback={<span>waiting</span>}>
      <div style={style}>
        <MyCom />
      </div>
    </Suspense>
    
  );
}
    
render(<App />, root);

E10 组件库介绍

E10 的前端页面大多数都由组件库中的组件构成。 E10 组件库地址:/ui

在 E-Code 中也可以看到组件库

组件参数复写

**应用场景:**

  • 适合只通过修改组件参数就能完成对组件修改的场景
  • 只是通过组件参数复写作为执行入口,判断当前页面是否已经加载

通过修改标准页面上的组件参数,就可以达到修改组件数据的效果。

示例:通过修改 `Title` 组件参数,在流程表单添加按钮

先创建自定义按钮

import {Button} from '@weapp/ui';


const CustomButton = () => {
    return <Button>自定义按钮</Button>
}

export default CustomButton;

然后在 `entry.js` 文件注册组件参数修改事件,使用 `regOvProps` 进行注册,参数说明:

  • 所属组件库:pc 端组件的组件库一般为 weappUi
  • 组件名称:需要进行参数修改的组件名称
  • 参数修改函数:可在此函数对参数进行修改

通过异步方式导入 `CustomButton` 组件,然后将此自定义按钮组件添加到 `Title` 组件的 `buttons` 参数中。

注意:进行组件参数修改时需要先判断页面地址,和根据组件的标识(weId)判断是否为对应组件,一般可通过该标识的最后几个字母进行判断,如:_6xdqsn 如何获取 weId:在组件参数中获取,通过 React 浏览器插件就能方便的查看组件参数,看到 weId

import {asyncImport} from '@weapp/ecodesdk';
import {regOvProps} from '@weapp/utils';
import {lazy, Suspense} from 'react';

const CustomButton = lazy(() => asyncImport('${appId}', 'components/CustomButton'));

/**
 * 在流程中添加自定义按钮
 */
regOvProps('weappUi','Title',(props)=>{
    try {
        if(props.weId.endsWith('_6xdqsn') && location.href.includes('/sp/workflow/flowpage/view')){
            const {buttons} = props;
            if(buttons && buttons.length > 0){
                buttons.splice(buttons.length - 1, 0,
                    <Suspense fallback={<span></span>}>
                        <CustomButton/>
                    </Suspense>
                )
            }
        }
    } catch (e) {
        console.error('添加自定义按钮出现异常', e);
    }
    return props;
},1);

示例应用下载:

组件替换

**应用场景:** 需要在原组件添加其它组件,实现更复杂的修改

示例:通过替换 `Menu` 组件,在门户添加一个菜单

**创建自定义组件**

此组件将会作为替换的 Menu 组件

组件接受两个参数:

  • props:原组件参数
  • ref:组件引用
    
import { Menu } from '@weapp/ui';
    
const newMenu =(props,ref)=>{
  const { OriginCom } = props;
  // 添加新菜单
  const data = props.data;
  data.push({ id: 'custom-tab', content: '新添加的菜单' })
    
  // 可以使用别的组件,也可以用 <OriginCom /> 使用原组件
  // 使用原来的组件参数
  return(
    <Menu {...props} data={data} noOverwrite={true} ref={ref}/>
  )
}
    
export default newMenu;
    

**注册组件替换事件**

在 `entry.js` 文件中使用 `regOvComponent` 注册组件重写事件

⚠️ 注意:一定要在组件加上 _noOverwrite 标识,标记组件已被替换,并在组件替换时判断该标识,防止出现无限循环导致页面卡死

import React from 'react';
import { regOvComponent } from '@weapp/utils';
import { asyncImport } from '@weapp/ecodesdk';

const NewMenu = React.lazy(() => asyncImport('${appId}', 'NewMenu'));

regOvComponent('weappUi', 'Menu', (Com) => {
  return React.forwardRef((props, ref) => {
    // 需要根据 weId 去判断是否是要替换的组件,根据 noOverwrite 判断是否可重写,否则会无限循环造成卡死
    if (location.href.includes('/portal/view') && props?.weId?.endsWith('_39u13z') 
          && !props._noOverwrite) {
      return (
        <React.Suspense fallback={() => { }}>
          <NewMenu ref={ref} {...props} OriginCom={Com} />
        </React.Suspense>
      )
    }
    // 如果不是要替换的组件需要返回原组件
    return <Com ref={ref} {...props} />
    });
}, 1)

E-code 示例应用下载:

如何知道页面是使用哪个组件

方法一:通过样式名判断

通过浏览器的开发者工具,查看该组件的样式名, 比如下面可知道这个组件为 menu 组件。 此方法的缺点是不太直观

方法二:使用 React 浏览器插件(推荐)

该插件可通过插件商店下载,建议所有开发人员都使用此插件。 通过此插件可以很直观的看到组件名称和它所属的组件库,还有组件的参数。

接口拦截

**应用场景:** 通过拦截接口的返回数据,达到修改页面数据的目的

说明文档:e-code二开请求和拦截说明文档

**所有接口拦截,都需要写在 entry.js 文件上**

使用官方工具进行拦截(首选)

接口返回拦截

使用 `devRequest` 对接口进行拦截 注意:此方法不一定有效,只允许拦截部分接口,如果不成功就换其它方法

// 请求工具方法
import { devRequest } from '@weapp/ecodesdk';

// 使用方式同 request, 参数保持一致
// get 调用
devRequest({ url: '/papi/secondev/health' }).then(res => {
  if (res.code === 200) {
    Dialog.message({ type: 'info', content: `请求成功:${dayjs().unix()}` })
  } else {
    Dialog.message({ type: 'info', content: `请求失败:${dayjs().unix()}` })
  }
}).catch(err => {
  Dialog.message({ type: 'info', content: `请求异常:${dayjs().unix()}` })
  console.error(`请求异常:${dayjs().unix()}`, err)
})

// post 调用
devRequest({ url: '/papi/secondev/health', method: 'post' }).then(res => {
  if (res.code === 200) {
    Dialog.message({ type: 'info', content: `请求成功:${dayjs().unix()}` })
  } else {
    Dialog.message({ type: 'info', content: `请求失败:${dayjs().unix()}` })
  }
}).catch(err => {
  Dialog.message({ type: 'info', content: `请求异常:${dayjs().unix()}` })
  console.error(`请求异常:${dayjs().unix()}`, err)
})

使用 axios 进行拦截

`axios` 是一个 js 网络请求库,它是通用的,不受泛微的限制,所以它可以拦截所有接口的请求。

请求拦截

在接口请求时添加请求参数

示例:拦截 `api/hrm/employee/getTopMenu` 接口,添加请求参数

import axios from 'axios';

// 前置拦截,获取请求数据
axios.interceptors.request.use(
  config =>{
    // config 包含所有请求信息
    const {url,params} = config;
    if(url.includes('api/hrm/employee/getTopMenu')){
      // 添加参数
      params.isTest = 1;
    }
    return config;
  },
  error =>{
    // 对请求错误做些什么
    return Promise.reject(error);
  }
)
响应拦截

示例:拦截 `api/hrm/employee/getTopMenu` 接口,添加按钮数据

import axios from 'axios';

// 后置拦截,获取接口返回数据
axios.interceptors.response.use(
  response =>{
    // response 里包含了响应信息,以及 config,使用 config 可以获取请求信息,data可以获取响应数据
    const {data,config:{url,params}} = response;
    if(url.includes('api/hrm/employee/getTopMenu')){
      // 能够获取到上面请求拦截中添加的参数
     const {isTest} = params;
      // 修改返回数据,添加一个按钮
      if(data.data){
        data.data.push({
          content:'新增按钮',
          id:'devBt'
        });
      }
    }
    return response;
  },
  error =>{
    // 对请求错误做些什么
    return Promise.reject(error);
  }
)

效果: 添加了一个按钮

示例下载

ecode 应用

配置文件

在开发中我们常遇到,在哪条流程生效,在哪个节点生效,那这些就需要配到配置文件里面。

配置文件会在 E-Code 应用自动创建,路径为:`config/config.js`

配置文件编写示例:

const config = {
  workflowId:[1243,34356]
}
    
export default config;

获取配置文件:

使用`import`导入配置文件,注意 from 后面的路径,是相对于当前文件的路径,当前文件路径是根目录,所以使用 ./ 表示当前路径。

配置文件是前置加载的,在 entry.js 文件无需异步导入。

注意:配置文件导入时 config 不要加括号,不然会获取不到配置文件

import config from './config/config'
// 使用配置文件
config.width;

配置文件使用示例

配置文件内容:

/**
 * 下载PDF按钮相关配置
 */
const config = {
  // 需要下载的文件字段名称,可多个,只要表单中有这些字段(需放在表单),则显示下载PDF按钮
  fileFieldNames:['fj_fltf','fj_4ch3']
}

export default config;

在 entry.js 文件进行组件参数修改,实现在流程表单中添加按钮,根据配置文件,判断当前流程是否包含指定字段,如果有才添加按钮

import {asyncImport} from '@weapp/ecodesdk';
import {regOvProps} from '@weapp/utils';
import {lazy, Suspense} from 'react';
import config from './config/config';

function enable(){
    try{
        if(!config) return false;
        const fileFieldNames = config.fileFieldNames;
        if(!fileFieldNames || fileFieldNames.length === 0) return false;
        const formSdk = window.WeFormSDK?.getWeFormInstance();
        if(!formSdk) return false;
        // 验证配置文件中的字段名能否成功转换为字段ID
        return fileFieldNames.some(name => {
            const id = formSdk.convertFieldNameToId(name);
            return typeof id === 'string' && id.length > 0;
        });
    }catch(e){
        console.warn('enable() 出现异常',e);
   }
}

/**
 * 在流程中添加下载PDF按钮
 */
regOvProps('weappUi','Title',(props)=>{
    try {
        if(props.weId.endsWith('_6xdqsn') && location.href.includes('/sp/workflow/flowpage/view')
            && enable()){
            const {buttons} = props;
            if(buttons && buttons.length > 0 && formData){
                buttons.splice(buttons.length - 1, 0,
                    <Suspense fallback={<span></span>}>
                        <DownloadButton formData={formData}/>
                    </Suspense>
                )
            }
        }
    } catch (e) {
        console.error('注入下载按钮出现异常', e);
    }
    return props;
},1);

新页面开发

**场景:** 通过开发创建新页面,比如纯开发的门户,报表页面

创建新页面中的组件

const NewPage = ()=>{
  return <div>新页面</div>
}

export default NewPage;

注册新页面

在 `entry.js` 文件使用 `regOvProps` 注册新页面

**创建路由组件** 需要使用 `Route` 组件来创建路由组件,在组件内放新页面的组件(这里为 NewPage 组件)。 path 为新页面的地址,地址一般为:/sp/custom/{appId}/{页面组件名称} 。 通过 `publicUrl` 获取地址前缀,不过一般都是为空的。

const { publicUrl } = appInfo('@weapp/ecodesdk');

// 创建路由组件
const newPage = (
  // publicUrl可能为空,前缀必需包含 /sp/custom/
  <Route path={`${publicUrl}/sp/custom/` + '${appId}/newPage'}>
    <React.Suspense fallback= {() => { }}>
      <NewPage />
    </React.Suspense>
  </Route>
);

**注册路由** 下面除了 `newPage` , 其它都为固定写法

// 注册路由
regOvProps('weappEcodesdk', 'Switch', (props) => {
  regReactChildren(props, newPage);
  return props;
}, 0);

**完整示例**

import React from 'react';
import { Route } from 'react-router-dom';
import { regOvProps, appInfo, regReactChildren } from '@weapp/utils';
import { asyncImport, jsonp } from '@weapp/ecodesdk';

const NewPage = React.lazy(() => asyncImport('${appId}', 'newPage'));

const { publicUrl } = appInfo('@weapp/ecodesdk');

// 页面地址:/sp/custom/993285932735250435/newPage
// 创建路由组件
const newPage = (
  // publicUrl可能为空,前缀必需包含 /sp/custom/
  <Route path={`${publicUrl}/sp/custom/` + '${appId}/newPage'}>
    <React.Suspense fallback= {() => { }}>
      <NewPage />
    </React.Suspense>
  </Route>
);

// 注册路由
regOvProps('weappEcodesdk', 'Switch', (props) => {
  regReactChildren(props, newPage);
  return props;
}, 0);

实现效果:

示例应用:

加载第三方 js 库

e10平台定制开发指引

表单 JS 开发

表单 SDK

E10 的流程表单 SDK 与 E9 的表单 Js API 使用方法差不多。 入口:在布局页面,点击此图标

在流程中使用 E-Code 应用

在流程高级设置中可以添加 E-Code 应用,可以选择创建新的应用或者关联已有应用,不过要注意,如果选择创建新的应用,则这个应用只能用在这一条流程上,你可以到 E-Code 页面先创建应用,然后再关联应用,这样就可以在所有流程都可以用这个 E-Code 应用。

流程的 E-Code 应用放在此项目下,必需勾选系统项目才能显示此项目。流程只能关联此项目中的 E-Code 应用,你可以在此项目下创建流程的 E-Code 应用。

当进入表单后,会自动执行关联的 E-Code 应用下的非前置加载文件,因此在应用下的 js 文件中正常写代码即可,例如写在应用下的 index.js 文件

前端开发规范

官方文档说明:二次开发规范说明文档

组件必需通过样式名添加二开标记

任何通过二开在页面上添加元素的组件(比如弹窗、按钮、表格等),都必需在组件的样式名上注明二开标记,如:“ecode 应用id,代码入口(是ecode还是 表单js)”,方便定位二开代码编写位置。

这个源自之前的经历,我经常碰到这个按钮不知道是在哪里开发添加的,找的很痛苦

示例:

  • appid-${appId} 为ecode 应用id
  • seconddev-ecode 表示这是在 ecode 二开添加的
<Button onClick={handleClick} className={'appid-${appId} seconddev-ecode'} >下载为PDF</Button>

每个 E-Code 应用必需添加说明文档

在每个 E-Code 应用添加 README.md 文件,说明作者信息,开发的内容,如何配置等,方便了解这个应用的功能是什么,方便后续维护。

文档不齐全,维护两行泪

示例:

# EB列表单元格合并开发说明
作者:泛微柳州 姚礼林  
日期:2026-02-26

## 实现功能
e-builder 表格可合并重复数据的单元格,如果表格中的字段存在连续重复,则进行合并。 
- 可配置合并依据字段,如果该字段存在连续重复,则合并这些单元格
- 可配置其它需要合并的字段,在合并依据字段合并时,如果这些字段的值也相同,则进行合并
- 可对每个 e-builder 表格配置合并

合并前:

![](/api/docs-static/%E6%B3%9B%E5%BE%AE%E5%BC%80%E5%8F%91/E10/%E5%9F%B9%E8%AE%AD/%24%7BappMdRes%7D/1772070902-image.png)
合并后:

![](/api/docs-static/%E6%B3%9B%E5%BE%AE%E5%BC%80%E5%8F%91/E10/%E5%9F%B9%E8%AE%AD/%24%7BappMdRes%7D/1772070927-image.png)

## 配置说明
需修改 `config/config.js` 文件,需配置需要合并的eb表格,在配置中的表格才进行合并。  
配置示例:

const config = { viewConfigs :[ { // 列表id,通过链接地址获取,例如链接包含: // /sp/ebdapp/build/1211771683381723136/search/1211771941323030537-7516533764297595633? // 则取 1211771941323030537 id:'1240398310603153522', // 主键字段名,必填,按此字段合并单元格,值相同且上下连续时合并 primaryKeyField:'supplier', // 其它需要合并单元格的字段名,可为空 mergeColFieldNames:['produce'] }, { // 其它表格配置 } ] }

export default config;

在 `viewConfigs` 中可配置多个表格

后端

架构说明

| 层级 | 技术组件 | 说明 | | ------- | ------------------------------------------------- | ---------------- | | 注册/配置中心 | Nacos(支持 Zookeeper/Consul/Eureka 一键切换) | 服务注册发现、动态配置管理 | | 网关层 | Spring Cloud Gateway | 统一入口、负载均衡、熔断降级限流 | | 服务间调用 | Dubbo + OpenFeign | RPC + HTTP 双通道 | | 分布式事务 | Seata | 保障跨服务数据一致性 | | 消息队列 | RabbitMQ / ActiveMQ / RocketMQ(可一键切换) | 异步解耦 | | 缓存 | Redis(单机/哨兵/集群模式) | 高性能缓存 | | 链路追踪 | SkyWalking | 全链路性能监控 | | 日志收集 | Filebeat → Logstash → ElasticSearch → Kibana(ELK) | 集中日志分析 | 框架:使用 SpringBoot + Spring Cloud 微服务,支持单体部署和微服务部署(大多数都为单体部署) ⚠️ 注意:需要区分单体和微服务环境,这会对开发产生影响,如果是微服务环境则一些内部接口会无法调用,需要使用 RPC 接口

配置管理:配置文件 或 Nacos 最低内存要求:32G Java 版本:8

**二开服务** E10 会有一个独立的二开服务,专门用来做二次开发,它和主服务是分开的,我们写的二次开发都会部署到二开服务下。二开服务可以单独重启,不会导致主服务停止运行。

开发环境搭建

创建 Gradle 项目

E10 开发环境搭建要比 E9 复杂的多,你需要创建一个 Gradle 项目,可以使用 IDEA 进行创建,创建时 JDK 选择 1.8

创建后会下载 Gradle ,由于网络环境可能会下载失败

创建好项目之后,创建 common 子模块(你可能不需要,但为了兼容下面的配置)以及另外一个模块,你开发的代码就写在这个模块上。

然后在 build.gradle 文件覆盖粘贴下面代码:

group 'com.weaver.seconddev'  
version '1.0.0'  
description 'e10二开'  
  
// 应用到所有项目(包括根项目)  
allprojects {  
    apply plugin: 'java'  
  
    java {  
        sourceCompatibility = JavaVersion.VERSION_1_8  
        targetCompatibility = JavaVersion.VERSION_1_8  
    }  
  
    compileJava {  
        options.encoding = 'UTF-8'  
        targetCompatibility = JavaVersion.VERSION_1_8  
        sourceCompatibility = JavaVersion.VERSION_1_8  
    }  
  
  
    repositories {  
        maven { url 'https://maven.aliyun.com/repository/public/' }  
        mavenLocal()  
        mavenCentral()  
    }  
  
    // 添加 zipJar 任务,生成 build.zip 文件,可以用于上传到二开服务  
    tasks.register('zipJar', Zip) {  
        dependsOn jar  
        archiveFileName = 'build.zip'  
        destinationDirectory = layout.buildDirectory.dir("libs")  
  
        from jar.archiveFile  
    }  
  
    jar {  
        // 确保在执行jar任务之前先执行classes任务  
        dependsOn ':classes'  
        // 执行 jar 任务之后执行 zipJar 任务,生成 build.zip 文件  
        finalizedBy(zipJar)  
    }  
}  
  
// 应用到子模块  
subprojects { subproject ->  
    // 除 common 模块外,其它子模块都引入 common 模块的源码  
    if (subproject.name != 'common') {  
        dependencies {  
            implementation project(':common')  
        }  
    }  
  
    // 打包配置  
    if (subproject.name != 'common') {  
        jar {  
            archiveBaseName = "secondev-hnweaver-" + subproject.name  
        }  
    }  
  
    sourceSets {  
        main {  
            resources {  
                exclude '**'  
            }  
        }    }  
    jar {  
        manifest {  
            attributes 'weaver-ecode-seconddev-id': 'hnweaver-' + rootProject.version,  
                    'Implementation-Version': rootProject.version,  
                    'Implementation-Vendor-Id': rootProject.group,  
                    'Implementation-Title': rootProject.name  
        }  
        // 将源码打进jar包  
        from(subproject.sourceSets.main.java)  
    }  
  
    // 添加公共的依赖  
    dependencies {  
        compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.30'  
        annotationProcessor 'org.projectlombok:lombok:1.18.30'  
        testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.3")  
        testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.3")  
        testImplementation("org.junit.platform:junit-platform-launcher:1.13.3")  
        testImplementation("org.mockito:mockito-core:5.18.0")  
        testImplementation("org.mockito:mockito-junit-jupiter:5.18.0")  
  
        // 添加二开服务拉取的清单依赖  
        def includeType = ['**/*.jar', '**/*.class']  
        implementation fileTree(dir: rootProject.projectDir.getPath() + '/secDevLib', includes: includeType)  
        // 项目二开自定义依赖  
        implementation fileTree(dir: subproject.projectDir.getPath() + "/out-dep", includes: includeType)  
  
    }  
  
    test {  
        useJUnitPlatform()  
    }  
}

该 Gradle 包含了以下配置:

  • java 版本为 1.8
  • E10 的依赖都放在 `secDevLib` 此目录下
  • 第三方依赖都放在 `out-dep` 目录下
  • 所有模块都依赖 common 模块(放公共代码)
  • 执行 jar 任务时打成符合 E10 部署要求的压缩包
  • 添加测试依赖:包含 junit5 和 mockito

build.gradle 文件中的 hnweaver 为组织名称,你可以改为你的组织名称

拉取开发依赖

按照官方说明文档,在你的 E10 服务器中拉取依赖到你的电脑中,文档:e10平台定制开发指引

将所有依赖都拷贝到项目的 `secDevLib` 目录中,然后重新加载 gradle 项目。

开发环境项目模板

使用此项目模板可免除上面复杂的配置,里面开发环境都帮你搭好了 模板下载:

注意:模板里面的开发依赖比较旧,需要去除里面的依赖,到服务器拉取开发依赖到 `secDevLib` 目录中

你的第一个接口

在你的开发环境项目上创建一个接口,如果你使用的是我给的开发环境模板,那么就有这个接口了

package com.weaver.seconddev.hnweaver.demo.api;

import com.weaver.common.authority.annotation.WeaPermission;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 姚礼林
 * @desc 测试接口
 * @date 2024/4/1
 */
@RestController
@RequestMapping({"/api/secondev/hnweaver/workflow/demo","/papi/secondev/hnweaver/workflow/demo"})
@WeaPermission(publicPermission = true)
@Slf4j
public class ApiDemoController {

    @GetMapping("/hello")
    public String hello() {

        return "hello world";
    }
}

E10 后端 WEB 接口的开发与 SpringBoot 中是相同的。

接口必需包含的注解:

  • @RestController:表示此接口只返回数据,而不是视图
  • @WeaPermission:不加则访问不了
  • @RequestMapping:接口路径,/api 开头表示需要认证才能访问,/papi 开头表示此接口是公共暴露的,不需要进行认证就能访问(相当于 E9 的白名单接口)

打包与部署

执行你的模块中的 gradle 的 jar 任务,生成部署的压缩包

在当前模块的 /build/libs 目录下,找到 build.zip 文件,等会需要将此压缩包上传到系统进行部署

打开你的 E10 系统,进入 /ecode 页面,然后点击此图标进入监控管理平台(必需是系统管理员账号)

选择部署 jar,选择二开服务,上传 build.zip 文件夹,此时会进行文件校验,过程比较久需要耐心等待。之后会让你填写开发说明,以及各个类的说明。

然后进行 jar 包审核,审核通过之后到运维平台重启二开服务

重启之后在系统链接地址上输入 `/api/secondev/hnweaver/workflow/demo/hello` 进行访问,看是否会返回 hello world

团队 jar 包部署

对于团队开发,可以在项目中为每个团队成员创建一个模块,例如 yllDev,在部署时打包当前模块的 jar 包,这样每个人打包的 jar 包就不会冲突。

⚠️ 注意:jar 包之间不能包含相同的文件,否则无法通过部署校验,如果团队中共享了公共模块的代码,需将公共模块进行独立打包部署,各成员的 jar 包不含公共模块代码

WEB 接口开发

接口开与 SpringBoot 的接口开发一致

强制要求: 接口统一返回 `WeaResult` 对象

接口开发示例:

Controller:

package com.weaver.seconddev.hnweaver.demo.api;

import com.weaver.common.authority.annotation.WeaPermission;
import com.weaver.common.base.entity.result.WeaResult;
import com.weaver.seconddev.hnweaver.demo.domain.param.HelloParam;
import com.weaver.seconddev.hnweaver.demo.service.HelloService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 姚礼林
 * @desc 测试接口
 * @date 2024/4/1
 */
@RestController
@RequestMapping({"/api/secondev/hnweaver/workflow/demo","/papi/secondev/hnweaver/workflow/demo"})
@WeaPermission(publicPermission = true)
@Slf4j
@RequiredArgsConstructor
public class ApiDemoController {

    private final HelloService helloService;

    @GetMapping("/hello")
    public WeaResult<String> hello(@RequestParam("name") String name) {

        return WeaResult.success("hello world");
    }

    @PostMapping("/hello")
    public WeaResult<String> posetHello(@RequestBody HelloParam param) {
        if (param == null || param.getName() == null) {
            return WeaResult.fail("name 不能为空");
        }
        if (helloService.hello(param)) {
            return WeaResult.success("hello world");
        }
        return WeaResult.fail("执行错误");
    }
}

请求参数对象:

package com.weaver.seconddev.hnweaver.demo.domain.param;  
  
import lombok.Data;  
  
/**  
 * @author 姚礼林  
 * @desc 请求参数  
 * @date 2026/6/16  
 **/@Data  
public class HelloParam {  
    private String name;  
    private Integer age;  
}

业务接口:

package com.weaver.seconddev.hnweaver.demo.service;  
  
import com.weaver.seconddev.hnweaver.demo.domain.param.HelloParam;  
  
/**  
 * @author 姚礼林  
 * @desc 测试业务接口  
 * @date 2026/6/16  
 **/
 public interface HelloService {  
  
    boolean hello(HelloParam param);  
}

业务实现类:

package com.weaver.seconddev.hnweaver.demo.service.impl;

import com.weaver.seconddev.hnweaver.demo.domain.param.HelloParam;
import com.weaver.seconddev.hnweaver.demo.service.HelloService;

/**
 * @author 姚礼林
 * @desc 测试业务类
 * @date 2026/6/16
 **/
public class HelloServiceImpl implements HelloService {

    @Override
    public boolean hello(HelloParam param) {
        return true;
    }
}

全局异常处理器

如果接口发生异常,则会使用系统默认的对象进行返回,提示服务器错误,可以创建全局异常处理器,让发生异常时也返回 `WeaResult` ,这样前端能统一按 `WeaResult` 处理。

package com.weaver.seconddev.hnweaver.common.handler;

import com.weaver.common.base.entity.result.WeaResult;
import com.weaver.seconddev.hnweaver.common.exception.ApiExceptionMsgInterface;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器,仅作用于 com.weaver.seconddev.hnweaver 包下的 Controller
 */
@RestControllerAdvice(basePackages = "com.weaver.seconddev.hnweaver")
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public WeaResult<String> handleException(Exception ex) {
        log.error("接口发生异常:{}",ex.getMessage(), ex);
        if (ex instanceof ApiExceptionMsgInterface) {
            return WeaResult.fail("系统异常:" + ex.getMessage());
        } else if (ex instanceof MissingServletRequestParameterException) {
            return WeaResult.fail("缺少参数:" + ex.getMessage());
        }

        return WeaResult.fail("系统发生异常");
    }
}

配置获取

配置文件路径:`E10部署路径\weaver-secondev-service\webapps\ROOT\WEB-INF\classes\weaver\config\config-center\weaver-secondev-service.properties`

读取示例:

@Data
@Configuration
@RefreshScope
public class TwoHaoHrProperty {
    @Value("${2HaoHr.appId}")
    private String appId;
    @Value("${2HaoHr.appKey}")
    private String appKey;
    @Value("${oaAddress}")
    private String oaAddress;
    @Value("${2HaoHr.ssoUrl:https://h5.2haohr.com/browser/authorize.html}")
    private String ssoUrl;
}

**读取其它配置文件:**

使用 `PropertySource` 指定配置文件

⚠️ 注意:如果配置文件不存在会导致服务无法启动

@PropertySource("classpath:/weaver/config/config-center/seconddev-demo-openapi.properties")
@Configuration
@Data
public class DemoProperties {
    @Value("${openapi.serverAddress}")
    private String serverAddress;
    @Value("${openapi.cropid}")
    private String cropid;
    @Value("${openapi.appKey}")
    private String appKey;
    @Value("${openapi.appSecret}")
    private String appSecret;
}

日志

E10 默认日志级别为 error,如果需要输出 info 或 debug 级别日志,需要修改配置文件配置日志级别,修改后只能临时生效,过段时间会自动恢复日志级别为 error。

**配置日志级别方法:** 修改 `E10部署路径\weaver-secondev-service\webapps\ROOT\WEB-INF\classes\weaver\config\config-center\weaver-secondev-service.properties` 配置文件,在配置文件加上:

logging.level.com.weaver.seconddev=[日志级别]

**日志输出:** 在类中使用 `@Slf4j` 注解

示例:

package com.weaver.seconddev.hnweaver.demo.api;

import com.weaver.common.authority.annotation.WeaPermission;
import com.weaver.common.base.entity.result.WeaResult;
import com.weaver.seconddev.hnweaver.demo.domain.param.HelloParam;
import com.weaver.seconddev.hnweaver.demo.service.HelloService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author 姚礼林
 * @desc 测试接口
 * @date 2024/4/1
 */
@RestController
@RequestMapping({"/api/secondev/hnweaver/workflow/demo","/papi/secondev/hnweaver/workflow/demo"})
@WeaPermission(publicPermission = true)
@Slf4j
@RequiredArgsConstructor
public class ApiDemoController {

    private final HelloService helloService;

    @GetMapping("/hello")
    public WeaResult<String> hello(@RequestParam("name") String name) {
        log.info("这是一条日志,参数name={}", name);
        return WeaResult.success("hello world");
    }
}

**日志获取:** 在运维平台中获取日志

数据库操作

参见:e10平台定制开发指引

E10 执行数据库 SQL 要比 E9 更加复杂

需要先创建 `ExecuteSqlEntity` 对象,用来存放 SQL 语句以及其它的一些信息

  • Sql :必需使用 Base64 编码
  • GroupId :数据库表对应的服务,可以通过这个接口查到每个模块的 GroupId :/api/datasource/ds/group?sourceType=LOGIC,目前没有方法知道哪个表对应哪个服务,所以只能凭感觉查找,比如说这是一个流程相关的表,那么你就选流程的模块
  • SourceType :固定为 SourceType.LOGIC

此方法不支持使用占位符注入参数,只能使用字符串拼接参数

ExecuteSqlEntity sqlEntity = new ExecuteSqlEntity();
String sql = "select person_name,age from uf_personrecord";
sqlEntity.setSql(Base64.encode(sql));
sqlEntity.setGroupId("weaver-ebuilder-form-service");
sqlEntity.setSourceType(SourceType.LOGIC);

之后使用 `dataSetService` 执行 ,获取返回结果,返回的 `HashMap` 键为字段名,值为字段的值

Map<String, Object> result = dataSetService.executeSql(sqlEntity);
// records 可能为null,注意判断
List<HashMap<String ,Object>> records = (List<HashMap<String, Object>>) result.get("records");
for (HashMap<String, Object> row : records) {
     row.forEach((k,v) -> log.info("column:"+k+",value:"+v));
}

更多详细使用请查看官方说明文档

使用工具类执行 SQL

为了简化 SQL 的执行,我创建了一个工具类,让它和 E9 的 `RecordSet` 一样简单。

  • 操作简单
  • 支持使用占位符注入参数

你需要将此公共类库放入你的项目,开源地址:weaver-e10-second-dev-common: 泛微 E10 二次开发公共类库,提供工具类和通用组件,可在任何 E10 二开项目中使用。

使用示例:

String sql = "SELECT " + fieldName + " FROM " + tableName + " WHERE id = ?";
SqlExecuteResult result = sqlExecuteClient.executeSql(groupType, sql, dataId);
if (!result.isSuccess()) {
    log.error("查询字段值失败,sql:{},错误信息:{}", sql, result.getMessage());
    throw new SqlExecuteException("查询字段值失败,sql:" + sql + ",错误信息:" + result.getMessage());
}
// 获取查询数据
List<Map<String, Object>> records = result.getRecords();

`executeSql()` :

SqlExecuteResult executeSql(DatasourceGroupType groupType, String sql, SqlParam... params)
  • groupType:数据库表所属模块
  • sql:需要执行的sql,不区分查询和更新
  • params:传入 sql 中的参数,如果没有则不需要传

可通过返回结果的 `SqlExecuteResult.isSuccess` 判断是否执行成功

⚠️ 注意:返回结果的字段名可能会为大写,获取时需要注意,你可以使用 `SqlExecuteClient.getFieldValueIgnoreCase()` 方法忽略字段名大小写来获取字段值

缓存

通过 `BaseCache` 来操作缓存,参考:e10平台定制开发指引

示例:

@RestController
@RequestMapping({"/api/secondev/workflow/demo","/papi/secondev/workflow/demo"})
@Slf4j
public class ApiDemoController {
    
    // 默认读weaver-secondev-service\webapps\ROOT\WEB-INF\classes\weaver\config\config-center\weaver-secondev-service.properties文件
    @Value("${spring.datasource.username}")
    private String name;
    @Autowired
    private BaseCache baseCache;
    
    @GetMapping("/hello")
    public String hello() {
        log.info("hello,=> {}",name);
        // 写入缓存并设置时间
        baseCache.set("dev", "age", 18,60*60);
        log.info("age = {}", baseCache.get("dev", "age"));
        baseCache.del("dev", "age");
        log.info("age = {}", baseCache.get("dev", "age"));
        return "hello world";
    }
}

消息队列

官方文档说明:e10平台定制开发指引

定时任务

官方文档说明:E10自定义定时任务开发文档

定时任务有两个方法: 1. 通过动作流实现 2. 通过 @ESchedulerHandler 注解实现

@ESchedulerHandler 注解实现示例:

package com.weaver.seconddev.demo.escheduler;

import com.weaver.common.escheduler.context.ESchedulerJobHelper;
import com.weaver.common.escheduler.handler.annotation.ESchedulerHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class DemoEscheduler {

    private static final Logger log = LoggerFactory.getLogger(DemoEscheduler.class);

    /**
     * value: 业务方法的执行名称
     * cron: cron表达式自定义执行时间
     * @throws Exception
     */
    @ESchedulerHandler(value = "demoJobHandler", cron = "0/10 * * * * ?")
    public void demoJobHandler() throws Exception {
        log.info("demoJobHandler runnint");
        try {
            // todo
            // 获取页面上配置的任务参数
            final String jobParam = ESchedulerJobHelper.getJobParam();

        } catch (Exception e) {
            e.printStackTrace();
            log.error("demoJobHandler runnint error");
        }
        ESchedulerJobHelper.log("ESchedulerHandler, Hello World.");
    }
}

你可以通过 ` /info/dispatch/escheduler/report` 页面来查看这些计划任务

动作流介绍

动作流是 E10 的一个强大的功能,它内置了很多功能,比如表单数据修改、接口连接器调用、消息提醒等,可以通过多种形式触发动作流,可在动作流内使用条件进行分叉,控制动作走向。

可进入系统后台,在这个页面管理所有动作流,地址:/info/esb/application

动作流功能包括:

  • 内置多种标准组件,比如消息提醒,表单数据修改,数据计算(变量修改)等
  • 可添加条件,实现组件根据条件进行流转
  • 每个组件都可以获取前面组件的数据
  • 可对参数进行动态赋值,例如使用函数计算(可执行js),获取表单字段值(流程中使用)

函数

在动作流中可以创建自定义函数,来进行参数赋值

函数可以在动态赋值对话框中创建,也可以在 ESB 中心的函数列表中创建:`/info/esb/setting/function`

**创建函数**

params 参数可以有多个参数,例如:

function test(param1,param2){

}

示例:

function convertGenderToNum(value){
	return value === 'male'? 1: 2;
}

注意:创建函数时要先选择结果类型,因为函数创建之后结果类型就不能更改,你只能删了函数重新创建。 js 中不能包含注释

连接器

连接器可作为动作流中的组件,是一个非常常用的功能,可用于进行接口调用,例如流程归档之后调用第三方系统接口反馈状态。

入口:ESB 中心> 连接器 创建连接器时配置接口地址以及请求参数、返回参数即可,连接器发布后可在动作流中调用此连接器,实现接口调用。

自定义 Action 接口

可在动作流中配置自定义 Action 接口,通过 Action 来进行处理

**应用场景:** 在流程节点后操作添加动作流,在动作流内配置 Action,通过 Action 来实现功能,这个有点像 E9 的流程 Action,不过 E10 的 Action 是通用的,不止是在流程上

Action 具有输入和输出参数,在输入参数中可通过动态赋值,来获取表单字段值或者流程系统参数。输出参数可在动作流后面进行获取,比如说输出参数返回的是成功还是失败,在动作流进行流转控制,或者是在动作流获取输出参数中的结果。

输入参数:

输出参数:

创建 Action

在类中实现 `EsbServerlessRpcRemoteInterface` 接口 需要使用 `@Service` 注解,并且配置名称,名称和类名一样即可,如果不一样则可能无法识别该 Action。

execute() 方法的参数对应动作流中 Action 的输入参数,方法返回的 `WeaResult` 中的类型对象,对应动作流中 Action 的输出参数

示例:

package com.weaver.seconddev.hnweaver.demo.action;  
  
import com.weaver.common.base.entity.result.WeaResult;  
import com.weaver.esb.api.rpc.EsbServerlessRpcRemoteInterface;  
import org.springframework.stereotype.Service;  
  
import java.util.HashMap;  
import java.util.Map;  
  
/**  
 * @author 姚礼林  
 * @desc 测试 Action  
 * @date 2024/5/6  
 */
@Service("TestAction")  
public class TestAction implements EsbServerlessRpcRemoteInterface {  
  
    @Override  
    public WeaResult<Map<String, Object>> execute(Map<String, Object> params) {  
        WeaResult<Map<String, Object>> result;  
        if (params.get("name") == null || "1".equals(params.get("name"))) {  
            result =  WeaResult.fail("流程出错啦,name 参数不正确");  
            return result;  
        }  
        Map<String, Object> data = new HashMap<>();  
        result = WeaResult.success(data);  
        return result;  
    }  
}

**在 xml 配置文件上配置 Action**

上传后端 jar 包后, 需要在 `/ecode/monitor/loom/deploy/jar` 页面编辑配置文件,配置刚才创建的 Action,注意如果 Action 不存在则会导致二开服务无法启动。

需要在 xml 文件内的 `beans` 标签内添加: 以刚才创建的 Action 为示例

<dubbo:service ref="TestAction"
               interface="com.weaver.esb.api.rpc.EsbServerlessRpcRemoteInterface"
               group="TestAction" />
  • ref: Action 中的 @Service 注解中的名称,不可与其它名称重复
  • interface: 固定为 `com.weaver.esb.api.rpc.EsbServerlessRpcRemoteInterface`
  • group : 与 ref 一致

配置之后重启二开服务生效

**在动作流上配置 Action**

在动作流中添加操作,选择 serverless/Action

分组标识填写刚才在配置文件上配的 `group`

之后填写输入和输出参数

开放平台

开放平台介绍

开放平台入口:后台中心 -> 开放平台 开放平台接口文档:/sp/opendoc

**什么是开放平台** 开放平台内有许多公开的接口,可以供第三方或者OA系统内部进行调用,也就是这些接口就用来给别人调的

开放平台使用

新建应用和完善开发者资料

需要先新建应用和完善开发者资料才可以调用开放平台接口,可以在开放平台文档查看如何操作:`/sp/opendoc`

在后端中调用开放平台接口

**获取 SDK** 从此处下载 SDK,然后在此页面 `/ecode/monitor/loom/thirdJar` 将 SDK 作为第三方依赖上传到二开服务中,如提示文件格式不对,需要打包成 build.zip 文件进行上传

**获取 Token** 需要 token 才能调用接口,也就是先要进行认证

编写一个 Token 获取工具类,用于获取 Token

package com.weaver.seconddev.hnweaver.common.sdk.util;

import com.weaver.openapi.pojo.auth.params.AccessTokenParam;
import com.weaver.openapi.pojo.auth.params.CodeParam;
import com.weaver.openapi.pojo.auth.res.AccessToken;
import com.weaver.openapi.pojo.auth.res.Code;
import com.weaver.openapi.service.AuthService;
import lombok.experimental.UtilityClass;

/**
 * @author 姚礼林
 * @desc 开放接口 token 获取工具类
 * @date 2025/7/5
 **/
@UtilityClass
public class OpenApiTokenUtil {

    /**
     * 获取开放平台接口 Token
     *
     * @param appKey        应用 Key
     * @param appSecret     应用 Secret
     * @param cropid        企业 cropid,可在开发者资料中查看 corpId
     * @param state         重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节
     * @param serverAddress OA 地址
     * @return 开放平台接口 Token
     */
    public static String getToken(String appKey, String appSecret, String cropid, String state, String serverAddress) {
        AccessTokenParam tokenParam = new AccessTokenParam(appKey, appSecret,
                "authorization_code", fetchCode(cropid, state, serverAddress));
        AccessToken token = AuthService.getAuthAccessToken(tokenParam,
                serverAddress + "/papi/openapi", null);
        return token.getAccessToken();
    }

    private static String fetchCode(String cropid, String state, String serverAddress) {
        CodeParam codeParam = new CodeParam(cropid, "code", state);
        Code code = AuthService.getAuthCode(codeParam, serverAddress + "/papi/openapi", null);
        return code.getCode();
    }
}

getToken() 参数说明:

  • appKey :可在开放平台的应用中查看
  • appSecret:可在开放平台的应用中查看
  • cropid:可在开放平台的开发者资料中查看 corpId
  • state:随便填
  • serverAddress:E10 地址,如 http://127.0.0.1:20600

使用示例:

package com.weaver.seconddev.hnweaver.common.request;

import com.weaver.seconddev.hnweaver.common.sdk.util.OpenApiTokenUtil;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author 姚礼林
 * @desc todo
 * @date 2025/9/26
 **/
class OpenApiTokenUtilTest {

    @Test
    void getToken() {
        String token = OpenApiTokenUtil.getToken("301dcdc2a74ba068d687e9359325af44",
                "d1abb71bs77ec2h0d4ddf4461b11e4f", "7701caaehg185d19c3je87ad0c4642fa",
                "dev", "http://127.0.0.1:20600");
        System.out.println(token);
        assertFalse(token.isEmpty());
    }
}

为了在后端中方便调用,可以创建一个管理 Token 获取的类,将 appKey、appSecret 这些信息配置在配置文件里

示例: 可将 Token 存放到缓存

package com.weaver.seconddev.hnweaver.common.request;

import com.weaver.common.cache.base.BaseCache;
import com.weaver.openapi.pojo.auth.params.AccessTokenParam;
import com.weaver.openapi.pojo.auth.params.CodeParam;
import com.weaver.openapi.pojo.auth.res.AccessToken;
import com.weaver.openapi.pojo.auth.res.Code;
import com.weaver.openapi.service.AuthService;
import com.weaver.seconddev.hnweaver.common.config.OpenApiProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author 姚礼林
 * @desc 获取开放接口 token
 * @date 2024/5/30
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OpenApiTokenManger {
    private static final String STATE = "dev";
    private static final String MODULE_KEY = "seconddev";
    private static final String TOKEN_CACHE_KEY = "openApiAccessToken";
    private final BaseCache baseCache;
    private final OpenApiProperties properties;

    public String fetchAccessToken() {
        Object tokenCache = baseCache.get(MODULE_KEY, TOKEN_CACHE_KEY);
        if (tokenCache != null) {
            return (String) tokenCache;
        }
        AccessTokenParam tokenParam = new AccessTokenParam(properties.getAppKey(), properties.getAppSecret(),
                "authorization_code", fetchCode());
        AccessToken token = AuthService.getAuthAccessToken(tokenParam,
                properties.getServerAddress()+"/papi/openapi", null);
        baseCache.set(MODULE_KEY, TOKEN_CACHE_KEY,token.getAccessToken(),token.getExpiresIn());
        return token.getAccessToken();
    }

    public String getApiUrlPrefix() {
        return properties.getServerAddress() + "/papi/openapi";
    }

    private  String fetchCode() {
        CodeParam codeParam = new CodeParam(properties.getCropid(), "code", STATE);
        Code code = AuthService.getAuthCode(codeParam, properties.getServerAddress()+"/papi/openapi", null);
        return code.getCode();
    }

}

配置类,存放用于调用开放平台的一些信息。 需要在配置类上配置属性,都以 openapi. 开头,例如:openapi.serverAddress 、openapi.cropid

package com.weaver.seconddev.hnweaver.common.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author 姚礼林
 * @desc 开放接口配置属性
 * @date 2024/5/31
 */
@Configuration
@ConfigurationProperties(prefix = "openapi")
@Data
public class OpenApiProperties {
    private String serverAddress;
    private String cropid;
    private String appKey;
    private String appSecret;
}

**调用 Open API 接口**

该示例使用 SDK 调用部门列表接口

/**
 * @author 姚礼林
 * @desc TODO
 * @date 2024/5/30
 */
@RestController
@WeaPermission(publicPermission = true)
@RequestMapping("/api/secondev/hnweaver/demo/openapi")
public class OpenApiTestController {
    @Autowired
    private  OpenApiTokenManger tokenManger;
    @Autowired
    private OpenApiProperties properties;

    @GetMapping("/departments")
    public WeaResult<DeptList> queryDepartmentList() {
        DeptListParam deptListParam = new DeptListParam(tokenManger.fetchAccessToken(),null,null,null);
        DeptList deptList = DeptService.listDeptV2(deptListParam, properties.getServerAddress()+"/papi/openapi", null);

        return WeaResult.success(deptList);
    }
}

如果想用 HTTP 请求接口进行调用,在接口参数中添加 `access_token` 参数,传入获取的 token 即可

标准内部接口调用

E10 后端有一些公开的内部接口可以调用,比如获取人员信息,下载文件等

获取用户信息 HrmCommonEmployeeService

该类在微服务下可正常调用 使用 `HrmCommonEmployeeService` 类可以获取到用户信息

⚠️ 注意用户表的id和user_id字段不是同一个值,前端调用系统标准接口参数中的用户id为用户表的id字段

获取用户信息:

// 获取用户对象
SimpleEmployee employee = employeeService.getById(Long.parseLong(userId));

根据用户id和租户key获取用户信息:

User currentUser = UserContext.getCurrentUser();
List<SimpleEmployee> employee = employeeService
        .getEmployeeByUserIds(CollUtil.toList(currentUser.getUserId()), currentUser.getTenantKey());

参考文档:

技术文档-人员全信息查询接口

获取当前用户

<span style="color:red">注意:有 getUserId() 和 getEmployeeId(),两个是不一样的,getEmployeeId() 获取的是数据库表的数据id,getUserId() 获取的是数据库表的 user_id 字段,一般用 getEmployeeId()</span>

<span style="color:red">如果是系统远程调用接口可能获取不到当前用户</span>

UserContext.getCurrentUser();

文件下载

可根据文件id获取实体文件(下载),例如下载文档中的文件

注入 FileDownloadService

@Autowired
FileDownloadService downloadService;
    

获取文件流

FileData fileData = downloadService.downloadFile(1213L);
fileData.getInputStream();