树形下拉组件数据查询覆写

一、背景与问题

Oinone 的树形下拉组件(TreeSelect)和树视图(Tree)在查询数据时,最终调用的是树节点所配置模型的数据管理器函数:

树接口 调用的数据管理器函数 场景
fetchAll queryListByWrapper 一次性全量加载
fetchChildren queryPage 按层级懒加载(默认)
queryKeywords4Tree queryListByWrapper 关键字搜索
reverselyQuery queryListByWrapper 已选数据回填

官方文档原文:「所有树查询接口都会调用模型对应的数据管理器函数,可以通过重写的方式修改其查询结果。」

直接覆写的风险

如果直接在原模型(如 business.PamirsDepartment)上覆写 queryPage:

// ⚠️ 危险写法:会影响所有使用 PamirsDepartment.queryPage 的场景
@Component
@Model.model(PamirsDepartment.MODEL_MODEL)
public class DeptQueryAction {

    @Function.Advanced(type = FunctionTypeEnum.QUERY, category = FunctionCategoryEnum.QUERY_PAGE)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public Pagination<PamirsDepartment> queryPage(Pagination<PamirsDepartment> page, IWrapper<PamirsDepartment> queryWrapper) {
        // 追加过滤条件...
        return new PamirsDepartment().queryPage(page, queryWrapper);
    }
}

该覆写会全局生效——不仅是树形下拉,所有调用 PamirsDepartment 的 queryPage 的地方(组织架构页面、人员管理列表、权限分配、框架内部关联查询等)都会受到影响,容易引发难以排查的副作用。

二、推荐方案:代理模型(PROXY)隔离

核心思路

  1. 创建一个继承原模型的代理模型(ModelTypeEnum.PROXY),拥有独立的模型命名空间
  2. 在代理模型中重新声明自关联字段(如 parent),将关联目标指向代理模型自身
  3. 在代理模型的 Action 中覆写数据管理器函数,追加树形场景专属的过滤逻辑
  4. 树形下拉的视图 DSL 中,<node> 的 model 指向代理模型

代理模型与原模型共享同一张数据表,但函数注册在各自独立的命名空间下,互不干扰。

框架官方示例

Oinone 框架自身就采用了这一模式:ResourceRegionProxyModel 继承 ResourceRegion,用于地区页面的代理展示,其扩展点 ResourceRegionProxyModelExtPoint 中的 queryPageAfter 只影响代理模型自身的查询。

三、完整实现步骤(以 PamirsDepartment 为例)

场景假设

业务需求:某个表单页面的「部门」树形下拉只需要展示当前公司(companyCode == 当前用户公司)且启用状态(dataStatus == ENABLED)的部门,而系统中其他使用 PamirsDepartment 的地方(组织架构、权限管理等)不受影响。

步骤 1:定义代理模型

package com.example.demo.model;

import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import pro.shushi.pamirs.business.api.model.PamirsDepartment;

import java.util.List;

/**
 * 部门树形下拉专用代理模型
 * <p>
 * 继承 PamirsDepartment,共享同一张数据表,
 * 但拥有独立的函数命名空间,覆写 queryPage 不影响原模型。
 */
@Model.model(DeptTreeProxy.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY)
@Model(displayName = "部门(树选择)", labelFields = "name")
public class DeptTreeProxy extends PamirsDepartment {

    public static final String MODEL_MODEL = "demo.DeptTreeProxy";

    /**
     * 【必须重新声明】上级部门(自关联字段)
     * <p>
     * 基类 PamirsDepartment 中 parent 的类型是 PamirsDepartment,
     * 代理模型必须将其覆盖为指向自身(DeptTreeProxy),
     * 否则设计器/树组件无法在代理模型命名空间内构成完整的自关联关系,
     * 导致树形下拉无法正确展开层级。
     */
    @Field.many2one
    @Field.Relation(relationFields = {"parentCode"}, referenceFields = {"code"})
    @Field(displayName = "上级部门")
    private DeptTreeProxy parent;

