集成设计器数据流程、流程设计器可以暴露接口触发

集成设计器数据流程暴露接口触发

需求:在集成设计器配置的连接器、数据流程链接到外部接口,需要可以有一个管理页面,统一管理这些集成配置。比如对接多个医院的挂号系统,希望可以配置好数据流程之后,能够在已经实现的开放接口上,动态的调用集成平台配置的数据流程。

连接器暴露接口触发

  1. 设计连接器资源配置模型。使用业务ID+接口唯一标识+连接器实现连接器资源和业务唯一。

    @Model.model(EipConnectorResourceSetting.MODEL_MODEL)
    @Model(displayName = "连接器资源配置", summary = "连接器资源配置")
    @Model.Advanced(unique = {"hospitalId, interfaceUnique"})
    public class EipConnectorResourceSetting extends IdModel {
    
    public static final String MODEL_MODEL = "hr.simple.EipConnectorResourceSetting";
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field(displayName = "医院", required = true)
    @Field.Relation(relationFields = {"hospitalId"}, referenceFields = {"id"})
    private Hospital hospital;
    
    @Field.Integer
    @Field(displayName = "医院ID", invisible = true)
    private Long hospitalId;
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field.String
    @Field(displayName = "接口唯一标识", required = true)
    private String interfaceUnique;
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field.many2one
    @Field(displayName = "连接器", required = true)
    @Field.Relation(relationFields = {"connectorId"}, referenceFields = {"id"})
    private EipConnector connector;
    
    @Field.Integer
    @Field(displayName = "连接器ID", invisible = true)
    private Long connectorId;
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field.many2one
    @Field(displayName = "连接器接口", required = true)
    @Field.Relation(relationFields = {"integrationInterfaceId"}, referenceFields = {"id"}, domain = "connectorId==${activeRecord.connector.id}")
    private EipConnectorResource connectorResource;
    
    @Field.Integer
    @Field(displayName = "连接器接口ID", invisible = true)
    private Long connectorResourceId;
    }
  2. 开放接口定义,文档参考:https://doc.oinone.top/oio4/9326.html
    外部平台调用开放接口,实现动态调用连接器资源

    @Model.model(HospitalSection.MODEL_MODEL)
    @Model(displayName = "医院科室", summary = "医院科室")
    public class HospitalSection extends IdModel {
    
    public static final String MODEL_MODEL = "net.example.HospitalSection";
    
    @Field(displayName = "科室Code", required = true)
    private String deptCode;
    
    @Field(displayName = "科室名称", required = true)
    private String deptName;
    
    @Field(displayName = "科室描述")
    private String deptDesc;
    }
    @Slf4j
    @Fun
    @Component
    public class TestOpenService {
    
    @Resource
    private TestCommonService testCommonService;
    
    @Function
    @Open(name = "获取科室", path = "test/gainSections")
    @Open.Advanced(httpMethod = "POST")
    public OpenEipResult<List<HospitalSection>> gainSections(IEipContext<SuperMap> context, ExtendedExchange exchange) {
        return new OpenEipResult<>(testCommonService.gainSections(context));
    }
    }
    @Slf4j
    @Component
    public class TestCommonService {
    
    @Resource
    private CustomWorkflowService customWorkflowService;
    
    /**
     * 获取科室
     *
     * @param context 请求上下文
     * @return 科室集合
     */
    public List<HospitalSection> gainSections(IEipContext<SuperMap> context) {
        EipResult<SuperMap> gainSections = executeEipConnector(context, "gainSections");
        if (gainSections.getSuccess()) {
            if (gainSections.getContext().getInterfaceContext().get("DepartmentList")!=null) {
                return JSON.parseArray(JSON.toJSONString(gainSections.getContext().getInterfaceContext().get("DepartmentList")),
                        HospitalSection.class);
    
            }
        }
        String errorCode = gainSections.getErrorCode();
        String errorMessage = gainSections.getErrorMessage();
        // Object result = gainSections.getResult();
        return new ArrayList<>();
    }
        /**
     * 根据请求参数中医院id和参数key-value、接口标识获取连接器
     *
     * @param context         请求上下文
     * @param interfaceUnique 接口标识
     * @return 连接器
     */
    private EipResult<SuperMap> executeEipConnector(IEipContext<SuperMap> context, String interfaceUnique) {
        String hospitalId = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("hospitalId"))).orElse("");
        String parameterValue = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("parameterValue"))).orElse("");
        //step1. 获取连接器模型
        EipConnectorResourceSetting interfaceSetting = new EipConnectorResourceSetting()
                .setHospitalId(Long.valueOf(hospitalId)).setInterfaceUnique(interfaceUnique).queryOne();
        if (interfaceSetting == null) {
            throw PamirsException.construct(ExpEnumerate.SYSTEM_ERROR).appendMsg("连接器模型未找到").errThrow();
        }
        //step2. 获取连接器接口
        interfaceSetting.fieldQuery(EipConnectorResourceSetting::getConnectorResource);
        EipConnectorResource connectorResource = interfaceSetting.getConnectorResource();
        if (connectorResource == null) {
            throw PamirsException.construct(ExpEnumerate.SYSTEM_ERROR).appendMsg("连接器接口未找到").errThrow();
        }
        //3. 执行连接器获取结果
        Map<String, Object> params = new HashMap<>();
        params.put("wxValue1", parameterValue);
        return EipExecutor.newInstance().call(connectorResource.getInterfaceName(), params);
    }

