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

44 KiB
Raw Blame History

Ecology9 开发手册

在线地址: http://wcode.store/#/./weaver/Ecology9%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C

1. 环境搭建

1.1 Ecology安装和启动

  • 准备 ecology | resin | jdk1.8[可直接使用已安装的]
  • 修改resin配置 resin/config/resin.conf
// 修改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_homejdk的路径,可以是相对路径也可以是绝对路径

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/propweb配置文件目录
  • ecology/WEB-INF/lib:系统jar包路径

1.3 数据库配置

如果数据库与Ecology不在同一台服务器上则可以修改数据库配置文件中的数据库配置。

数据库配置文件路径:ecology/WEB-INF/prop/weaver.properties

# sqlServer
DriverClasses = com.microsoft.sqlserver.jdbc.SQLServerDriver
ecology.url = jdbc:sqlserver://host:port;DatabaseName=dbname
ecology.user = username
ecology.password = password
# 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

  • 所有接口统一封装在全局对象window.WfForm
  • 表单字段相关操作不推荐使用jQuery禁止原生JS直接操作DOM结构
  • 在开发过程中推荐都使用API接口操作由产品统一运维同时使用API才能完整的兼容移动终端

3.2 建模开发

3.2.1 布局代码块

建模布局代码块使用上与流程表单的代码块基本一致区别在于接口的SDK不同建模表单的所有接口统一封装在全局对象 window.ModeForm中。

建模前端接口API

3.2.2 自定义按钮

后端应用中心 -> 建模引擎 -> 查询

任选一个查询页面 -> 自定义按钮 -> 右键 -> 新建

1570360554483

  • 方法体中存在多行代码时,每个语句必须以;结尾;否则会报错!

  • params的值等于 field1+field2+field3 这个值是一个字符串

  • id指的是数据ID

前端显示

1570109435919

  • 配置ecode使用

新建前置文件 index.js并将方法挂到全局对象 window.g

1570109735133

新建自定义按钮

1570109991787

1570110063242

3.2.3 页面扩展

后端应用中心 -> 建模引擎 -> 模块

任选一个模块 -> 页面扩展 -> 右键 -> 新建

1570110626080

  • 扩展用途:卡片页面、查询列表(批量操作)、卡片页面和查询列表
    • 卡片页面:可以设置页面扩展显示在卡片信息页面,可以选择在新建页面、编辑页面、查看页面显示页面扩展。
    • 查询列表(批量操作):设置在查询列表时,则会在引用该模块的查询列表的批量操作中显示页面扩展项,在批量操作中勾选后会在前台列表中显示对应的页面扩展项。
    • 卡片页面和查询列表:可以设置页面扩展项既显示在对应的卡片页面又显示在查询列表(批量操作)中。
  • javascript:test(): 该方法可以在 建模引擎 -> 查询 -> 该模块的查询列表 -> 编辑代码块 中定义

1570110945435

1570110988895

前端按钮测试如下

1570110886190

  • 页面扩展同样可以配置 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 动态注入代码

通过前置加载动态注册代码,完全脱离代码块的实现方式。非官方的方案,使用中有任何问题概不负责!

官方用法请参考:全局流程代码块整合

注意:Ecode部分版本不能使用更新至最新版本即可

  • 打开ecode页面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 的前置文件,根据不同场景加入对应的代码

  • 流程节点使用
Wcode.runScript({
	mode: Wcode.WF_TYPE,
	id: '4',
	appId: '${appId}'
})
  • 建模布局使用
Wcode.runScript({
	mode: Wcode.MODE_TYPE,
	id: '1',
	appId: '${appId}',
	type: Wcode.SHOW_TEMPLATE,
})
  • 查询列表使用
Wcode.runScript({
	mode: Wcode.LIST_TYPE,
	id: '1',
	appId: '${appId}'
})

4. 后端开发

代码案例:E9Demo

工具类 wcode 代码: https://github.com/Y-Aron/wcode

4.1 Java项目环境搭建

4.1.1 web.xml 部分配置