    /**
     * 【必须重新声明】下级部门(自关联 O2M 字段)
     * <p>
     * 与 parent 对应,保持自关联关系的双向完整性。
     */
    @Field.one2many
    @Field.Relation(relationFields = {"code"}, referenceFields = {"parentCode"})
    @Field(displayName = "下级部门")
    private List<DeptTreeProxy> childList;
}

要点说明:

  • ModelTypeEnum.PROXY:标记为代理模型,不会新建数据表,底层复用父模型的表
  • MODEL_MODEL 使用项目自己的命名空间(如 demo.),与框架的 business. 隔离

⚠️ 关键陷阱:自关联字段必须重新声明

基类 PamirsDepartment 中 parent 字段的类型是 PamirsDepartment,其关联关系指向 business.PamirsDepartment。
如果代理模型不重新声明 parent,则树组件在解析 selfReferences="parent" 时,
发现该字段的关联目标不是当前代理模型(demo.DeptTreeProxy),而是父模型(business.PamirsDepartment),
导致设计器页面和树形下拉无法构成完整的自关联树结构,表现为:树只有一层、无法展开子节点、或报错。

因此,所有构成树形结构的自关联字段(M2O 的 parent 和 O2M 的 childList)都必须在代理模型中重新声明,将类型指向代理模型自身。

步骤 2:在代理模型 Action 中覆写数据管理器函数

package com.example.demo.action;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.framework.connectors.data.sql.query.LambdaQueryWrapper;
import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionCategoryEnum;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.core.common.enmu.DataStatusEnum;
import com.example.demo.model.DeptTreeProxy;

import java.util.List;

/**
 * 部门树形下拉代理模型的查询动作
 * <p>
 * 覆写 queryPage 和 queryListByWrapper,
 * 仅对树形下拉组件的查询生效,不影响 PamirsDepartment 原模型。
 */
@Component
@Model.model(DeptTreeProxy.MODEL_MODEL)
public class DeptTreeProxyAction {

    /**
     * 覆写分页查询 —— 树组件懒加载(fetchChildren)时调用
     *
     * @param page         分页参数
     * @param queryWrapper 框架自动构建的查询条件(已包含树层级关联条件)
     * @return 过滤后的分页结果
     */
    @Function.Advanced(type = FunctionTypeEnum.QUERY, category = FunctionCategoryEnum.QUERY_PAGE)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public Pagination<DeptTreeProxy> queryPage(Pagination<DeptTreeProxy> page,
                                               QueryWrapper<DeptTreeProxy> queryWrapper) {
        // 将 QueryWrapper 转为 LambdaQueryWrapper,通过方法引用指定字段,避免硬编码列名
        LambdaQueryWrapper<DeptTreeProxy> wrapper = queryWrapper.lambda();
        // 追加业务过滤条件:只展示启用状态的部门
        wrapper.eq(DeptTreeProxy::getDataStatus, DataStatusEnum.ENABLED);
        // 追加业务过滤条件:只展示当前用户所属公司的部门
        String companyCode = PamirsSession.getContext().getCompanyCode();
        wrapper.eq(DeptTreeProxy::getCompanyCode, companyCode);
        // 调用代理模型的默认数据管理器执行查询
        return new DeptTreeProxy().queryPage(page, wrapper);
    }

    /**
     * 覆写列表查询 —— 树组件全量加载(fetchAll)和搜索(queryKeywords4Tree)时调用
     *
     * @param queryWrapper 框架自动构建的查询条件
     * @return 过滤后的列表结果
     */
    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryListByWrapper)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public List<DeptTreeProxy> queryListByWrapper(QueryWrapper<DeptTreeProxy> queryWrapper) {
        // 将 QueryWrapper 转为 LambdaQueryWrapper
        LambdaQueryWrapper<DeptTreeProxy> wrapper = queryWrapper.lambda();
        // 追加与 queryPage 相同的业务过滤条件
        wrapper.eq(DeptTreeProxy::getDataStatus, DataStatusEnum.ENABLED);
        String companyCode = PamirsSession.getContext().getCompanyCode();
        wrapper.eq(DeptTreeProxy::getCompanyCode, companyCode);
        return new DeptTreeProxy().queryListByWrapper(wrapper);
    }
}

