e9 后端无侵入二次开发

发布于 2026-05-25

参考:E9BackendDdevelopmentGuide.pdf

E9BackendDdevelopmentGuide.pdf

接口拦截

什么是接口拦截?

就是允许前端在调用后端接口时,在接口执行前或执行后进行拦截,可获取接口的传入参数、返回结果等数据。

创建接口拦截类

接口类路径必需在 com.*.impl 中,不然不会生效。

类必需有`@WeaIocReplaceComponent`注解,注解中可指定名称,不指定也可以。


@WeaIocReplaceComponent("DocSearchIntercept")
public class DocSearchIntercept {
}

前置拦截

前置拦截是在接口执行之前拦截,此时接口的业务还未执行。前置拦截的主要作用是获取接口请求参数,但用途并不是很大,后置拦截也可以获取请求参数,一般都使用后置拦截。

在拦截类内创建一个方法,方法需加上`@WeaReplaceBefore`注解,注解中的3个属性必填,value 为需要拦截的接口路径,必需以 /api 开头,order 为拦截顺序,如果此接口有多个拦截,则按此顺序进行拦截,description 填写拦截说明。


@WeaIocReplaceComponent
public class ApiInterceptDemo {
    
    @WeaReplaceBefore(value = "/api/ec/dev/table/datas", order = 1, description = "表格数据前置拦截")
    public void before(WeaBeforeReplaceParam param) {
        // 获取接口参数
        Map<String, Object> params = param.getParamMap();
    }
}

后置拦截

后置拦截就是在接口执行之后进行拦截,主要用途是修改接口的返回数据,和在接口执行之后,将接口的请求参数推送到第三方系统,比如在人力资源人员修改时调用接口,当接口执行后就需要将修改后的人员信息同步到第三方系统。

后置拦截需要在拦截类下创建一个方法,并在方法添加`@WeaReplaceAfter`注解,和前置拦截一样,这3个属性也是必填。

在后置拦截中我们可以对接口的返回数据进行修改,例如过滤不想要的数据。


@WeaIocReplaceComponent
public class ApiInterceptDemo {
    
    @WeaReplaceAfter(value = "/api/ec/dev/table/datas", order = 1, description = "表格数据后置拦截")
    public String after(WeaAfterReplaceParam param) {
        // 获取接口参数
        Map<String, Object> params = param.getParamMap();
        String dataKey = (String) params.get("dataKey");
    
        // 可对接口执行结果进行更改,结果类型为 json
        String result = param.getData();
    
        // 返回接口执行结果
        return result;
    }
    
}

接口拦截示例

过滤会议

使用场景:会议室使用页面需要只保留会议室为某类型的会议

图片1

对会议页面中的表格数据接口进行后置拦截,roomTypes 为传入的接口参数,指定需要保留的会议室类型


/**
 * 拦截表格数据获取接口,过滤会议数据,只保留指定会议室类型的会议。在 {@link #getRoomReportList} 方法中在 sql where 中添加了标记,
 * 根据此标记判断是否进行数据过滤。
 */
@WeaReplaceAfter(value = "/api/ec/dev/table/datas", order = 1,
        description = "会议过滤,根据传入的会议室类型参数,根据会议室类型过滤会议,只保留指定会议室类型的会议")
public String datas(WeaAfterReplaceParam param) {
    Map<String, Object> params = param.getParamMap();
    // 从接口参数中获取会议室类型
    String roomTypes = Util.null2String(params.
            get(RoomReportDataCmdProxyWithMeetingFilter.ROOM_TYPE_PARAM_NAME));
    // 接口执行结果
    String data = param.getData();
    // 如果没有会议室类型,或者该接口不是需要拦截的接口,返回原数据
    if (roomTypes.isEmpty() || !sqlWhereContainsFlag(params)) {
        return data;
    }
    
    // 对接口返回数据进行处理,过滤会议数据
    JSONObject dataJson = JSONObject.parseObject(data);
    if (dataJson.containsKey("datas")) {
        JSONArray datas = dataJson.getJSONArray("datas");
        // 过滤会议数据
        datas = filterMeetings(datas, Util.splitString2List(roomTypes, ","));
        dataJson.put("datas", datas);
        data = dataJson.toJSONString();
    }
    return data;
}
    
private JSONArray filterMeetings(JSONArray datas, List<String> roomTypes) {
    List<Object> newData = datas.stream().filter(i -> {
        JSONObject meeting = (JSONObject) i;
        String address = meeting.getString("address");
        if (StrUtil.isEmpty(address)) {
            return false;
        }
        List<String> roomIds = Util.splitString2List(address, ",");
        return roomsContainsAnyTypes(roomIds, roomTypes);
    }).collect(Collectors.toList());
    return new JSONArray(newData);
}java

添加流程菜单

示例:


package com.engine.customization.demo.service.impl;
    