数据流程暴露接口触发

  1. 设计数据流程配置模型。使用业务ID+接口唯一标识+连接器实现数据流程配置和业务唯一。

    @Model.model(EipOpenDataflowSetting.MODEL_MODEL)
    @Model(displayName = "数据流程配置", summary = "数据流程配置")
    @Model.Advanced(unique = {"hospitalId,interfaceUnique"})
    public class EipOpenDataflowSetting extends IdModel {
    
    public static final String MODEL_MODEL = "hr.simple.EipOpenDataflowSetting";
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field(displayName = "医院", required = true)
    @Field.Relation(relationFields = {"hospitalId"}, referenceFields = {"id"})
    private Hospital hospital;
    
    @Field.Integer
    @Field(displayName = "医院ID", invisible = true)
    private Long hospitalId;
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field.String
    @Field(displayName = "接口唯一标识", required = true)
    private String interfaceUnique;
    
    @UxTableSearch.FieldWidget(@UxWidget())
    @Field.many2one
    @Field(displayName = "数据流程", required = true)
    @Field.Relation(relationFields = {"dataflowId"}, referenceFields = {"id"})
    private DataflowDesigner dataflow;
    
    @Field.Integer
    @Field(displayName = "数据流程编码", invisible = true)
    private Long dataflowId;
    
    @Field.String
    @Field.Related(related = {"dataflow", "code"})
    @UxForm.FieldWidget(@UxWidget(readonly = "true"))
    @Field(displayName = "数据流程编码", store = NullableBoolEnum.TRUE)
    private String dataflowCode;
    }
  2. 开放接口定义。

    @Fun(RegisterOpenService.FUN_NAMESPACE)
    public interface RegisterOpenService {
    
    String FUN_NAMESPACE = "net.example.RegisterService";
    
    @Function
    RegisterOrder queryById(Long hospitalId, Long memberId);
    }
    @Slf4j
    @Fun(RegisterOpenService.FUN_NAMESPACE)
    @Component
    public class RegisterOpenServiceImpl implements RegisterOpenService {
    
    @Autowired
    private CustomWorkflowService customWorkflowService;
    
    /**
     * 开放接口:把自己的开放出去供外部调用(如:小程序调用)
     *
     * 文档参考:https://doc.oinone.top/oio4/9326.html
     *
     * 1、接口访问URL地址:http://127.0.0.1:8195/openapi/pamirs/register/queryById 端口根据yml配置进行修改
     * 2、参数默认是map格式,构造为IEipContext对象
     */
    @Function
    @Open(name = "根据ID查询挂号流水", path = "register/queryById")
    @Open.Advanced(
            httpMethod = "GET"
    )
    public OpenEipResult<RegisterOrder> queryById4Open(IEipContext<SuperMap> context, ExtendedExchange exchange) {
        String memberId = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("id"))).orElse("0");
        String hospitalId = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("hospitalId"))).orElse("0");
        // 开发接口的内部具体实现
        RegisterOrder register = queryById(Long.valueOf(hospitalId), Long.valueOf(memberId));
        if (register!=null) {
            return new OpenEipResult<>(register);
        } else {
            throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("根据ID "+ memberId +"查询挂号流水").errThrow();
        }
    }
    
    @Override
    @Function
    public RegisterOrder queryById(Long hospitalId, Long memberId) {
        // step1、业务前置逻辑、参数校验等
        // step2、构造请求数据、或者通过BD获取数据(这里先进行Mock)
        RegisterOrder register = new RegisterOrder();
        register.setHospitalId("719331903102009889");
        register.setSectionId("1");
        register.setDoctorId("1");
        register.setSourceId("1");
        register.setPatienterName("zhangsan");
        register.setPatienterIdCard("123444");
        // 配置表,医院 + 接口功能 == >> 流程编码。   ****** 实际项目这些基础数据需要缓存,如果Caffeine/Redis*********
        EipOpenDataflowSetting dataflowSetting = new EipOpenDataflowSetting().setHospitalId(hospitalId).setInterfaceUnique("theWeather").queryOne();
        if (dataflowSetting !=  null) {
            // step3、调用数据流程
            WorkflowDefinition workflowDefinition = customWorkflowService.queryWorkflowDefinition(dataflowSetting.getDataflowCode());
            if (workflowDefinition != null) {
                // 分支1:调用数据流程
                Map<String, Object> result = customWorkflowService.startWorkflow(workflowDefinition, register);
                log.info("调用数据流程返回:" + result);
    
                // 根据返回处理业务逻辑
            }
        } else {
            // 分支2:异常处理,或者没有数据流程实现执行默认逻辑
        }
    
        // Step4 业务后置逻辑,数据操作、其他service的调用之类
        return register;
    }
    }
    @Slf4j
    @Component
    public class CustomWorkflowService {
    
    public WorkflowDefinition queryWorkflowDefinition(String workflowCode) {
        return new WorkflowDefinition().queryOneByWrapper(
                Pops.<WorkflowDefinition>lambdaQuery()
                        .from(WorkflowDefinition.MODEL_MODEL)
                        .eq(WorkflowDefinition::getWorkflowCode, workflowCode)
                        .eq(WorkflowDefinition::getActive, 1)
        );
    }
    
    /**
     * 触发⼯作流实例
     */
    public Map<String, Object> startWorkflow(WorkflowDefinition workflowDefinition, IdModel modelData) {
        Map<String, Object> result = new HashMap<>();
        if (null == workflowDefinition) {
            // 流程没有运⾏实例
            return result;
        }
        String model = Models.api().getModel(modelData);
        //⼯作流上下⽂
        WorkflowDataContext wdc = new WorkflowDataContext();
        wdc.setDataType(WorkflowVariationTypeEnum.ADD);
        wdc.setModel(model);
        wdc.setWorkflowDefinitionDefinition(workflowDefinition.parseContent());
        wdc.setWorkflowDefinition(workflowDefinition);
        wdc.setWorkflowDefinitionId(workflowDefinition.getId());
        IdModel copyData = KryoUtils.get().copy(modelData);
        // ⼿动触发创建的动作流,将操作⼈设置为当前用户,作为流程的发起⼈
        copyData.setCreateUid(PamirsSession.getUserId());
        copyData.setWriteUid(PamirsSession.getUserId());
        String jsonData = JsonUtils.toJSONString(copyData.get_d());
        //触发工作流 新增时触发-onCreateManual 更新时触发-onUpdateManual
        Fun.run(WorkflowModelTriggerFunction.FUN_NAMESPACE, "onCreateManualSync", wdc, "1", jsonData);
    
        // 处理返回结果
        Long workflowInstanceId = (Long) wdc.getDataMap().get(WorkflowConstant.WORKFLOW_INSTANCE_ID);
        if (workflowInstanceId !=null) {
            WorkflowInstance workflowInstance = new WorkflowInstance().setId(workflowInstanceId).queryById();
            WorkflowContext workflowContext = workflowInstance.fetchWorkflowContext();
            result = (HashMap)workflowContext.get(ParamNode.PARAM_PREFIX);
        }
    
        return result;
    }
    
    public Map<String,Object> startCreatOrderWorkflow(WorkflowDefinition workflowDefinition, IdModel modelData){
        Map<String, Object> result = new HashMap<>();
        if (null == workflowDefinition) {
            // 流程没有运⾏实例
            return result;
        }
        String model = Models.api().getModel(modelData);
        //⼯作流上下⽂
        WorkflowDataContext wdc = new WorkflowDataContext();
        wdc.setDataType(WorkflowVariationTypeEnum.ADD);
        wdc.setModel(model);
        wdc.setWorkflowDefinitionDefinition(workflowDefinition.parseContent());
        wdc.setWorkflowDefinition(workflowDefinition);
        wdc.setWorkflowDefinitionId(workflowDefinition.getId());
        IdModel copyData = KryoUtils.get().copy(modelData);
        // ⼿动触发创建的动作流,将操作⼈设置为当前用户,作为流程的发起⼈
        copyData.setCreateUid(PamirsSession.getUserId());
        copyData.setWriteUid(PamirsSession.getUserId());
        String jsonData = JsonUtils.toJSONString(copyData.get_d());
        //触发⼯作流 新增时触发-onCreateManual 更新时触发-onUpdateManual
        Fun.run(WorkflowModelTriggerFunction.FUN_NAMESPACE, "onCreateManualSync", wdc, "1", jsonData);
    
        // 处理返回结果
        Long workflowInstanceId = (Long) wdc.getDataMap().get(WorkflowConstant.WORKFLOW_INSTANCE_ID);
        if (workflowInstanceId !=null) {
            WorkflowInstance workflowInstance = new WorkflowInstance().setId(workflowInstanceId).queryById();
            WorkflowContext workflowContext = workflowInstance.fetchWorkflowContext();
            result = (HashMap)workflowContext.get(ParamNode.PARAM_PREFIX);
        }
    
        return result;
    }
    }

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/20972.html

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