要点说明:

  • @Function.fun(FunctionConstants.queryPage):将方法注册为代理模型命名空间下的 queryPage 数据管理器函数,覆盖默认实现
  • 树组件内部通过 Models.data().queryPage(pagination, queryWrapper) 调用时,pagination.model 已被设置为节点配置的模型编码,因此会自动路由到代理模型的覆写版本
  • 通过 queryWrapper.lambda() 转为 LambdaQueryWrapper,使用 DeptTreeProxy::getDataStatus 等方法引用指定字段,避免硬编码数据库列名,字段重命名时编译期即可发现错误
  • 方法体内调用 new DeptTreeProxy().queryPage(page, wrapper) 执行实际的数据库查询

步骤 3:配置树形下拉视图 DSL

在表单视图中,将 TreeSelect 字段的 <node> 指向代理模型:

<view model="业务模型编码" type="form" name="xxx_form_view">
  <template slot="form">
    <!-- 树形下拉字段:node 的 model 指向代理模型 -->
    <field data="department" label="部门" widget="TreeSelect" span="1">
      <nodes>
        <node label="activeRecord.name"
              model="demo.DeptTreeProxy"
              selfReferences="parent"
              searchFields="name" />
      </nodes>
    </field>
  </template>
</view>

DSL 关键属性:

属性 说明
model 树节点数据来源模型 —— 指向代理模型,这是隔离生效的关键
selfReferences 自关联字段名(parent),表示该模型通过 M2O 字段 parent 构成树
label 节点显示文本的表达式,activeRecord.name 表示取记录的 name 字段
searchFields 搜索时匹配的字段列表
filter 可选,RSQL 语法的静态过滤条件(简单场景可替代后端覆写)

步骤 4(可选):多级树节点配置

如果树形下拉需要「公司 → 部门」两级结构:

<field data="department" label="部门" widget="TreeSelect" span="1">
  <nodes>
    <!-- 第一级:公司 -->
    <node label="activeRecord.name" model="business.PamirsCompany" />
    <!-- 第二级:部门,通过 company 字段关联上级,parent 构成部门内自关联 -->
    <node label="activeRecord.name"
          model="demo.DeptTreeProxy"
          references="company"
          selfReferences="parent"
          searchFields="name" />
  </nodes>
</field>

四、方案对比

维度 直接覆写原模型 代理模型隔离(推荐) DSL filter 属性
影响范围 全局所有使用该模型查询的场景 仅树组件(指向代理模型的视图) 仅该视图的树节点
灵活性 高(可写任意 Java 逻辑) 高(可写任意 Java 逻辑) 低(仅支持 RSQL 静态条件)
副作用风险 高 无 无
适用场景 确实需要全局修改查询行为 特定页面/组件需要定制数据 简单的静态过滤

五、注意事项

  1. 自关联字段必须在代理模型中重新声明:基类的 parent(M2O)和 childList(O2M)字段的关联目标指向基类模型,代理模型必须用自身类型重新声明这些字段,否则设计器无法识别代理模型内的完整树形关联关系,树形下拉将无法正确展开
  2. queryPage 和 queryListByWrapper 都要覆写:树组件的懒加载走 queryPage,全量加载和搜索走 queryListByWrapper,只覆写一个会导致部分场景过滤失效
  3. 代理模型不建新表:ModelTypeEnum.PROXY 类型的模型复用父模型的物理表,无需担心数据同步问题
  4. 使用 LambdaQueryWrapper + 方法引用指定字段:通过 queryWrapper.lambda() 转换后,使用 DeptTreeProxy::getDataStatus 等方法引用指定字段,避免硬编码数据库列名字符串,字段重命名时编译期即可发现错误
  5. 不要在覆写方法中调用 Models.data().queryPage():这会再次路由到函数注册中心,可能形成递归调用;应使用 new XxxProxy().queryPage(page, wrapper) 直接执行
  6. 简单过滤优先考虑 DSL filter:如果过滤条件是静态的(如 dataStatus == ENABLED),直接在 <node filter="..."> 中配置 RSQL 即可,无需写代码