import com.alibaba.fastjson.JSONObject;
import com.engine.common.util.ParamUtil;
import com.engine.workflow.constant.menu.SystemMenuType;
import com.engine.workflow.constant.requestForm.RequestMenuType;
import com.engine.workflow.entity.requestForm.RightMenu;
import com.weaverboot.frame.ioc.anno.classAnno.WeaIocReplaceComponent;
import com.weaverboot.frame.ioc.anno.methodAnno.WeaReplaceAfter;
import com.weaverboot.frame.ioc.anno.methodAnno.WeaReplaceBefore;
import com.weaverboot.frame.ioc.handler.replace.weaReplaceParam.impl.WeaAfterReplaceParam;
import com.weaverboot.frame.ioc.handler.replace.weaReplaceParam.impl.WeaBeforeReplaceParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weaver.hrm.HrmUserVarify;
import weaver.hrm.User;
import weaver.systeminfo.SystemEnv;
    
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
    
/**
 * <Description> <br>
 * @author han.mengyu <br>
 * @version 1.0 <br>
 * @createDate 2021/12/29 <br>
 * @see com.engine.customization.demo <br>
 */
@WeaIocReplaceComponent("demoService") //如不标注名称,则按类的全路径注入
public class GetRightMenuProxy {
    private static final Logger LOGGER = LoggerFactory.getLogger("customlog");
    
    //这是接口前置方法,这个方法会在接口执行前执行
    //前值方法必须用@WeaReplaceBefore,这里面有两个参数,第一个叫value,是你的api地址
    //第二个参数叫order,如果你有很多方法拦截的是一个api,那么这个就决定了执行顺序
    //前置方法的参数为WeaBeforeReplaceParam 这个类,里面有四个参数,request,response,请求参数的map,api的地址
    @WeaReplaceBefore(value = "/api/workflow/reqform/rightMenu",order = 1,description = "rightMenu接口前置方法")
    public void rightMenuBefore(WeaBeforeReplaceParam beforeReplaceParam){
        Map paramMap = beforeReplaceParam.getParamMap();
        paramMap.putIfAbsent("iocTestKey", "1");
        LOGGER.debug("com.engine.customization.demo.service.impl.GetRightMenuProxy.rightMenuBefore start...");
    }
    
    @WeaReplaceBefore(value = "/api/workflow/reqform/rightMenu",order = 2,description = "/api/workflow/reqform接口前置方法")
    public void reqFormBefore(WeaBeforeReplaceParam beforeReplaceParam){
        String apiUrl = beforeReplaceParam.getApiUrl();
        LOGGER.debug("com.engine.customization.demo.service.impl.GetRightMenuProxy.reqFormBefore start...  apiUrl = [{}]", apiUrl);
    }
    
    //这个是接口后置方法,大概的用法跟前置方法差不多,稍有差别
    //注解名称为WeaReplaceAfter
    //返回类型必须为String
    //参数叫WeaAfterReplaceParam,这个类前四个参数跟前置方法的那个相同,不同的是多了一个叫data的String,这个是那个接口执行完返回的报文
    //你可以对那个报文进行操作,然后在这个方法里return回去
    @WeaReplaceAfter(value = "/api/workflow/reqform/rightMenu",order = 1,description = "rightMenu接口ioc方式添加按钮")
    public String rightMenuAfter(WeaAfterReplaceParam weaAfterReplaceParam){
        HttpServletRequest request = weaAfterReplaceParam.getRequest();
        HttpServletResponse response = weaAfterReplaceParam.getResponse();
    
        // 获取用户id
        User user = HrmUserVarify.getUser(request, response);
        // 获取请求参数
        Map<String, Object> requestMap = ParamUtil.request2Map(request);
        String data = weaAfterReplaceParam.getData();//这个就是接口执行完的报文
        LOGGER.debug("com.engine.customization.demo.service.impl.GetRightMenuProxy.rightMenuAfter start...  data = [{}]", data);
    
        Map<String, Object> parasedData = JSONObject.parseObject(data, Map.class);
        List<RightMenu> rightMenus = (List<RightMenu>) parasedData.getOrDefault("rightMenus", new ArrayList<>());
        RightMenu rightMenu = new RightMenu(SystemEnv.getHtmlLabelName(-81472, user.getLanguage()),
                RequestMenuType.BTN_CUSTOMIZE,
                "window.doProxyCmd(\"iocBtn\")",
                "icon-coms-daiban", 101, SystemMenuType.CUSTOMIZE);
    
        rightMenus.add(rightMenu);
        parasedData.put("rightMenus", rightMenus);
        return JSONObject.toJSONString(parasedData);
    }
    
}

注意问题

如果接口路径api前没有“/”可能会导致接口不能拦截

图片2

Command 类代理

什么是 Command 类?Command 类是业务代码执行的类,例如接口一般分为3层,第一层是接口定义层,也就是 Controller 层,然后再到 Service 层,然后到 Command 层,Command 就是接口执行业务代码的地方,许多 Ecology 标准的接口都会在 Command 层执行业务逻辑。

可对 Command 类进行代理,如在 Command 类执行前或执行后进行业务处理。