Like (0)
yexiu's avataryexiu数式员工
Previous 2025年4月17日 pm8:52
Next 2025年4月21日 pm3:53

相关推荐

  • 如何改变调度策略,让Schedule独立执行线程

    schedule里,相同的taskType跑多个业务任务,如果其中一个任务大量重试占满了调度线程,会影响别的业务任务及时被执行,如下面截图中,taskType用的是平台内置的常量,这个常量会被其他任务也使用,如果当前任务出现了异常占用了这个taskType的所有线程,那么这个taskType下面的其他任务就会被阻塞延后执行。应该给需要业务及时性的任务单独建立自定义的taskType,这样每个taskType的线程就是独立的,A任务异常不会影响B任务的执行。 1、后台创建task type相关的类,继承BaseScheduleNoTransactionTask,要加springbean的注解,参考:task type建议使用类名 2、提交任务的时候,设置tasktype为步骤1的TaskType 3、控制台新增策略和任务bean名称为步骤1的spring beanName,任务名称 $xxx,右边的占位符内容为yml里面配置的ownSign字段任务的名称也是步骤1的 spring beanName 4、配置完成后,控制台启动任务,就可以测试了

    2024年2月20日
    1.2K00
  • 读写分离

    总体介绍 Oinone的读写分离方案是基于Sharding-JDBC的整合方案,要先具备一些Sharding-JDBC的知识。 [Sharding-JDBC] 读写分离依赖于主从复制来同步数据,从库复制数据后,才能通过读写分离策略将读请求分发到从库,实现读写操作的分流,请根据业务需求自行实现主从配置。 配置读写策略 配置 top_demo 模块走读写分离的逻辑数据源 pamirsSharding。 配置数据源。 为 pamirsSharding 配置数据源以及 sharding 规则。 指定需要被sharding-jdbc接管的模块 指定top_demo模块给 Sharding-JDBC 接管,接管逻辑数据源名为 pamirsSharding pamirs: framework: data: ds-map: base: base top_demo: pamirsSharding 配置数据源 pamirs: datasource: pamirsMaster: driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/61_pamirs_mydemo_master?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: root password: ma123456 initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true pamirsSlaver: # 从库数据源配置 driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/61_pamirs_mydemo_slaver?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: root password: ma123456 initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true 配置读写数据源及规则 pamirs: sharding: define: data-sources: pamirsSharding: pamirsMaster # 为逻辑数据源pamirsSharding指向主数据源pamirsMaster。 models: "[trigger.PamirsSchedule]": tables: 0..13 rule: pamirsSharding: actual-ds: # 指定逻辑数据源pamirsSharding代理的数据源为pamirsMaster、pamirsSlaver – pamirsMaster – pamirsSlaver # 以下配置跟sharding-jdbc配置一致 replicaQueryRules: – data-sources: pamirsSharding: primaryDataSourceName: pamirsMaster # 写库数据源 replicaDataSourceNames: – pamirsSlaver # 读库数据源 loadBalancerName: round_robin load-balancers: round_robin: type: ROUND_ROBIN # 读写规则

    2025年5月22日
    90600
  • 模型定义在数据库中的映射

    模型定义在数据库中的映射 Oinone中通过定义模型来建立数据表,使用注解的方式来使多张表之间的关联。 数据库字段与模型定义字段映射 package pro.shushi.pamirs.top.api.model; import pro.shushi.pamirs.core.common.enmu.DataStatusEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; import pro.shushi.pamirs.meta.enmu.DateFormatEnum; import pro.shushi.pamirs.meta.enmu.DateTypeEnum; import pro.shushi.pamirs.meta.enmu.MimeTypeEnum; import java.math.BigDecimal; import java.util.Date; @Model.model(PamirsDemo.MODEL_MODEL) @Model(displayName = “PamirsDemo”) public class PamirsDemo extends IdModel { public static final String MODEL_MODEL = “top.PamirsDemo”; @Field.Binary(mime = MimeTypeEnum.html) @Field(displayName = “二进制类型”) private Byte[] byteType; @Field.Integer @Field(displayName = “整数”) private Long longType; @Field.Float @Field(displayName = “浮点数”) private BigDecimal floatType; @Field.Boolean @Field(displayName = “布尔类型”) private Boolean booleanType; @Field.Enum @Field(displayName = “枚举”) private DataStatusEnum enumType; @Field.String @Field(displayName = “字符串”) private String stringType; @Field.Text @Field(displayName = “多行文本”) private String textType; @Field.Html @Field(displayName = “富文本”) private String richText; @Field.Date(type = DateTypeEnum.DATE, format = DateFormatEnum.DATE) @Field(displayName = “日期类型”) private Date dataType; @Field.Date(type = DateTypeEnum.DATETIME, format = DateFormatEnum.DATETIME) @Field(displayName = “日期时间类型”) private Date dataTimeType; @Field.Money @Field(displayName = “金额”) private BigDecimal amount; } 更多字段基础请参考文档字段基础与复合 多对一的关系映射 例:设计一张教师表,一张科目表,教师表对科目表属于多对一的关系,在教师表中使用科目id管理关联关系。 教师表teacher 科目表professional 那么在Oinone的模型定义中,这两张表定义是这样的; 教师模型 package pro.shushi.pamirs.top.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; @Model.model(Teacher.MODEL_MODEL) @Model(displayName = “教师”, summary = “教师”) public class Teacher extends IdModel { public static final String MODEL_MODEL = “top.Teacher”; @Field.String @Field(displayName = “教师名字”) private String teacherName; @Field.Integer @Field(displayName = “科目id”) private Long professionalId; @Field(displayName…

    2024年8月16日
    1.6K00
  • 非存储字段搜索,适应灵活的搜索场景

    1、非存储字段搜索 1.1 描述 通常根据本模型之外的信息作为搜索条件时,通常会把 这些字段放在代理模型上。这类场景我们称之为 非存储字段搜索 1.2 场景一 非存储字段为基本的String。1.代码定义:非存储字段为基本的包装数据类型 @Field(displayName = "确认密码", store = NullableBoolEnum.FALSE) private String confirmPassword; 2.设计器拖拽:列表中需要有退拽这个字段,字段是否隐藏的逻辑自身业务是否需要决定。3.页面通过非存储字段为基本的包装数据类型进行搜索时。会拼在 queryWrapper 的属性queryData中,queryData为Map,key为字段名,value为搜索值。4.后台逻辑处理代码示例: Map<String, Object> queryData = queryWrapper.getQueryData(); if (null != queryData) { Object productIdObj = queryData.get(PRODUCT_ID); if (Objects.nonNull(productIdObj)) { String productId = productIdObj.toString(); queryWrapper.lambda().eq(MesProduceOrderProxy::getProductId, productId); } } 1.3 场景二 非存储字段为非存储对象。 定义 为非存储的 @Field(displayName = "款", store = NullableBoolEnum.FALSE) @Field.many2one @Field.Relation(store = false) private MesProduct product; 2.页面在搜索栏拖拽非存储字段作为搜索条件 3.后台逻辑处理代码示例: try { if (null != queryData && !queryData.isEmpty()) { List<Long> detailId = null; BasicSupplier supplier = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(supplierField), BasicSupplier.class); MesProduct product = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(productField), MesProduct.class); MesMaterial material = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(materialField), MesMaterial.class); if (supplier != null) { detailId = bomService.queryBomDetailIdBySupplierId(supplier.getId()); if (CollectionUtils.isEmpty(detailId)) { detailId.add(-1L); } } if (product != null) { List<Long> produceOrderId = produceOrderService.queryProductOrderIdByProductIds(product.getId()); if (CollectionUtils.isNotEmpty(produceOrderId)) { queryWrapper.lambda().in(MesProduceBomSizes::getProduceOrderId, produceOrderId); } } if (material != null) { //找出两个bom列表的并集 List<Long> materBomDetailId = bomService.queryBomDetailIdByMaterialId(material.getId()); if (CollectionUtils.isNotEmpty(detailId)) { detailId = detailId.stream().filter(materBomDetailId::contains).collect(Collectors.toList()); } else { detailId = new ArrayList<>(); if (CollectionUtils.isEmpty(materBomDetailId)) { detailId.add(-1L); } else { detailId.addAll(materBomDetailId); } } } if (CollectionUtils.isNotEmpty(detailId)) { queryWrapper.lambda().in(MesProduceBomSizes::getProductBomId, detailId); } } } catch (Exception e) { log.error("queryData处理异常", e); } 注意:…

    2024年2月20日
    1.9K00
  • 蓝绿发布

    背景 应用程序升级面临最大挑战是新旧业务切换,将软件从测试的最后阶段带到生产环境,同时要保证系统不间断提供服务。长期以来,业务升级渐渐形成了几个发布策略:蓝绿发布、灰度发布和滚动发布,目的是尽可能避免因发布导致的流量丢失或服务不可用问题。 本文主要介绍Oinone平台如何实现蓝绿发布。蓝绿发布:项目逻辑上分为AB组,在项目系统时,首先把A组从负载均衡中摘除,进行新版本的部署。B组仍然继续提供服务。 需求 统一权限统一登录信息不同业务数据 实现方案 首先需要两个环境并统一流量入口,这里使用Nginx配置负载均衡:nginx如何配置后端服务的负载均衡 统一权限配置 在蓝绿环境添加不同的redis前缀 spring: redis: prefix: xxx 在蓝绿环境的修改AuthRedisTemplate Bean,利用setKeySerializer去掉redis前缀。 可以使用@Profile注解指定仅线上环境生效。 @Configuration // @Profile("prod") public class AuthRedisTemplateConfig { @Bean(AuthConstants.REDIS_TEMPLATE_BEAN_NAME) public AuthRedisTemplate<?> authRedisTemplate( RedisConnectionFactory redisConnectionFactory, PamirsStringRedisSerializer pamirsStringRedisSerializer ) { AuthRedisTemplate<Object> template = new AuthRedisTemplate<Object>(redisConnectionFactory, pamirsStringRedisSerializer) { @Override public void afterPropertiesSet() { // 重写 key serializer,去掉前缀隔离 this.setKeySerializer(new PamirsStringRedisSerializer(null)); super.afterPropertiesSet(); } }; return template; } } 统一登录 在蓝绿环境自定义实现pro.shushi.pamirs.user.api.spi.UserCacheApi SPI,去除redis前缀 package pro.shushi.pamirs.top.core.spi; import com.alibaba.fastjson.JSON; import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.SessionCallback; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import pro.shushi.pamirs.auth.api.cache.redis.AuthRedisTemplate; import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO; import pro.shushi.pamirs.meta.api.dto.protocol.PamirsRequestVariables; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.spi.SPI; import pro.shushi.pamirs.user.api.cache.UserCache; import pro.shushi.pamirs.user.api.configure.UserConfigure; import pro.shushi.pamirs.user.api.spi.UserCacheApi; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; @Order(1) @Component @SPI.Service("MyUserCache") public class MyUserCache implements UserCacheApi { private static final Set<String> DEFAULT_FILTER_URIS = Collections.singleton("/pamirs/message"); @Autowired private AuthRedisTemplate redisTemplate; @Override public PamirsUserDTO getSessionUser(String key) { String objectValue = getUserCacheAndRenewed(key); if (StringUtils.isNotBlank(objectValue)) { return JSON.parseObject(objectValue, PamirsUserDTO.class); } return null; } @Override public void setSessionUser(String key, PamirsUserDTO user, Integer expire) { user.setPassword(null); expire = getExpire(expire); redisTemplate.opsForValue().set(key.replace("'", " "), JSON.toJSONString(user), expire, TimeUnit.SECONDS); // 当前的实现是一个user可以在多个客户端登录,需要在管理端修改user权限后强制清除掉该用户已登录的session,所以需要记录uid对应所有已登录的sessionId String userRelSessionKey = UserCache.createUserRelSessionKey(user.getUserId()); redisTemplate.opsForSet().add(userRelSessionKey, key); redisTemplate.expire(userRelSessionKey,…

    2025年9月18日
    1.1K00

Leave a Reply

Please Login to Comment