Oinone社区 作者:nation原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/25596.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (1)
nation's avatarnation数式员工
Previous 2026年7月2日 pm4:53
Next 2024年5月23日 am9:23

相关推荐

  • 工作流审核撤回/回退/拒绝/同意/反悔钩子使用

    目录 1. 流程撤回、拒绝和回退调用自定义函数1.1 工作流【撤销】回调钩子1.2 撤销【回退】回调钩子1.3 工作流【拒绝】回调钩子1.4 工作流【同意】回调钩子1.4 工作流【反悔】回调钩子1.4 回调钩子在业务系统中的调用示例2. 自定义审批方式、自定义审批节点名称 1.流程撤回、拒绝和回退调用自定义函数 1.1工作流【撤销】回调钩子 使用方式:把该方法放置到XXX模型的Action下面,或@Fun(XXX.MODEL_MODEL)触发方式:当流程实例被撤销时调用入口:pro.shushi.pamirs.workflow.app.core.service.impl.WorkflowInstanceServiceImpl#undoInstance /** * XXX为当前流程触发方式为模型触发时对应的触发模型、 * 对应返回不影响流程上下文 * @param data 入参为触发时的业务数据,数据的JsonString * @return */ @Function public XXX recall(String data) { // TODO: 根据实际的业务逻辑把data转换为对象 WorkRecord workRecord = JsonUtils.parseObject(data, new TypeReference<WorkRecord>(){}); // TODO: 增加自定义业务逻辑 return new XXX(); } 1.2撤销【回退】回调钩子 使用方式:把该方法放置到XXX模型的Action下面,或@Fun(XXX.MODEL_MODEL)触发方式:流程待办进行回退操作时调用入口:pro.shushi.pamirs.workflow.app.core.service.operator.ApprovalFallbackOperatorService /** * XXX为当前流程触发方式为模型触发时对应的触发模型 * 对应返回不影响流程上下文 * @param data 入参为触发时的业务数据,数据的JsonString * @return */ @Function public XXX fallBack(String data) { // TODO: 根据实际的业务逻辑把data转换为对象 WorkRecord workRecord = JsonUtils.parseObject(data, new TypeReference<WorkRecord>(){}); // TODO: 增加自定义业务逻辑 return new XXX(); } 1.3工作流【拒绝】回调钩子 使用方式:把该方法放置到XXX模型的Action下面,或@Fun(XXX.MODEL_MODEL)触发方式:流程待办进行拒绝操作时调用入口:pro.shushi.pamirs.workflow.app.core.service.operator.ApprovalFallbackOperatorService /** * XXX为当前流程触发方式为模型触发时对应的触发模型 * 对应返回不影响流程上下文 * @param data 入参为触发时的业务数据,数据的JsonString * @return */ @Function public XXX reject(String data) { // TODO: 根据实际的业务逻辑把data转换为对象 WorkRecord workRecord = JsonUtils.parseObject(data, new TypeReference<WorkRecord>(){}); // TODO: 增加自定义业务逻辑 return new XXX(); } 1.4 工作流【同意】回调钩子 使用方式:把该方法放置到XXX模型的Action下面,或@Fun(XXX.MODEL_MODEL)触发方式:流程待办进行同意操作时调用入口:pro.shushi.pamirs.workflow.app.core.util.ArtificialTaskUtils @Function(summary = "发起的审批同意时会自动调用此方法") @Function.Advanced(displayName = "审批同意") public Teacher agree(String data) { // TODO: 根据实际的业务逻辑把data转换为对象 // WorkRecord workRecord = JsonUtils.parseObject(data, new TypeReference<WorkRecord>(){}); // TODO: 增加自定义业务逻辑 return new Teacher(); } 1.4 工作流【反悔】回调钩子 使用方式:把该方法放置到XXX模型的Action下面,或@Fun(XXX.MODEL_MODEL)触发方式:流程待办进行反悔操作时使用场景:流程待办进行反悔操作时,需要额外更改其他的业务数据逻辑时可用该回调钩子。 注意:该函数的namespace需要设置为流程触发模型。 调用入口:pro.shushi.pamirs.workflow.app.core.service.operator.ArtificialRetractOperatorService @Function @Function.fun(WorkflowBizCallConstants.retract) public void retract(WorkflowUserTask workflowUserTask) { // 获取流程实例 workflowUserTask.fieldQuery(WorkflowUserTask::getInstance); WorkflowInstance instance = workflowUserTask.getInstance(); // 获取用户任务实例 WorkflowUserInstance userInstance = new WorkflowUserInstance() .setId(workflowUserTask.getWorkflowUserInstanceId()) .queryById(); // 反悔的用户id…

    2023年11月15日
    2.0K00
  • 如何加密gql请求内容

    介绍 在一些对安全等级要求比较高的场景,oinone提供了扩展前端加密请求内容,后端解密请求内容的能力,该方案要求前后端的加解密方案统一。 后端 1. 继承平台的RequestController新增一个请求类,在里面处理加密逻辑 package pro.shushi.pamirs.demo.core.controller; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import pro.shushi.pamirs.framework.gateways.graph.java.RequestController; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.protocol.PamirsClientRequestParam; import pro.shushi.pamirs.user.api.utils.AES256Utils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @Slf4j public class DemoRequestController extends RequestController { @SuppressWarnings("unused") @RequestMapping( value = "/pamirs/{moduleName:^[a-zA-Z][a-zA-Z0-9_]+[a-zA-Z0-9]$}", method = RequestMethod.POST ) public String pamirsPost(@PathVariable("moduleName") String moduleName, @RequestBody PamirsClientRequestParam gql, HttpServletRequest request, HttpServletResponse response) { decrypt(gql); return super.pamirsPost(moduleName, gql, request, response); } @SuppressWarnings("unused") @RequestMapping( value = "/pamirs/{moduleName:^[a-zA-Z][a-zA-Z0-9_]+[a-zA-Z0-9]$}/batch", method = RequestMethod.POST ) public String pamirsBatch(@PathVariable("moduleName") String moduleName, @RequestBody List<PamirsClientRequestParam> gqls, HttpServletRequest request, HttpServletResponse response) { for (PamirsClientRequestParam gql : gqls) { decrypt(gql); } return super.pamirsBatch(moduleName, gqls, request, response); } private static final String GQL_VAR = "gql"; private void decrypt(PamirsClientRequestParam gql) { Map<String, Object> variables = null != gql.getVariables() ? gql.getVariables() : new HashMap<>(); String encodeStr = (String) variables.get(GQL_VAR); if (StringUtils.isNotBlank(encodeStr)) { variables.put(GQL_VAR, null); // TODO 此处的加密方法可以换为其他算法 String gqlQuery = AES256Utils.decrypt(encodeStr); gql.setQuery(gqlQuery); } } } 2.boot工程的启动类排除掉平台默认的RequestController类 @ComponentScan( excludeFilters = { // 该注解排除平台的RequestController类 @ComponentScan.Filter( type = FilterType.REGEX, pattern = "pro.shushi.pamirs.framework.gateways.graph.java.RequestController" ) }) public class DemoApplication { } 以下为实际项目中的启动类示例 package pro.shushi.pamirs.demo.boot; import org.apache.ibatis.annotations.Mapper; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import…

    2024年6月20日
    1.9K00
  • Oinone协同开发源码分析

    前提 源码分析版本是 5.1.x版本 什么是协同开发模式 协同开发模式解决的是不同开发,在开发同一个模型时,不会相互影响,也不会影响到测试环境详见:Oinone协同开发使用手册 协同开发原理 在协同模式下,本地开发的元数据,配置pamirs.data.distribution.session.ownSign参数后,元数据前缀加ownSign值,然后只存在redis缓存,不落库。其它环境无法直接访问到该数据。测试环境,或其它环境访问,需要在url上加ownSign等于设置的,则读redis数据时,除了加载通用数据,也会合并ownSign前缀的redis数据,显示出来 注意事项 协同开发仅支持界面设计器,其他设计器均不支持 不支持权限配置 不支持工作流触发 版本支持 完整支持5.1.0及以上 功能详解 启动时操作 做元数据保护检查 配置ownSign,则key拼接为 ownSign + ‘:’ + key 清除掉ownSign的redis缓存数据;非ownSign不用清理 计算差量数据 有差量数据,放入ownSign标识数据,并清理本地标识 dubbo注册服务,group拼接group + ownSign 后进行注册 读取时操作 读本地 组装key: ownSign + ‘:’ + key 本地缓存有数据,更新缓存本地数据,返回 本地没有数据,读redis,并插入本地缓存 读远程 dubbo注册消费者,group拼接group + ownSign 后进行泛化调用 元数据保护检查 开启数据保护模式,在启动参数里加-PmetaProtected=pamirs 会在启动时,往redis里写入数据 private static final String META_PROTECTED_KEY = “pamirs:check:meta-protected”; private void writeMetaProtected(String metaProtected) { stringRedisTemplate.opsForValue().set(META_PROTECTED_KEY, metaProtected); } 如果同时又设置 pamirs.data.distribution.session.ownSign则会报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign] 处理逻辑如下 看redis是否启用保护标识的值 获取pamirs.distribution.session.ownSign配置 没有启动参数 且redis没有值,则retrun 如果有启动参数且配置了ownSign,报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign] 如果有启动参数且 redis没有值或启动参数设置 -P metaForceProtected,则写入redis 如果有启动参数, 且启动参数跟redis值不同,则报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]] 如果没有启动参数且redis有值,但没有配置ownSign 报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]] 核心代码如下MetadataProtectedChecker public void process(AppLifecycleCommand command, Set<String> runModules, List<ModuleDefinition> installModules, List<ModuleDefinition> upgradeModules, List<ModuleDefinition> reloadModules) { String currentMetaProtected = stringRedisTemplate.opsForValue().get(META_PROTECTED_KEY); String metaProtected = getMetaProtected(); boolean hasCurrentMetaProtected = StringUtils.isNotBlank(currentMetaProtected); boolean hasMetaProtected = StringUtils.isNotBlank(metaProtected); if (!hasCurrentMetaProtected && !hasMetaProtected) { return; } if (hasMetaProtected) { if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) { // 如果有启动参数且配置了ownSign throw new UnsupportedOperationException(“在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]”); } if (!hasCurrentMetaProtected || isForceProtected()) { writeMetaProtected(metaProtected); } else if (!metaProtected.equals(currentMetaProtected)) { // 如果有启动参数, 且启动参数跟redis值不同 throw unsupportedLocalOperation(); } } else { if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) { return; } // 没有启动参数且redis有值,但没有配置ownSign 报错 throw unsupportedLocalOperation(); } } 取ownSign方式 看header是否有ownSign这个标识 header没有,则从配置里取,并放到header里 ownSign的获取核心代码 CdDistributionSessionFillOwnSignApi @Override public String getCdOwnSign() { String cdOwnSign = null; // 看header是否有ownSign这个标识…

    2024年9月12日
    2.6K00
  • 引入搜索(增强模型Channel)常见问题解决办法

    总体描述 引入Oinone的搜索(即Channel模块)后,因错误的配置、缺少配置或者少引入一些Jar包,会出现一些报错。 问题1:启动报类JCTree找不到 具体现象 启动过程可能会出现报错:java.lang.NoClassDefFoundError: com/sun/tools/javac/tree/JCTree$JCExpression 产生原因 引入Channel模块后,启动过程中会扫描Class包找寻Enhance的注解,Pamirs底层有使用到jdk的tools中的类, com/sun/tools/javac/tree/JCTree$JCExpression 特定版本的jdk可能会缺少tools.jar导致启动失败 具体报错 at org.springframework.boot.loader.Launcher.launch(Launcher.java:107) [pamirs-venus-boot.jar:na] at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) [pamirs-venus-boot.jar:na] at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88) [pamirs-venus-boot.jar:na] Caused by: java.util.concurrent.ExecutionException: java.lang.NoClassDefFoundError: com/sun/tools/javac/tree/JCTree$JCExpression at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) ~[na:1.8.0_381] at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908) ~[na:1.8.0_381] at pro.shushi.pamirs.boot.common.initial.PamirsBootMainInitial.init(PamirsBootMainInitial.java:66) ~[pamirs-boot-api-4.6.10.jar!/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_381] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_381] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_381] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_381] at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:305) ~[spring-context-5.2.12.RELEASE.jar!/:5.2.12.RELEASE] … 20 common frames omitted Caused by: java.lang.NoClassDefFoundError: com/sun/tools/javac/tree/JCTree$JCExpression at java.lang.Class.forName0(Native Method) ~[na:1.8.0_381] at java.lang.Class.forName(Class.java:264) ~[na:1.8.0_381] at pro.shushi.pamirs.meta.util.ClassUtils.getClasses(ClassUtils.java:157) ~[pamirs-meta-model-4.6.8.jar!/:na] at pro.shushi.pamirs.meta.util.ClassUtils.getClassesByPacks(ClassUtils.java:73) ~[pamirs-meta-model-4.6.8.jar!/:na] at pro.shushi.pamirs.channel.core.manager.EnhanceModelScanner.enhanceModel(EnhanceModelScanner.java:51) ~[pamirs-channel-core-4.6.15.jar!/:na] at pro.shushi.pamirs.channel.core.init.ChannelSystemBootAfterInit.init(ChannelSystemBootAfterInit.java:31) 解决办法 方式一【推荐】、配置channel的扫描路径 pamirs: channel: packages: – com.pamirs.ic 方式二、使用Oracle版本的jdk,确保jdk的lib目录,tools.jar有com/sun/tools/javac/tree/JCTree对应的类 问题2:启动报类JsonProvider找不到 具体报错 如果启动报错信息如下: Caused by: java.lang.NoClassDefFoundError: jakarta/json/spi/JsonProvider at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_181] at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_181] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_181] at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[na:1.8.0_181] 产生原因 项目中只引入了pamirs-channel-core,但未引入elasticsearch相关的包 解决办法 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>8.4.1</version> </dependency> <dependency> <groupId>jakarta.json</groupId> <artifactId>jakarta.json-api</artifactId> <version>2.1.1</version> </dependency> 其他参考: Oinone引入搜索引擎步骤:https://doc.oinone.top/backend/7235.html

    2024年5月17日
    1.8K00
  • Nacos做为注册中心:如何调用其他系统的SpringCloud服务?

    Oinone项目引入Nacos作为注册中心,调用外部的SpringCloud服务 Nacos可以做为注册中心,提供给Dubbo和SpringCloud等微服务框架使用。 目前Oinone的底层使用的是Dubbo进行微服务的默认协议调用,但是我们项目如果存在需要调用其他系统提供的SpringCloud服务,Oinone其实并没有限制大家去这么写代码。 可以参考Nacos或SpringCloud的官方文档,只要不存在Jar包冲突等场景,很多的扩展其实大家都可以使用。 注意!!!Nacos、SpringCloud、SpringCloudAlibaba是有依赖版本严格要求的:点击查看 具体示例: 一、项目中增加依赖 主pom引入兼容的版本: <dependencyManagement> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.7.RELEASE</version> <!– 目前兼容的版本 –> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement> 使用模块的pom引入依赖: <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 二、 配置 application.yml spring: cloud: nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos 三、启动类添加注解 @EnableDiscoveryClient @EnableFeignClients public class NacosConsumerApplication { public static void main(String[] args) { SpringApplication.run(NacosConsumerApplication.class, args); } } 四、验证 创建 Feign Client 接口 import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(name = "nacos-demo") // 指定目标服务的名称 public interface ProviderClient { @GetMapping("/hello") String hello(); } 创建 Controller 调用 Feign Client @RestController public class ConsumerController { private final ProviderClient providerClient; public ConsumerController(ProviderClient providerClient) { this.providerClient = providerClient; } @GetMapping("/hello") public String hello() { return providerClient.hello(); } } 在浏览器中访问 http://localhost:8082/hello你应该会看到服务提供者返回的响应。

    2024年6月4日
    3.4K00

Leave a Reply

Please Login to Comment