类路径必需位于`com.customization`路径下

创建代理类需要使用`@CommandDynamicProxy`注解,target 填写被代理 Command 类的 class ,desc 填写代理说明,类需要继承`AbstractCommandProxy<>`并实现方法,`AbstractCommandProxy<>`中的范型类型为被代理类的 execute 方法的返回类型。

要获取被代理类的实例,可通过强转 execute 方法的参数获取


// 获取被代理类
GetRoomReportDataCmd cmd = (GetRoomReportDataCmd) command;

示例:


@CommandDynamicProxy(target = GetRoomReportDataCmd.class, desc = "会议室数据接口动态代理,过滤会议,只保留指定会议室类型的会议")
public class CmdProxyDemo extends AbstractCommandProxy<Map<String, Object>> {
    private final IntegrationLog log = new IntegrationLog(CmdProxyDemo.class);
    
    @Override
    public Map<String, Object> execute(Command<Map<String, Object>> command) {
        // 获取被代理类
        GetRoomReportDataCmd cmd = (GetRoomReportDataCmd) command;
        // 获取请求参数,可获取到接口请求参数(有时不行)
        Map<String, Object> params = cmd.getParams();
        // 执行被代理类,获取执行结果
        Map<String, Object> result = nextExecute(cmd);
        
        try {
            // 对执行结果进行处理
        } catch (Exception e) {
            log.error("过滤会议发生异常", e);
        }
        return result;
    }
    
}

如何找到接口对应的 Cmd 类?

后端开发知识

可按照链接文档的"根据接口路径找到对应的类"进行查找

示例-会议数据过滤

`GetRoomReportDataCmd`类为会议数据的业务执行 cmd 类,可通过代理该 cmd 类,修改 cmd 类的执行结果,也就是会议数据,进行会议数据过滤,只保留某会议室类型的会议。


@CommandDynamicProxy(target = GetRoomReportDataCmd.class, desc = "会议室数据接口动态代理,过滤会议,只保留指定会议室类型的会议")
public class RoomReportDataCmdProxyWithMeetingFilter extends AbstractCommandProxy<Map<String, Object>> {
    public static final String ROOM_TYPE_PARAM_NAME = "meetingRoomType";
    private final IntegrationLog log = new IntegrationLog(RoomReportDataCmdProxyWithMeetingFilter.class);
    
    private final RecordSet recordSet;
    private final MeetingFilterHelper meetingFilterHelper;
    
    public RoomReportDataCmdProxyWithMeetingFilter(RecordSet recordSet, MeetingFilterHelper meetingFilterHelper) {
        this.recordSet = recordSet;
        this.meetingFilterHelper = meetingFilterHelper;
    }
    
    public RoomReportDataCmdProxyWithMeetingFilter() {
        this.recordSet = new RecordSet();
        this.meetingFilterHelper = new MeetingFilterHelper(recordSet);
    }
    
    @Override
    public Map<String, Object> execute(Command<Map<String, Object>> command) {
        GetRoomReportDataCmd cmd = (GetRoomReportDataCmd) command;
        Map<String, Object> result = nextExecute(cmd);
    
        try {
            Map<String, Object> params = cmd.getParams();
            // 该参数为前端页面传入的参数,根据该参数指定需要过滤的会议类型
            String meetingRooms = Util.null2String(params.get(ROOM_TYPE_PARAM_NAME));
            if (meetingRooms.isEmpty()) {
                return result;
            }
            List<String> meetingRoomList = Util.splitString2List(meetingRooms, ",");
            if (meetingRoomList.isEmpty()) {
                log.info("传入的会议室类型为空");
                return result;
            }
    
            if (!result.containsKey("datas")) {
                log.info("不存在 datas");
                return result;
            }
    
            log.info("启用会议过滤");
            changeData(result, meetingRoomList);
        } catch (Exception e) {
            log.error("过滤会议发生异常", e);
        }
        return result;
    }
    
    private void changeData(Map<String, Object> result, List<String> roomTypeList) {
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> datas = (List<Map<String, Object>>) result.get("datas");
        filterMeetings(roomTypeList, datas);
        result.put("datas", datas);
    }
    
    /**
     * 过滤数据中的会议,数据中的会议室的会议室类型需要在传入的会议室类型中,否则将该会议室的会议数据清空
     */
    private void filterMeetings(List<String> roomTypeList, List<Map<String, Object>> datas) {
        datas.forEach(room -> {
            String roomId = Util.null2String(room.get("roomid"));
            Optional<Integer> roomTypeOp = meetingFilterHelper.getRoomType(Integer.parseInt(roomId));
            if (!roomTypeOp.isPresent() || !roomTypeList.contains(roomTypeOp.get().toString())) {
                room.put("info", new ArrayList<>());
            }
        });
    }
    
}

service 方法代理

我已经验证,无法对自行二开的 service 类进行代理。

相关问题

cmd代理类不生效问题

路径不对,cmd代理类必需位于以下目录

图片3