API 接口的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/*

<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后端开发指南

4.1.3 Java项目结构

一般情况下遵循以下项目结构进行后端开发

其中,com.api.wcodecom.engine.wcode中的 wcode为自定义包名,不与已有的包重复即可。

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)

参考代码如下

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

1570158010000

4.3.2 页面扩展接口

页面扩展 -> 接口动作 -> 自定义接口动作

执行页面扩展的后续操作,通过配置自定义 Java接口动作类实现。

接口动作类文件必须是类全名。该类必须继承 weaver.formmode.customjavacode.AbstractModeExpandJavaCode 方法 public void doModeExpand(Map<String, Object> param)

参考代码

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

4.3.3 计划任务

通过配置自定义 Java接口的实现类,定时执行相应的代码

  • 按照设定的时间定时执行任务,计划任务标识不能重复
  • 计划任务类必须是类的全名,该类必须继承 weaver.interfaces.schedule.BaseCronJob类,重写方法public void execute() {}
  • 时间格式按Cron表达式的定义

参考代码

package com.engine.wcode.cron;

import weaver.interfaces.schedule.BaseCronJob;

public class CronTemplate extends BaseCronJob {

    @Override
    public void execute() {
    	//------请在下面编写业务逻辑代码------
    }
}

配置:后端应用中心 -> 集成中心 -> 计划任务 -> 任务列表 -> 新建

1570158310329

通过计划任务列表的每个计划任务的自定义按钮,可以对每个任务进行状态操作,具体使用如下所示

1570156694722

状态详解:

  1. 启用: 计划任务将根据Cron表达式执行;

  2. 禁用: 计划任务将不再执行,重启服务也不会再次执行;

  3. 暂停: 针对计划任务进行停止,重启服务将恢复正常状态;

  4. 恢复: 针对暂停状态的计划任务进行恢复,恢复后计划任务将继续执行;

  5. 执行: 单次执行计划任务不影响Cron表达式周期执行;

  6. 测试: 检查填写的计划任务类是否符合规范继承weaver.interfaces.schedule.BaseCronJob类,重写方法public void execute() {}

4.3.4 自定义按钮接口

通过配置自定义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

前端查询列表中由于接口中返回false则 ==受控按钮== 不显示

1570158575523

4.4 数据库操作

4.4.1 CURD

使用 weaver.conn.RecordSet可以对数据库进行 CURD 等数据库操作

参考代码:

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可以对数据库进行事务操作

参考代码

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

xml ⽅式开发

创建 mapper 接⼝

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 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 接⼝

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
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());
    }
}
  • 数据库操作
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

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路径为不检查放行的路径(白名单)

<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 中的路径地址。

# 头部验证路径 适用于 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认证

5.3 单点登录

单点登录直接通过URL打开 E9 页面

  • ecology/WEB-INF/web.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 地址

test=127.0.0.1
  • 重启 E9 服务

  • 使用 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

6. 其他开发

6.1 日志配置

Ecology底层采用的是log4j日志框架, 可根据环境自定义日志配置

log4j配置文件路径: ecology/WEB-INF/log4jinit.properties

  • 打开配置文件, 在文件末尾加上如下代码, 然后重启resin服务
# 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
  • 代码中使用:
// 获取自定义的 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

建议: 将重要的日志以 info 级别以上输出, 开发的日志以 debug 级别输出, 这样的话再正式环境下只需修改配置, 即可实现只输出 info 级别的日志, 减少日志的输出!

# 将日志级别提升至INFO
log4j.logger.debug=INFO,debug

6.2 Redis 配置

  • 本地下载redis
  • ecology/WEB-INF/prop/weaver_new_session.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

  • 代码中使用
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接口

使用 HttpClientxml 报文形式调用 webservice 服务

请求报文可以通过 SoapUI 查看

1571909908394

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

# 初始配置
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

总结:更为详细的配置过程参考: https://www.jianshu.com/p/4c657bf02cb3

6.7 容器化部署

具体部署资料: http://wcode.store/#/./docker/rancher