ForeverSmiYngEcologyKit/Ecology9开发手册.md
2024-10-09 09:19:17 +00:00

1211 lines
44 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ecology9 开发手册
> 在线地址: [http://wcode.store/#/./weaver/Ecology9%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C](http://wcode.store/#/./weaver/Ecology9开发手册)
## 1. 环境搭建
### 1.1 Ecology安装和启动
- 准备 `ecology` | `resin` | `jdk1.8[可直接使用已安装的]`
- 修改resin配置 `resin/config/resin.conf`
```xml
// 修改jdk路径相对路径或者绝对路径
<javac compiler="C:\Program Files\Java\jdk1.8.0_151\bin\javac" args="-encoding UTF-8"/>
// 修改ecology路径
<web-app id="/" root-directory="F:\WEAVER\ecology">
```
- 修改resin启动配置 `resin/resinstart.bat`
其中`java_home``jdk`的路径,可以是相对路径也可以是绝对路径
```properties
set java_home=C:\Program Files\Java\jdk1.8.0_151
```
- 启动`resin` 点击`resinstart.bat`
- 初始验证码文件路径 `ecology/WEB-INF/code.key`
### 1.2 主要目录介绍
- `ecology/classbean`存放编译后的class文件
- `ecology/log`:系统中日志存放目录
- `ecology/WEB-INF/prop``web`配置文件目录
- `ecology/WEB-INF/lib`:系统`jar`包路径
### 1.3 数据库配置
> 如果数据库与Ecology不在同一台服务器上则可以修改数据库配置文件中的数据库配置。
>
> 数据库配置文件路径:`ecology/WEB-INF/prop/weaver.properties`
```properties
# sqlServer
DriverClasses = com.microsoft.sqlserver.jdbc.SQLServerDriver
ecology.url = jdbc:sqlserver://host:port;DatabaseName=dbname
ecology.user = username
ecology.password = password
```
```properties
# oracle
DriverClasses = oracle.jdbc.OracleDriver
ecology.url = jdbc:oracle:thin:@host:port:ecology
ecology.user = username
cology.password = password
```
---
## 2. E9常见表结构
### 2.1 流程相关数据存储表
| 数据库表名 | 中文说明 | 备注 |
| ------------------------ | ---------------- | ------------------------------ |
| workflow_base | 流程基本信息 | isbill=1 |
| workflow_bill | 流程表单信息 | id > 0固定表名 id < 0 动态生成 |
| workflow_billfield | 表单字段信息 | |
| workflow_billdetailtable | 表单明细表 | |
| workflow_nodebase | 节点信息 | |
| workflow_flownode | 流程节点信息 | |
| workflow_nodelink | 流程出口信息 | |
| workflow_nodegroup | 节点操作人信息 | |
| workflow_groupdetail | 节点操作人详情 | |
| workflow_requestbase | 请求基本信息 | |
| workflow_currentoperator | 请求节点操作人 | |
| workflow_requestlog | 请求签字意见 | |
| workflow_nownode | 请求当前节点 | |
| workflow_browserurl | 系统浏览按钮信息 | |
| workflow_selectitem | 下拉框信息 | |
### 2.2 人力资源相关数据存储
| 表名 | 说明 | 备注 |
| -------------------- | ---------------------------- | ---- |
| HrmResource | 人力资源基本信息表 | - |
| HrmResource_online | 人员在线信息表 | - |
| HrmResourceManager | 系统管理员信息表 | - |
| HrmDepartment | 人力资源部门表 | - |
| HrmDepartmentDefined | 人力资源部门自定义字段信息表 | - |
| HrmSubCompany | 人力资源分部表 | - |
| hrmroles | 角色信息表 | - |
| hrmrolemembers | 角色人员 | - |
| hrmjobtitles | 岗位信息表 | - |
## 3. 前端开发
> `ecology9`前端上采用`react`+`antd`+`mobx`+`react-router`等框架实现的单页面应用
### 3.1 流程开发
> 流程表单前端接口:[E9流程表单前端接口API](https://e-cloudstore.com/ecode/doc?appId=98cb7a20fae34aa3a7e3a3381dd8764e&tdsourcetag=s_pctim_aiomsg#E9%E6%B5%81%E7%A8%8B%E8%A1%A8%E5%8D%95%E5%89%8D%E7%AB%AF%E6%8E%A5%E5%8F%A3API](https://e-cloudstore.com/ecode/doc?appId=98cb7a20fae34aa3a7e3a3381dd8764e&tdsourcetag=s_pctim_aiomsg#E9流程表单前端接口API))
- 所有接口统一封装在全局对象`window.WfForm`
- 表单字段相关操作不推荐使用jQuery禁止原生JS直接操作DOM结构
- 在开发过程中推荐都使用API接口操作由产品统一运维同时使用API才能完整的兼容移动终端
### 3.2 建模开发
#### 3.2.1 布局代码块
> 建模布局代码块使用上与流程表单的代码块基本一致区别在于接口的SDK不同建模表单的所有接口统一封装在全局对象 `window.ModeForm`中。
>
> [建模前端接口API](https://e-cloudstore.com/ecode/doc.html?appId=e783a1d75a784d9b97fbd40fdf569f7d&tdsourcetag=s_pctim_aiomsg)
#### 3.2.2 自定义按钮
> 后端应用中心 -> 建模引擎 -> 查询
>
> 任选一个查询页面 -> 自定义按钮 -> 右键 -> 新建
>
> ![1570360554483](asset/1570360554483.png)
- 方法体中存在多行代码时每个语句必须以`;`结尾否则会报错
- `params`的值等于 ` field1+field2+field3` 这个值是一个字符串
- `id`指的是数据`ID`
> 前端显示
![1570109435919](asset/1570109435919.png)
- **配置`ecode`使用**
> 新建前置文件 `index.js`并将方法挂到全局对象 `window.g` 下
![1570109735133](asset/1570109735133.png)
> 新建自定义按钮
![1570109991787](asset/1570109991787.png)
![1570110063242](asset/1570110063242.png)
#### 3.2.3 页面扩展
> 后端应用中心 -> 建模引擎 -> 模块
>
> 任选一个模块 -> 页面扩展 -> 右键 -> 新建
>
> ![1570110626080](asset/1570110626080.png)
- 扩展用途卡片页面查询列表批量操作)、卡片页面和查询列表
- 卡片页面可以设置页面扩展显示在卡片信息页面可以选择在新建页面编辑页面查看页面显示页面扩展
- 查询列表批量操作设置在查询列表时则会在引用该模块的查询列表的批量操作中显示页面扩展项在批量操作中勾选后会在前台列表中显示对应的页面扩展项
- 卡片页面和查询列表可以设置页面扩展项既显示在对应的卡片页面又显示在查询列表批量操作
- `javascript:test()`: 该方法可以在 `建模引擎 -> 查询 -> 该模块的查询列表 -> 编辑代码块` 中定义
![1570110945435](asset/1570110945435.png)
![1570110988895](asset/1570110988895.png)
> 前端按钮测试如下
![1570110886190](asset/1570110886190.png)
- 页面扩展同样可以配置 `ecode`使用**链接目标地址**改成: `javascript: window.g.test()`即可建议这样做方便后续代码维护
### 3.3 Ecode在线编辑
> ecode官方文档https://e-cloudstore.com/ecode/doc
>
> E9 技术站地址: https://e-cloudstore.com/e9/index2.html?tdsourcetag=s_pctim_aiomsg
>
> 使用已经封装好的组件进行页面开发或者页面改写会更加便捷迅速。
### 3.4 动态注入代码
> 通过前置加载动态注册代码,完全脱离代码块的实现方式。**非官方的方案,使用中有任何问题概不负责!**
>
> 官方用法请参考:[全局流程代码块整合](https://e-cloudstore.com/ecode/doc#4、全局流程代码块整合)
注意**Ecode部分版本不能使用更新至最新版本即可**
- 打开ecode页面 [config.js](https://github.com/Y-Aron/wcode/blob/master/WEB-INF/config.js) 中的代码复制到 `系统配置 -> config.js`
- 使用 `Wcode.runScript` 动态注入代码参数说明如下
| 参数 | 类型 | 可选 | 默认 | 说明 |
| ------- | --------------- | ---- | --------------- | ------------------------------------------------------------ |
| id | string | 必填 | | 流程代码块 id 指的是 流程的workflowid建模代码块 id 指的是 模块的 modeId查询列表代码块 id 指的是查询列表的id customid |
| mode | string | 必填 | '' | Wcode.WF_TYPE: 流程代码块MODE_TYPE建模代码块LIST_TYPE查询列表代码块 |
| appId | function | 必填 | '' | ecode 中应用的 id, 可使用 `${appid}` 占位符替换 |
| nodeId | integer\|string | 选填 | 0 | 流程使用指定节点时才加载代码不指定则只在创建节点时加载 |
| type | integer\|string | 选填 | 0 | 建模使用指定布局时才加载代码不指定则只在新建布局时加载 |
| menuIds | string | 选填 | ALL_MODE_LIST | 列表使用指定查询列表在哪个菜单下时加载不指定则默认所有菜单下存在该列表都加载 |
| noCss | boolean | 选填 | true | 是否禁止单独加载css通常为了减少css数量css默认前置加载 |
| cb | function | 选填 | function() {} | 代码块成功加载完毕的回调方法 |
> 在 Ecode 中任意创建一个应用,应用下新建一个 `register.js` 的前置文件,根据不同场景加入对应的代码
- 流程节点使用
```javascript
Wcode.runScript({
mode: Wcode.WF_TYPE,
id: '4',
appId: '${appId}'
})
```
- 建模布局使用
```javascript
Wcode.runScript({
mode: Wcode.MODE_TYPE,
id: '1',
appId: '${appId}',
type: Wcode.SHOW_TEMPLATE,
})
```
- 查询列表使用
```javascript
Wcode.runScript({
mode: Wcode.LIST_TYPE,
id: '1',
appId: '${appId}'
})
```
---
## 4. 后端开发
> 代码案例:[E9Demo](https://github.com/Y-Aron/E9Demo/tree/master)
>
> 工具类 wcode 代码: https://github.com/Y-Aron/wcode
### 4.1 `Java`项目环境搭建
#### 4.1.1 `web.xml` 部分配置
> `API` 接口的`xml`配置
```xml
<!-- ecology/WEB-INF/web.xml -->
<servlet>
<servlet-name>restservlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<!-- 设置jersey的扫包路径可配置多个隔开 -->
<param-value>com.cloudstore;com.api</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restservlet</servlet-name>
<url-pattern>/api/*</url-pattern>
<!-- 自定义接口前缀,绕开过滤器拦截 -->
<url-pattern>/port/*</url-pattern>
</servlet-mapping>
```
注意 /api/* 的请求必须在用户登录时才能进行访问否则会被过滤器过滤 web.xml 配置 SessionFilter
现了过滤拦截当然也可以自定义接口前缀如上配置了 /port/*
```xml
<filter>
<filter-name>SessionFilter</filter-name>
<filter-class>com.cloudstore.dev.api.service.SessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<!-- 拦截 /api/* 请求,做是否登录以及其他逻辑判断 -->
<url-pattern>/api/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<url-pattern>/page/interfaces/*.jsp</url-pattern>
</filter-mapping>
```
#### 4.1.2 项目环境搭建
> 传统 `JavaWeb` 项目搭建
- 创建`Java`项目
- 添加所需依赖
**File -> Project Structure -> Project Settings -> Libraries**
需要添加的`ecology`的依赖路径有: `ecology/WEB-INF/lib`; `resin/lib`; `ecology/classbean`;
其中`classbean`是必须要引入的, 其他两个按需引入
- 编译`Java`文件将编译后的`class`文件放入`ecology/classbean/`目录下即可
**注意:** `ecology/classbean` 最好备份, 因为`IDEA`在编译的时候可能会清除掉已有的`classbean`
> 使用 `Maven` 构建项目
相对于传统的 `JavaWeb` 项目而言使用 maven 可以做到快速搭建项目项目结构层次分明并且 jar 间的依赖关系可以得到直观的体现当然需要二开人员对 maven 有一定的了解并且熟悉 ec 系统所使用的依赖防止版本冲突
后端项目结构以及开发案例详见手册: [E9后端开发指南](https://e-cloudstore.com/e9/file/E9BackendDdevelopmentGuide.pdf)
#### 4.1.3 Java项目结构
> 一般情况下遵循以下项目结构进行后端开发
其中`com.api.wcode``com.engine.wcode`中的 `wcode`为自定义包名不与已有的包重复即可
```xml-dtd
com.api.wcode
|-- web 接口定义层
com.engine.wcode
|-- web 接口实现层
|-- service 服务定义层
|-- impl 服务实现层
|-- domain 数据层
|-- cmd 原子操作层定义
|-- dao Dao层
|-- mapper mapper接口定义层
```
### 4.2 后端API路径规范
| 模块 | 文件路径(含下级) | 接口访问地址 |
| -------- | ------------------- | ------------------ |
| 流程 | com.api.workflow | /api/workflow/… |
| 门户 | com.api.portal | /api/portal/… |
| 文档 | com.api.doc | /api/doc/… |
| 建模 | com.api.formmode | /api/formmode/… |
| 移动建模 | com.api.mobilemode | /api/mobilemode/… |
| 会议 | com.api.meeting | /api/meeting/… |
| 人力 | com.api.hrm | /api/hrm/… |
| 财务 | com.api.fna | /api/fna/… |
| 项目 | com.api.prj | /api/prj/… |
| 公文 | com.api.odoc | /api/odoc/… |
| 集成 | com.api.integration | /api/integration/… |
| 微博 | com.api.blog | /api/blog/… |
### 4.3 自定义`Java`接口
#### 4.3.1 流程节点前后附加操作
> 在节点前后附加操作中可设置接口动作,完成流程自定义附加操作
>
> 接口动作标识不能重复;接口动作类文件必须是类全名,该类必须实现接 `weaver.interfaces.workflow.action` 方法 `public String execute(RequestInfo request)`
参考代码如下
```java
package com.engine.wcode.action;
import com.weaver.general.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weaver.hrm.User;
import weaver.interfaces.workflow.action.Action;
import weaver.soa.workflow.request.*;
public class TestAction implements Action {
private String customParam; //自定义参数
private final Logger logger = LoggerFactory.getLogger(TestAction.class);
@Override
public String execute(RequestInfo requestInfo) {
logger.debug("进入action requestid = {}", requestInfo.getRequestid());
showCurrentForm(requestInfo);
showFormProperty(requestInfo);
showDetailsTables(requestInfo);
logger.debug("Action 执行完成,传入自定义参数:{}", this.getCustomParam());
// requestInfo.getRequestManager().setMessagecontent("返回自定义的错误消息");
// requestInfo.getRequestManager().setMessageid("自定义消息ID");
// return FAILURE_AND_CONTINUE; // 注释的三句话一起使用才有效果!
return SUCCESS;
}
private void showCurrentForm(RequestInfo requestInfo) {
String requestid = requestInfo.getRequestid(); // 请求ID
String requestLevel = requestInfo.getRequestlevel(); // 请求紧急程度
// 当前操作类型 submit:提交/reject:退回
String src = requestInfo.getRequestManager().getSrc();
// 流程ID
String workFlowId = requestInfo.getWorkflowid();
// 表单名称
String tableName = requestInfo.getRequestManager().getBillTableName();
// 表单数据ID
int bill_id = requestInfo.getRequestManager().getBillid();
// 获取当前操作用户对象
User user = requestInfo.getRequestManager().getUser();
// 请求标题
String requestName = requestInfo.getRequestManager().getRequestname();
// 当前用户提交时的签字意见
String remark = requestInfo.getRequestManager().getRemark();
// 表单ID
int form_id = requestInfo.getRequestManager().getFormid();
// 是否是自定义表单
int isbill = requestInfo.getRequestManager().getIsbill();
logger.debug("requestid: {}", requestid);
logger.debug("requestLevel: {}", requestLevel);
logger.debug("src: {}", src);
logger.debug("workFlowId: {}", workFlowId);
logger.debug("tableName: {}", tableName);
logger.debug("bill_id: {}", bill_id);
logger.debug("user: {}", user);
logger.debug("requestName: {}", requestName);
logger.debug("remark: {}", remark);
logger.debug("form_id: {}", form_id);
logger.debug("isbill: {}", isbill);
}
/**
* 获取主表数据
*/
private void showFormProperty(RequestInfo requestInfo) {
logger.debug("获取主表数据 ...");
// 获取表单主字段值
Property[] properties = requestInfo.getMainTableInfo().getProperty();
for (Property property : properties) {
// 主字段名称
String name = property.getName();
// 主字段对应的值
String value = Util.null2String(property.getValue());
logger.debug("name: {}, value: {}", name, value);
}
}
/**
* 取明细数据
*/
private void showDetailsTables(RequestInfo requestInfo) {
logger.debug("获取所有明细表数据 ...");
// 获取所有明细表
DetailTable[] detailTables = requestInfo.getDetailTableInfo().getDetailTable();
if (detailTables.length > 0) {
for (DetailTable table: detailTables) {
// 当前明细表的所有数据,按行存储
Row[] rows = table.getRow();
for (Row row: rows) {
// 每行数据再按列存储
Cell[] cells = row.getCell();
for (Cell cell: cells) {
// 明细字段名称
String name = cell.getName();
// 明细字段的值
String value = cell.getValue();
logger.debug("name: {}, value: {}", name, value);
}
}
}
}
}
public String getCustomParam() {
return customParam;
}
public void setCustomParam(String customParam) {
this.customParam = customParam;
}
}
```
> 配置:后端应用中心 -> 流程引擎 -> 路径管理 -> 路径设置
>
> 任选一个流程 -> 流程设置 -> 节点信息
>
> 任选一个节点 -> 节点前 / 节点后附加操作
>
> ![1570114991641](asset/1570114991641.png)
>
> ![1570158010000](asset/1570158010000.png)
#### 4.3.2 页面扩展接口
> 页面扩展 -> 接口动作 -> 自定义接口动作
>
> 执行页面扩展的后续操作,通过配置自定义 `Java`接口动作类实现。
>
> 接口动作类文件必须是类全名。该类必须继承 `weaver.formmode.customjavacode.AbstractModeExpandJavaCode` 方法 `public void doModeExpand(Map<String, Object> param)`
参考代码
```java
package com.engine.wcode.formmode.extend;
import weaver.conn.RecordSet;
import weaver.general.Util;
import weaver.hrm.User;
import weaver.soa.workflow.request.RequestInfo;
import weaver.formmode.customjavacode.AbstractModeExpandJavaCode;
import java.util.Map;
public class ModeExpandTemplate extends AbstractModeExpandJavaCode {
@Override
public void doModeExpand(Map<String, Object> param) throws Exception {
// 当前用户
User user = (User) param.get("user");
int billid = -1; // 数据id
int modeid = -1; // 模块id
RequestInfo requestInfo = (RequestInfo) param.get("RequestInfo");
if (requestInfo != null) {
billid = Util.getIntValue(requestInfo.getRequestid());
modeid = Util.getIntValue(requestInfo.getWorkflowid());
if (billid > 0 && modeid > 0) {
RecordSet rs = new RecordSet();
//------请在下面编写业务逻辑代码------
}
}
}
}
```
> 配置:后端应用中心 -> 建模引擎 -> 模块
>
> 任选一个模块 -> 页面扩展 -> 任选一个扩展名称 -> 接口动作 -> 点击 `+` 号 -> 自定义接口动作
>
> ![1570158149458](asset/1570158149458.png)
#### 4.3.3 计划任务
> 通过配置自定义 `Java`接口的实现类,定时执行相应的代码
- 按照设定的时间定时执行任务,计划任务标识不能重复
- 计划任务类必须是类的全名,该类必须继承 `weaver.interfaces.schedule.BaseCronJob`类,重写方法`public void execute() {}`
- 时间格式按`Cron`表达式的定义
**参考代码**
```java
package com.engine.wcode.cron;
import weaver.interfaces.schedule.BaseCronJob;
public class CronTemplate extends BaseCronJob {
@Override
public void execute() {
//------请在下面编写业务逻辑代码------
}
}
```
> 配置:后端应用中心 -> 集成中心 -> 计划任务 -> 任务列表 -> 新建
>
> ![1570158310329](asset/1570158310329.png)
> **通过计划任务列表的每个计划任务的自定义按钮,可以对每个任务进行状态操作,具体使用如下所示**
>
> ![1570156694722](asset/1570156694722.png)
状态详解:
1. 启用: 计划任务将根据Cron表达式执行;
2. 禁用: 计划任务将不再执行,重启服务也不会再次执行;
3. 暂停: 针对计划任务进行停止,重启服务将恢复正常状态;
4. 恢复: 针对暂停状态的计划任务进行恢复,恢复后计划任务将继续执行;
5. 执行: 单次执行计划任务不影响Cron表达式周期执行;
6. 测试: 检查填写的计划任务类是否符合规范继承weaver.interfaces.schedule.BaseCronJob类,重写方法public void execute() {}
#### 4.3.4 自定义按钮接口
> 通过配置自定义`Java`类,判断自定义按钮在查询列表中是否显示
**参考代码**
```java
package com.engine.wcode.formmode.button;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weaver.formmode.interfaces.PopedomCommonAction;
public class CustomBtnShowTemplate implements PopedomCommonAction {
private Logger logger = LoggerFactory.getLogger(CustomBtnShowTemplate.class);
/**
* 得到是否显示操作项
* @param modeid 模块id
* @param customid 查询列表id
* @param uid 当前用户id
* @param billid 表单数据id
* @param buttonname 按钮名称
* @retrun "true"或者"false"true显示/false不显示
*/
@Override
public String getIsDisplayOperation(String modeid, String customid,String uid, String billid, String buttonname) {
logger.debug("modeId: {}", modeid);
logger.debug("customId: {}", customid);
logger.debug("uid: {}", uid);
logger.debug("billId: {}", billid);
logger.debug("buttonname: {}", buttonname);
return "false";
}
}
```
> 配置:后端应用中心 -> 建模引擎 -> 查询
>
> 任选一个查询列表 -> 自定义按钮 -> 右键 -> 新建
>
> ![1570158498273](asset/1570158498273.png)
**前端查询列表中由于接口中返回false则 ==受控按钮== 不显示**
![1570158575523](asset/1570158575523.png)
### 4.4 数据库操作
#### 4.4.1 CURD
> 使用 `weaver.conn.RecordSet`可以对数据库进行 `CURD` 等数据库操作
**参考代码:**
```java
RecordSet rs = new RecordSet();
String sql = "select loginid, lastname from hrmresource where id=?";
// 防止sql注入, objects 为动态参数
rs.executeQuery(sql, 2);
if (rs.next()) {
String loginid = rs.getString("loginid");
String lastname = rs.getString("lastname");
}
String updateSql = "update hrmresource lastname=? where id=?";
// 返回是否更新成功
boolean bool = rs.executeUpdate(sql, "孙悟空", 2);
```
#### 4.4.2 使用事务
> 使用`weaver.conn.RecordSetTrans`可以对数据库进行事务操作
**参考代码**
```java
RecordSetTrans rst = new RecordSetTrans();
// 开启事务
rst.setAutoCommit(false);
String sql = "update hrmresource lastname=? where id=?";
try {
int a = 1/0;
rst.executeUpdate(sql, "猪八戒", 2);
// 提交事务
rst.commit();
} catch (Exception e) {
e.printStackTrace();
// 事务回滚
rst.rollback();
}
```
#### 4.4.3 Mybatis的使用
> 目前E9没有成熟的使用Mybatis的方案以下是通过自己的研究给出一个不成熟的方案有`Bug`概不负责!
>
> 支持多数据源,数据源配置如下:
>
> 后端应用中心 -> 集成中心-> 数据源设置 -> 新建
>
> ![1570191781927](asset/1570191781927.png)
> **`xml` ⽅式开发**
**创建 `mapper` 接⼝**
```java
package com.engine.wcode.mapper;
import java.util.List;
import java.util.Map;
public interface TestMapper {
List<Map<String, String>> selectAll();
}
```
在 `../ecology/WEB-INF/config/mapper/ `路径下创建 `test.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.engine.wcode.mapper.TestMapper">
  <select id="selectAll" resultType="java.util.HashMap" >
select loginid, password, lastname from hrmresource
  </select>
</mapper>
```
> **注解⽅式开发**
>
> 与`xml`⽅式相⽐,使⽤注解开发的⽅便之处在于不⽤写`mapper.xml`⽂件
**创建 `mapper` 接⼝**
```java
package com.engine.wcode.mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
public interface HrmMapper {
@Select("select loginid, password, lastname from hrmresource")
List<Map<String, String>> selectHrm();
}
```
> 使用 `Mybatis` 操作数据库
- 获取 `Mapper`
```java
package com.wcode.db;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.SqlSession;
import weaver.conn.ConnectionPool;
import weaver.conn.WeaverConnection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static weaver.conn.mybatis.MyBatisFactory.sqlSessionFactory;
/**
* ⽅法参数不接受 null或 ""
* @author Y-Aron
* @create 2019/5/8
*/
public class MapperUtil {
private static final Map<Class, Object> cacheMapper = new ConcurrentHashMap<>(1);
private static final Map<String, SqlSession> cacheSqlSession = new ConcurrentHashMap<>(1);
public static <T> T getMapper(Class<T> clazz) {
return MapperUtil.getMapper(clazz, false);
}
public static <T> T getMapper(Class<T> clazz, boolean enableCache) {
return MapperUtil.getMapper(clazz, null, ExecutorType.SIMPLE, enableCache);
}
public static <T> T getMapper(Class<T> clazz, String dataSource) {
return MapperUtil.getMapper(clazz, dataSource, false);
}
public static <T> T getMapper(Class<T> clazz, String dataSource, boolean enableCache) {
return MapperUtil.getMapper(clazz, dataSource, ExecutorType.SIMPLE, enableCache);
}
public static <T> T getMapper(Class<T> clazz, String dataSource, ExecutorType executorType, boolean enableCache) {
String threadName = Thread.currentThread().getName();
SqlSession sqlSession = cacheSqlSession.get(threadName);
if (sqlSession == null) {
ConnectionPool pool = ConnectionPool.getInstance();
WeaverConnection connection = pool.getConnection(dataSource);
Configuration config = sqlSessionFactory.getConfiguration();
if (executorType == null) {
executorType = config.getDefaultExecutorType();
}
if (enableCache) {
config.setLocalCacheScope(LocalCacheScope.STATEMENT);
}
sqlSession = sqlSessionFactory.openSession(executorType, connection);
}
cacheSqlSession.put(threadName, sqlSession);
return MapperUtil.getMapper(clazz, sqlSession);
}
public static <T> T getMapper(Class<T> clazz, SqlSession sqlSession) {
if (cacheMapper.containsKey(clazz)) {
//noinspection unchecked
return (T) cacheMapper.get(clazz);
}
Configuration config = sqlSession.getConfiguration();
if (!config.hasMapper(clazz)) {
config.addMapper(clazz);
}
T mapper = sqlSession.getMapper(clazz);
cacheMapper.put(clazz, mapper);
return mapper;
}
private static SqlSession getCurrentSqlSession() {
if (cacheSqlSession.size() == 0) return null;
return cacheSqlSession.get(Thread.currentThread().getName());
}
}
```
- 数据库操作
```java
HrmMapper mapper = MapperUtil.getMapper(HrmMapper.class);
mapper.selectHrm();
```
### 4.5 缓存SDK
> 缓存基类:`Util_DataCache`
| 方法名称 | 方法作用 |
| -------------------------------------------------------- | --------------------------------------------------------- |
| getObjVal(String name) | 从所有缓存获取 缓存数据 (主要函数) |
| setObjVal(String name, Object value) | 设置所有缓存数据 (主要函数) |
| setObjVal(String name, Object value,int seconds) | 设置所有缓存数据 支持 超时自动消失 (主要函数) |
| containsKey(String name) | 判断该键名的所有缓存是否存在 |
| clearVal(String name) | 清除该键名的所有缓存 |
| setObjValWithEh(String name,Object value) | 设置本地缓存 ( 特定情况下使用) |
| getObjValWithEh(String name) | 获取本地缓存(特定情况下使用) |
| setObjValWithRedis(String name,Object value) | 设置Redis缓存 需要自己释放数据( 特定情况下使用) |
| setObjValWithRedis(String name,Object value,int seconds) | 单独设置Redis缓存 超时时间(s)后释放数据( 特定情况下使用) |
| getObjValWithRedis(String name) | 单独获取Redis缓存( 特定情况下使用) |
| containsKeylWithEh(String name) | 判断本地缓存是否存在该键名( 特定情况下使用) |
| clearValWithEh(String name) | 清除本地缓存( 特定情况下使用) |
| containsKeyWithRedis(String name) | 判断Redis上是否存在该键名( 特定情况下使用) |
| clearValWithRedis(String name) | 清除Redis缓存 |
> 检查页面
`chechRedis.jsp` 检查`Redis`环境的状态
`getRedis.jsp` 检查`DataKey`的数据
注意数据变更后必须再次执行`setObjVal`把数据推送到`Redis`
```java
import com.cloudstore.dev.api.util.Util_DataCache;
public Map<String,String> refreshDataFormDB() {
Map<String,String> map = new HashMap<String, String>();
Map<String,String> mapdb = getSystemIfo("y");
map.putAll(mapdb);
if(mapdb.size()>0) {
Util_DataCache.setObjVal(em_url, mapdb.get(em_url));
Util_DataCache.setObjVal(em_corpid, mapdb.get(em_corpid));
Util_DataCache.setObjVal(accesstoken,mapdb.get(accesstoken));
Util_DataCache.setObjVal(ec_id,mapdb.get(ec_id));
Util_DataCache.setObjVal(ec_url, mapdb.get(ec_url));
Util_DataCache.setObjVal(ec_name, mapdb.get(ec_name));
Util_DataCache.setObjVal(rsa_pub, mapdb.get(rsa_pub));
Util_DataCache.setObjVal(ec_version, mapdb.get(ec_version));
Util_DataCache.setObjVal(ec_iscluster, mapdb.get(ec_iscluster));
Util_DataCache.setObjVal(em_url, mapdb.get(em_url));
Util_DataCache.setObjVal(em_url_open, mapdb.get(em_url_open));
}
return map;
}
```
### 4.6 Rest API 接口
##### 4.6.1 流程表单数据接口
> https://www.evernote.com/l/AuMO8ps7HVpMlYkjCMpRC9xyc1VYIcbo1I0/
##### 4.6.2 流程待办列表接口
> https://www.evernote.com/l/AuM0l0TdGS9OvZuHd1eztup5KNwIgFokDTU/
##### 4.6.3 流程列表数据接口
> https://www.evernote.com/l/AuP8WpYtOmhHkYgnhs7aOKy_AL9kMACGWm4/
## 5. 对接异构系统
### 5.1 接口白名单配置
##### 5.1.1 `web.xml` 配置
> 在`web.xml`中配置(兼容以前的格式使用`weaver_session_filter.properties`后可以不添加参数配置)
>
> `SessionCloudFilter`过滤器修改一下`unchecksessionurl`路径主要功能超时自动跳转首页
- `checkurl` 头部验证路径 适用于 EM
- `uncheckurl` 头部验证放行路径 适用于 EM
- `unchecksessionurl`路径为不检查放行的路径(白名单)
```xml
<filter>
<filter-name>SessionCloudFilter</filter-name>
<filter-class>com.cloudstore.dev.api.service.SessionFilte</filter-class>
<init-param>
<param-name>checkurl</param-name>
<param-value>/api/hrm/emmanager;</param-value>
</init-param>
<init-param>
<param-name>uncheckurl</param-name>
<param-value>/api/ec/dev/app/getCheckSystemInfo;/api/ec/dev/app/emjoin;</param-value>
</init-param>
<init-param>
<param-name>unchecksessionurl</param-name>
<param-value></param-value>
</init-param>
</filter>
// ……(其他配置信息不动)
```
##### 5.1.2 `properties`配置
- `weaver_session_filter.properties`(系统)
外部配置文件,配置放行的路径地址。(启用了`weaver_session_filter.properties`会自动覆盖原`web.xml`中的路径)
- `weaver_session_filter_dev.properties`(用户自定义)
用户自定义配置文件,配置放行的路径地址(项目二开的路径建议放用户定义配置文件,升级时不被覆盖)会自动覆盖web.xml 中的路径地址。
```properties
# 头部验证路径 适用于 EM
checkurl=/api/hrm/emmanager;
# 头部验证放行路径 适用于 EM
uncheckurl=/api/ec/dev/app/getCheckSystemInfo;/api/ec/dev/app/emjoin;
# session验证放行 不检查放行的路径(白名单)
unchecksessionurl=/api/doc/upload/mobile/uploadFile;/api/doc/upload/mobile/shareFile;/weaver/weaver.file.FileDownload;/api/ec/dev/app/getCheckSystemInfo;/api/ec/dev/app/emjoin;/api/hrm/emmanager/;
```
### 5.2 Token认证
> [Token认证](http://wcode.store/#/./weaver/Token异构系统认证)
### 5.3 单点登录
> 单点登录直接通过URL打开 `E9` 页面
- 在 `ecology/WEB-INF/web.xml` 文件默认加上以下配置
```xml
<servlet>
<servlet-name>getToken</servlet-name>
<servlet-class>weaver.weaversso.GetToken</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getToken</servlet-name>
<url-pattern>ssologin/getToken</url-pattern>
</servlet-mapping>
<filter>
<filter-name>WeaverLoginFilter</filter-name>
<filter-class>weaver.weaversso.WeaverLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>WeaverLoginFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.html</url-pattern>
</filter-mapping>
```
- 在 `ecology/WEB-INF/prop/WeaverLoginClient.properties` 配置文件中加入以下参数
其中 `test` 指的是下文中的 `appid` 参数,`127.0.0.1` 指的是授权的 IP 地址
```properties
test=127.0.0.1
```
- 重启 `E9` 服务
- 使用 `Java` 测试
```java
package com.wcode.util;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author King
* @version 1.0.0
* @create 2019/11/5 16:17
*/
public class SsoUtils {
private static final String SSO_API = "/ssologin/getToken";
public static String getToken(String ip, int port, String appId, String loginId) throws IOException {
String url = ip + ":" + port + SSO_API;
HttpPost req = new HttpPost(url);
List<NameValuePair> params = new ArrayList<>(2);
params.add(new BasicNameValuePair("appid", appId));
params.add(new BasicNameValuePair("loginid", loginId));
req.setEntity(new UrlEncodedFormEntity(params, HTTP.DEF_CONTENT_CHARSET));
return HttpUtils.getString(req);
}
public static void main(String[] args) throws IOException {
String token = SsoUtils.getToken("http://127.0.0.1", 80, "test", "lcs");
System.out.println("ssoToken: " + token);
String url = "http://127.0.0.1/systeminfo/version.jsp";
HttpPost req = new HttpPost(url);
List<NameValuePair> params = new ArrayList<>(1);
params.add(new BasicNameValuePair("ssoToken", token));
req.setEntity(new UrlEncodedFormEntity(params));
System.out.println(HttpUtils.getString(req));
}
}
```
控制台输出内容如下:
![1572943794860](asset/1572943794860.png)
## 6. 其他开发
### 6.1 日志配置
> `Ecology`底层采用的是`log4j`日志框架, 可根据环境自定义日志配置
>
> `log4j`配置文件路径: `ecology/WEB-INF/log4jinit.properties`
- 打开配置文件, 在文件末尾加上如下代码, 然后重启`resin`服务
```properties
# appender
log4j.logger.debug=DEBUG,debug
log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
# 按日期滚动文件
log4j.appender.debug.DatePattern='_'yyyyMMdd'.log'
# 自定义日志文件路径
log4j.appender.debug.File=@debug/debug.log
log4j.appender.debug.layout=org.apache.log4j.PatternLayout
# 输出内容格式
log4j.appender.debug.layout.ConversionPattern=%d{HH:mm:ss.SSS}[%p] %l: %m%n
log4j.additivity.debug=false
```
- 代码中使用:
```java
// 获取自定义的 logger, 其中 debug为配置文件中 log4j.logger.debug中的debug
Logger logger = LoggerFactory.getLogger("debug");
// 支持占位符输出, 不定参数
logger.debug("debug级别消息: {}, {}", "参数1", "参数2");
logger.info("info级别消息!");
logger.warn("warn级别消息!");
logger.error("error级别消息!");
```
- 最终日志输出路径:
![1570369325637](asset/1570369325637.png)
**建议**: 将重要的日志以 `info` 级别以上输出, 开发的日志以 `debug` 级别输出, 这样的话再正式环境下只需修改配置, 即可实现只输出 `info` 级别的日志, 减少日志的输出!
```properties
# 将日志级别提升至INFO
log4j.logger.debug=INFO,debug
```
### 6.2 Redis 配置
- 本地下载`redis`
- `ecology/WEB-INF/prop/weaver_new_session.properties`
```properties
#1表示启用新的session方式其他值表示不启用
status=1
#用于调试的USERID
debugUsers=
#session id生成模式1表示自定义生成模式UUID模式其他值表示中间件自定义生成模式
useCustomSessionId=1
#同步频率设置(单位,秒)
#主表同步频率
SessionTableSync=2
#明细表同步频率
SessionItemTableSync=5
#超时扫描频率
SessionOverTime=300
#垃圾数据清理扫描频率
SessionLeak=3600
#启动模式,默认是数据库模式
#className=weaver.session.util.DBUtil
className=weaver.session.util.RedisSessionUtil
#redis ip
redisIp=127.0.0.1
#redis port
redisPort=6379
#redis password
redisPassword=123456
enableImmediatelySync=true
etoken=
```
### 6.3 lombok 配置
- 下载 `lombok.jar`
- `idea` 安装 `lombok plugin`
![1571882600348](asset/1571882600348.png)
- 代码中使用
```java
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class TestDTO {
private String name;
private String password;
}
```
### 6.4 Webservice 使用
> 使用`SoapUI` 解析 `webservice`接口
- SoapUI 介绍和使用方式可以参考博文: https://www.cnblogs.com/yatou-de/p/9273346.html
- 软件下载https://pan.baidu.com/s/1j36r6_NO_pcTfNDCwjLrog提取码: 8v9x
> 使用 `HttpClient` 以 `xml` 报文形式调用 `webservice` 服务
请求报文可以通过 `SoapUI` 查看
![1571909908394](asset/1571909908394.png)
```java
import com.wcode.util.WsUtils;
import java.io.IOException;
public class TestWebService {
public static void main(String[] args) throws IOException, DocumentException {
String url = "http://www.webxml.com.cn/webservices/ChinaTVprogramWebService.asmx";
String xml = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:web=\"http://WebXml.com.cn/\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <web:getTVstationDataSet>\n" +
" <web:theAreaID>18</web:theAreaID>\n" +
" </web:getTVstationDataSet>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
String resp = WsUtils.execute(url, xml);
Element rootElement = WsUtils.getRootElement(resp);
List<Element> elements = WsUtils.getElements(rootElement, "TvStation");
for (Element element : elements) {
Element e1 = element.element("tvStationID");
System.out.println(e1.getTextTrim());
Element e2 = element.element("tvStationName");
System.out.println(e2.getTextTrim());
}
}
}
```
### 6.5 SQL缓存注意事项
- 原则上禁止通过非程序渠道直接修改oa数据库数据。如果一定要修改请修改完数据后chrome浏览器访问/commcache/cacheMonitor.jsp界面点击重启加载配置。这样操作修改的数据可以及时生效。
- 如果存在第三方程序修改oa数据库的表则需要将会修改的表的名称以名称=名称的格式增加到例外配置文件ecology\WEB-INF\prop\cacheBackList.properties中然后再使用重启加载配置使其生效。
- 如果客户二次开发中存在非RecordSet系统标准sql操作类类修改数据库里的表也需要将该表名按注意事项2的方式操作将其加入例外配置文件中。
- 如果客户二次开发中还存在调用自己新建的存储过程视图函数方法。也需要将存储过程视图函数方法中涉及到的表名加入到例外配置文件中ecology\WEB-INF\prop\cacheBackList.properties。然后再使用重启加载配置使其生效。
- 集群环境如果开启sql缓存必须所有节点全部开启关闭也必须所有节点同时全部关闭否则必然存在缓存不同步问题
### 6.6 远程调试
> 该方案只能在测试或者开发环境中使用,不允许在生产环境中使用!**切记 **
配置`JVM`远程调试参数: `resin\conf\resin.properties`
```properties
# 初始配置
jvm_args : -Xmx5550m -Xms5550m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+DisableExplicitGC -javaagent:wagent.jar
# 修改后的配置
jvm_args : -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9090 -Xmx5550m -Xms5550m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+DisableExplicitGC -javaagent:wagent.jar
```
![1573458126874](asset/1573458126874.png)
总结:更为详细的配置过程参考: https://www.jianshu.com/p/4c657bf02cb3
### 6.7 容器化部署
> 具体部署资料: http://wcode.store/#/./docker/rancher