GQL GET 请求生成图片文件流 — 技术实现方案

GQL GET 请求生成图片文件流 — 技术实现方案

基于 Oinone/Pamirs GQL GET 文件流机制,参考 FileAction#downloadFormData(查询型)与 ExcelExportTaskAction#createExportTask(同步写流),实现「按业务 ID 动态生成图片并输出文件流」的通用指南。


1. 背景与目标

1.1 需求

在业务场景中,需要根据业务 ID(如订单 ID、模板 ID、单据 ID)在后端动态渲染图片(条码、标签、证书、预览图等),并将结果以文件流形式返回给前端。

1.2 约束

  • 复用 Oinone 既有鉴权、会话、模块路由体系,不新增独立 REST 下载接口
  • 与 Excel 同步导出保持一致的调用方式:通过 GET 访问 /pamirs/{module}?query=...&variables=...
  • 前端仅两种消费方式:
    • 页面预览<img :src="imageUrl" />
    • 触发下载window.open(imageUrl, '_blank')

2. 参考实现

2.1 查询型文件流(图片查看应对齐此模式)

图片查看属于只读查询,应使用 query + @Function(type = QUERY),与 CDN 文件下载一致:

// @oinone/kunlun-vue-ui-common/.../UploadService.ts
const gql = `query{resourceFileFormQuery{downloadFormData(resourceFileForm:{downloadUrl:"${url}"}){filename}}}`;
window.open(UrlHelper.appendBasePath(`/pamirs/file?query=${encodeURIComponent(gql)}`), '_blank');
// pamirs-core/pamirs-file2/.../FileAction.java
@Function.Advanced(displayName = "前端下载数据", type = FunctionTypeEnum.QUERY)
public ResourceFileForm downloadFormData(ResourceFileForm resourceFileForm) {
    // 直接写 HttpServletResponse 输出流
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=" + ...);
    IOUtils.copy(inputStream, response.getOutputStream());
    return new ResourceFileForm();
}

图片查看用 GQL.query,不用 GQL.mutation

2.2 同步写流机制(共用)

无论 query 还是 mutation,文件流输出的底层机制相同:在 Function/Action 执行过程中直接写 HttpServletResponse

2.2.1 HTTP 网关:RequestController

GET  /pamirs/{moduleName}?query={gql}&variables={json}
POST /pamirs/{moduleName}  body: { query, variables }

GET 与 POST 走同一执行链路

// pamirs-framework/pamirs-gateways-graph-java/.../RequestController.java
@RequestMapping(value = "/pamirs/{moduleName}", method = RequestMethod.GET)
public DeferredResult<String> pamirsGet(
    @PathVariable("moduleName") String moduleName,
    @RequestParam("query") String gql,
    @RequestParam(value = "variables", required = false) String variables,
    ...) {
    PamirsClientRequestParam gqlRequest = new PamirsClientRequestParam();
    gqlRequest.setQuery(gql);
    if (!StringUtils.isBlank(variables)) {
        gqlRequest.setVariables(JsonUtils.parseMap(variables));
    }
    return pamirsPost(moduleName, gqlRequest, request, response);
}

variables.pathRequestHelper.preparePamirsRequestParam 中经 SessionPrepareApi.prepare 注入会话上下文,与前端 getSessionPath() 对应。

2.2.2 Excel 导出对照:ExcelFileServiceImpl#doExportSync

Excel 同步导出使用 mutationcreateExportTask),因为会创建导出任务记录;图片查看无此副作用,故用 query

同步导出的关键:劫持 FileClient.upload,将字节直接写入当前 HTTP 响应:

// pamirs-core/pamirs-file2/.../ExcelFileServiceImpl.java
public void doExportSync(ExcelExportTask exportTask, ExcelDefinitionContext context) {
    FileClient fileClient = new FileClient() {
        @Override
        public CdnFile upload(String fileName, byte[] data) {
            HttpServletResponse response = ...; // 从 RequestContextHolder 获取
            response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length));
            response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, UTF_8));
            ServletOutputStream sos = response.getOutputStream();
            sos.write(data);
            sos.flush();
            return internalFileClient.upload(fileName, data);
        }
    };
    doExport0(exportTask, context, fileClient);
}

核心模式:GQL Function 执行业务逻辑 → 生成 byte[] → 通过 HttpServletResponse.getOutputStream() 直接输出,不再返回 JSON

2.3 mutation vs query 选型

场景 GQL 类型 后端注解 原因
图片查看 / 下载 query @Function(type = QUERY) 只读,按 ID 渲染并输出流
Excel 同步导出 mutation @Action 创建导出任务记录,附带写流
CDN 文件下载 query @Function(type = QUERY) 只读,代理已有文件流

2.4 端到端时序

GQL GET 请求生成图片文件流 — 技术实现方案


3. 通用图片流方案

3.1 调用方式

图片生成通过 GQL query 发起,一次 GET 请求完成渲染并输出文件流。前端通过同一 URL,配合后端 Content-Disposition 区分两种用途:

前端用法 后端响应头 效果
<img :src="imageUrl" /> Content-Disposition: inline 页面内预览
window.open(imageUrl, '_blank') Content-Disposition: attachment 新标签触发下载

请求参数中增加 download 字段控制响应头(见 §4、§5)。

3.2 模块选择

moduleName 说明
file 走 file 子系统,与 Excel 导出同模块
业务模块(如 order Action 挂在业务模型上,适合强业务耦合的图片生成

4. 后端实现指南

4.1 数据模型

@Model.model("order.BusinessImageRequest")
@Model(displayName = "业务图片请求")
public class BusinessImageRequest extends IdModel {

    @Field.String
    @Field(displayName = "业务ID")
    private String businessId;

    @Field.String
    @Field(displayName = "图片类型", summary = "如 BARCODE / LABEL / CERTIFICATE")
    private String imageType;

    @Field.Boolean
    @Field(displayName = "下载模式", summary = "true=attachment 下载,false=inline 预览")
    private Boolean download;

    @Field.Integer
    @Field(displayName = "宽度")
    private Integer width;

    @Field.Integer
    @Field(displayName = "高度")
    private Integer height;
}

4.2 Function 实现(查询型)

@Component
@Model.model(BusinessImageRequest.MODEL_MODEL)
public class BusinessImageAction {

    @Autowired
    private BusinessImageService businessImageService;

    @Function.Advanced(displayName = "渲染业务图片", type = FunctionTypeEnum.QUERY)
    @Function(openLevel = {FunctionOpenEnum.API, FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE})
    public BusinessImageRequest renderBusinessImage(BusinessImageRequest businessImageRequest) {
        businessImageService.renderImage(businessImageRequest);
        return businessImageRequest;
    }
}

4.3 Service:写图片流

@Service
public class BusinessImageServiceImpl implements BusinessImageService {

    public void renderImage(BusinessImageRequest request) {
        byte[] imageBytes = generateImage(request);

        HttpServletResponse response = Optional.ofNullable(RequestContextHolder.getRequestAttributes())
            .map(a -> (ServletRequestAttributes) a)
            .map(ServletRequestAttributes::getResponse)
            .orElseThrow(() -> new RuntimeException("未获取到 Http 响应"));

        try {
            String fileName = buildFileName(request);
            boolean download = Boolean.TRUE.equals(request.getDownload());
            String disposition = download ? "attachment" : "inline";

            response.setContentType("image/png");
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(imageBytes.length));
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                disposition + ";filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));

            ServletOutputStream out = response.getOutputStream();
            out.write(imageBytes);
            out.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private byte[] generateImage(BusinessImageRequest request) {
        // 1. 按 businessId 查业务数据
        // 2. 调用渲染引擎生成图片
        // 3. 返回 PNG/JPEG byte[]
        return ...;
    }
}

实现模式与 FileAction#downloadFormData 一致:在 GQL Query Function 调用链内直接写 HttpServletResponse,无需额外 Controller。


5. 前端实现指南

5.1 构造图片 URL(公共方法)

import { GQL, getSessionPath } from '@oinone/kunlun-request';
import { UrlHelper } from '@oinone/kunlun-shared';

interface BuildImageUrlOptions {
  moduleName: string;      // 如 'order'
  queryModel: string;      // 如 'businessImageRequest'(GQL 自动拼接为 businessImageRequestQuery)
  businessId: string;
  imageType: string;
  download?: boolean;      // false=预览(inline),true=下载(attachment)
}

async function buildImageUrl(options: BuildImageUrlOptions): Promise<string> {
  const { moduleName, queryModel, businessId, imageType, download = false } = options;

  const gql = await GQL.query(queryModel, 'renderBusinessImage')
    .buildRequest((builder) => {
      builder.buildObjectParameter('businessImageRequest', (b) => {
        b.stringParameter('businessId', businessId);
        b.stringParameter('imageType', imageType);
        b.booleanParameter('download', download);
      });
    })
    .buildResponse((b) => b.parameter('id'))
    .toString();

  const variables = { path: getSessionPath() };
  return UrlHelper.appendBasePath(
    `/pamirs/${moduleName}?query=${encodeURIComponent(gql)}&variables=${encodeURIComponent(
      JSON.stringify(variables)
    )}`
  );
}

5.2 方式一:<img src> 页面预览

<template>
  <img :src="imageUrl" alt="业务图片预览" />
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';

const props = defineProps<{ businessId: string; imageType: string }>();
const imageUrl = ref('');

watch(
  () => [props.businessId, props.imageType],
  async () => {
    imageUrl.value = await buildImageUrl({
      moduleName: 'order',
      queryModel: 'businessImageRequest',
      businessId: props.businessId,
      imageType: props.imageType,
      download: false   // inline,供 img 内联渲染
    });
  },
  { immediate: true }
);
</script>

5.3 方式二:window.open 下载图片

async function downloadBusinessImage(businessId: string, imageType: string) {
  const url = await buildImageUrl({
    moduleName: 'order',
    queryModel: 'businessImageRequest',
    businessId,
    imageType,
    download: true    // attachment,触发浏览器下载
  });
  window.open(url, '_blank');
}

6. GQL 请求示例

6.1 预览(download: false

query {
  businessImageRequestQuery {
    renderBusinessImage(businessImageRequest: {
      businessId: "ORD-20260702-001"
      imageType: "BARCODE"
      download: false
    }) {
      id
    }
  }
}

6.2 下载(download: true

query {
  businessImageRequestQuery {
    renderBusinessImage(businessImageRequest: {
      businessId: "ORD-20260702-001"
      imageType: "BARCODE"
      download: true
    }) {
      id
    }
  }
}

6.3 完整 GET URL(解码后示意)

/{basePath}/pamirs/order?query=query{businessImageRequestQuery{renderBusinessImage(businessImageRequest:{businessId:"ORD-20260702-001",imageType:"BARCODE",download:false}){id}}}&variables={"path":"order.OrderList#search"}

7. 注意事项

7.1 URL 长度

浏览器与网关对 GET URL 长度有限制(通常 2KB~8KB)。图片请求参数应保持精简(businessIdimageType 等标量字段),复杂渲染逻辑在后端按 ID 自行查数。

7.2 响应类型

场景 download Content-Type Content-Disposition
<img src> 预览 false image/png inline;filename=...
window.open 下载 true image/png attachment;filename=...

7.3 鉴权

GET 请求依赖同源 Cookievariables.path 用于恢复视图会话;若 Function 不依赖会话上下文,可按需简化。

7.4 本地开发代理

本地开发时 /pamirs 需代理到后端,例如在 Vue CLI vue.config.js 中:

proxy: {
  '/pamirs': { target: 'http://your-backend-host:port', changeOrigin: true }
}

8. 实施检查清单

  • [ ] 后端:BusinessImageRequest 模型 + renderBusinessImage Query Function(写 HttpServletResponse
  • [ ] 后端:download 字段控制 inline / attachment
  • [ ] 前端:公共 buildImageUrl 方法(GQL.query 构造 + encodeURIComponent
  • [ ] 前端:variables.path = getSessionPath()
  • [ ] 前端:UrlHelper.appendBasePath 拼接 BASE_PATH
  • [ ] 联调:dev 代理 /pamirs 到后端
  • [ ] 验证:<img> 内联预览、window.open 下载、未登录拦截

9. 源码索引

9.1 前端(Oinone 框架)

说明 路径
查询型文件下载 URL @oinone/kunlun-vue-ui-common/.../UploadService.ts (generatorDownloadUrl)
GQL Builder @oinone/kunlun-request/.../gql.ts
URL 工具 @oinone/kunlun-shared/.../UrlHelper.ts

9.2 后端(oinone-pamirs)

说明 路径
GET/POST 网关 pamirs-framework/pamirs-gateways-graph-java/.../RequestController.java
会话准备 pamirs-framework/pamirs-gateways-graph-java/.../RequestHelper.java
查询型文件流(首选参考) pamirs-core/pamirs-file2/pamirs-file2-api/.../FileAction.java (downloadFormData)
Excel 同步写流(机制参考) pamirs-core/pamirs-file2/pamirs-file2-api/.../ExcelFileServiceImpl.java (doExportSync)
GET URL 编码测试 pamirs-core/pamirs-file2/pamirs-file2-core/.../RequestGetUrlTest.java

10. 相关文档

Oinone社区 作者:nation原创文章,如若转载,请注明出处:https://doc.oinone.top/kai-fa-shi-jian/25587.html

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

Like (0)
nation's avatarnation数式员工
Previous 2026年3月31日 pm11:21
Next 2026年7月27日

相关推荐

  • Oinone项目引入Nacos作为配置中心

    Oinone项目引入nacos作为配置中心 Oinone项目配置默认读取的项目中yml文件(application-xxx.yml), 实际项目中有可能要求项目的配置放到Nacos配置中心中; Oinone默认引入的nacos-client-1.4.1,低于1.4.1的版本不支持认证配置;1.4.1的客户端版本支持Nacos服务端1.x的和2.x的版本; 一、项目中增加依赖 项目主pom引入依赖(最新版平台已默认引入), Nacos版本要求1.4.1以上,低版本不支持认证配置 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2021.1</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.4.1</version> </dependency> 项目的boot工程的pom引入依赖(最新版平台已默认引入) <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> 二、项目工程bootstrap配置 bootstrap.yml文件的配置修改为: spring: application: name: hr-simple # 替换为实际服务名 profiles: active: wx # 指定 profile cloud: config: enabled: false nacos: discovery: enabled: false config: server-addr: 127.0.0.1:8848 enabled: true # namespace: # 如果使用 public 命名空间,建议省略此行 # namespace: your-custom-namespace-id # 如果使用自定义命名空间,填写其 ID group: DEFAULT_GROUP # prefix: # 通常省略,使用默认的 spring.application.name file-extension: yaml # 推荐使用 yaml,而不是 yml (虽然通常兼容) timeout: 5000 #【可选】修改为和nacos一致即可(如果服务端未开启可以不用配置) # username: wangxian # password: wangxian 三、Naocs服务端配置 在Nacos服务端的对应的namespace(5a8b3710-a9a2-4f7c-932f-50f326cb1ccf)下增加配置,把原本配置在代码中的(application-xxx.yml)配置到Nacos中

    2024年2月28日
    2.1K00
  • 集成开放-开放接口如何鉴权加密

    使用前提 已经阅读过文档【oinone 7天从入门到精通】的6.2章节-集成平台 已经依赖了内置模块集成平台eip boot启动工程pom.xml新增jar依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-eip2-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-eip2-view</artifactId> </dependency> 配置文件application.yml新增启动依赖模块 pamirs: boot: modules: – eip eip: open-api: enabled: true route: # 开放接口访问IP,开放外网可以配置为0.0.0.0 host: 127.0.0.1 # 开放接口访问端口 port: 8094 # 认证Token加密的AES密钥 aes-key: NxDZUddmvdu3QQpd5jIww2skNx6U0w0uOAXj3NUCLu8= 一、新增开放接口示例代码 开放接口类定义 package pro.shushi.pamirs.demo.api.open; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; @Fun(TestOpenApiModelService.FUN_NAMESPACE) public interface TestOpenApiModelService { String FUN_NAMESPACE = "demo.open.TestOpenApiModelService"; @Function TestOpenApiModel queryById(Long id); } 开放接口实现类 package pro.shushi.pamirs.demo.core.open; import org.apache.camel.ExtendedExchange; import org.springframework.stereotype.Component; import pro.shushi.pamirs.core.common.SuperMap; import pro.shushi.pamirs.demo.api.open.TestEipConfig; import pro.shushi.pamirs.demo.api.open.TestOpenApiModel; import pro.shushi.pamirs.demo.api.open.TestOpenApiModelService; import pro.shushi.pamirs.demo.api.open.TestOpenApiResponse; import pro.shushi.pamirs.eip.api.IEipContext; import pro.shushi.pamirs.eip.api.annotation.Open; import pro.shushi.pamirs.eip.api.constant.EipFunctionConstant; import pro.shushi.pamirs.eip.api.enmu.EipExpEnumerate; import pro.shushi.pamirs.eip.api.entity.openapi.OpenEipResult; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.common.exception.PamirsException; import java.util.Optional; @Fun(TestOpenApiModelService.FUN_NAMESPACE) @Component public class TestOpenApiModelServiceImpl implements TestOpenApiModelService { @Override @Function public TestOpenApiModel queryById(Long id) { return new TestOpenApiModel().queryById(id); } @Function @Open @Open.Advanced( authenticationProcessorFun = EipFunctionConstant.DEFAULT_AUTHENTICATION_PROCESSOR_FUN, authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE ) public OpenEipResult<TestOpenApiResponse> queryById4Open(IEipContext<SuperMap> context , ExtendedExchange exchange) { String id = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("id"))).orElse(""); TestOpenApiModel temp = queryById(Long.valueOf(id)); TestOpenApiResponse response = new TestOpenApiResponse(); if(temp != null ) { response.setAge(temp.getAge()); response.setId(temp.getId()); response.setName(temp.getName()); }else{ response.setAge(1); response.setId(1L); response.setName("oinone eip test"); } OpenEipResult<TestOpenApiResponse> result = new OpenEipResult<TestOpenApiResponse>(response); return result; } @Function @Open(config = TestEipConfig.class,path = "error") @Open.Advanced( httpMethod = "post", authenticationProcessorFun = EipFunctionConstant.DEFAULT_AUTHENTICATION_PROCESSOR_FUN, authenticationProcessorNamespace…

    2024年7月25日
    1.6K00
  • Oinone如何支持构建分布式项目

    分布式调用下的[强制]约束 1、[强制]分布式调用情况下base库和redis需共用;2、[强制]如果环境有设计器,设计器的base库和redis保持一致也需与项目中的保持一致;3、[强制]相同base库下,不同应用的相同模块的数据源需保持一致;4、[强制]项目中需引入分布式缓存包。参考下文的分布式包依赖 分布式支持 1、分布式包依赖 1) 父pom的依赖管理中先加入pamirs-distribution的依赖 <dependency> <groupId>pro.shushi.pamirs</groupId> <artifactId>pamirs-distribution</artifactId> <version>${pamirs.distribution.version}</version> <type>pom</type> <scope>import</scope> </dependency> 2) 启动的boot工程中增加pamirs-distribution相关包 <!– 分布式服务发布 –> <dependency> <groupId>pro.shushi.pamirs.distribution</groupId> <artifactId>pamirs-distribution-faas</artifactId> </dependency> <!– 分布式元数据缓存 –> <dependency> <groupId>pro.shushi.pamirs.distribution</groupId> <artifactId>pamirs-distribution-session</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.distribution</groupId> <artifactId>pamirs-distribution-gateway</artifactId> </dependency> 3)启动工程的Application中增加类注解@EnableDubbo @EnableDubbo public class XXXStdApplication { public static void main(String[] args) throws IOException { StopWatch stopWatch = new StopWatch(); stopWatch.start(); // ……………………………… log.info("XXXX Application loading…"); } } 2、修改bootstrap.yml文件 注意序列化方式:serialization: pamirs 以下只是一个示例(zk为注册中心),注册中心支持zk和Nacos;Nacos作为注册中心参考:https://doc.oinone.top/kai-fa-shi-jian/5835.html spring: profiles: active: dev application: name: pamirs-demo cloud: service-registry: auto-registration: enabled: false pamirs: default: environment-check: true tenant-check: true — spring: profiles: dev cloud: service-registry: auto-registration: enabled: false config: enabled: false uri: http://127.0.0.1:7001 label: master profile: dev nacos: server-addr: http://127.0.0.1:8848 discovery: enabled: false namespace: prefix: application file-extension: yml config: enabled: false namespace: prefix: application file-extension: yml dubbo: application: name: pamirs-demo version: 1.0.0 registry: address: zookeeper://127.0.0.1:2181 protocol: name: dubbo port: -1 serialization: pamirs scan: base-packages: pro.shushi cloud: subscribed-services: metadata-report: disabled: true 3、模块启动的最⼩集 pamirs: boot: init: true sync: true modules: – base – sequence – 业务工程的Module 4、业务模型间的依赖关系 服务调用方(即Client端),在启动yml中modules不安装服务提供方的Module 服务调用方(即Client端),项目的pom中只依赖服务提供方的API(即模型和API的定义) 服务调用方(即Client端),项目模块定义(即模型Module定义),dependencies中增加服务提供方的Modeule. 如下面示例代码中的FileModule @Module( name = DemoModule.MODULE_NAME, displayName = "oinoneDemo工程", version = "1.0.0", dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, FileModule.MODULE_MODULE, SecondModule.MODULE_MODULE/**服务提供方的模块定义*/ } )…

    2024年2月20日
    1.4K00
  • Oinone协同开发使用手册

    概述 Oinone平台为开发人员提供了本地环境 – 测试环境之间的协同开发模式,可以使得开发人员在本地环境中设计的模型、函数等元数据实时被测试环境使用并设计。开发人员开发完成对应页面和功能后,可以部署至测试环境直接进行测试。 本篇文章将详细介绍协同开发模式在实际开发中的应用及相关内容。 名词解释 本地环境: 开发人员的本地启动环境 测试环境: 在测试服务器上部署的业务测试环境,业务工程服务和设计器服务共用中间件 业务工程服务:在测试服务器上部署的业务工程 设计器服务: 在测试服务器上部署的设计器镜像 一套环境:以测试环境为例,业务工程服务和设计器服务共同组成一套环境 生产环境: 在生产服务器上部署的业务生产环境 环境准备 部署了一个可用的设计器服务,并能正常访问。(需参照下文启动设计器环境内容进行相应修改) 准备一个用于开发的java工程。 准备一个用于部署测试环境的服务器。 协同参数介绍 用于测试环境的参数 -PmetaProtected=${value} 启用元数据保护,只有配置相同启动参数的服务才允许对元数据进行更新。通常该命令用于设计器服务和业务工程服务,并且两个环境需使用相同的元数据保护标记(value)进行启动。本地环境不使用该命令,以防止本地环境在协同开发时意外修改测试环境元数据,导致元数据混乱。 用法 java -jar boot.jar -PmetaProtected=pamirs 用于本地环境的配置 使用命令配置ownSign(推荐) java -jar boot.jar –pamirs.distribution.session.ownSign=demo 使用yaml配置ownSign pamirs: distribution: session: allMetaRefresh: false # 启用元数据全量刷新(备用配置,如遇元数据错误或混乱,启用该配置可进行恢复,使用一次后关闭即可) ownSign: demo # 协同开发元数据隔离标记,用于区分不同开发人员的本地环境,其他环境不允许使用 启动设计器环境 docker-run启动 -e PROGRAM_ARGS=-PmetaProtected=pamirs docker-compose启动 services: backend: container_name: designer-backend image: harbor.oinone.top/oinone/designer-backend-v5.0 restart: always environment: # 指定spring.profiles.active ARG_ENV: dev # 指定-Plifecycle ARG_LIFECYCLE: INSTALL # jvm参数 JVM_OPTIONS: "" # 程序参数 PROGRAM_ARGS: "-PmetaProtected=pamirs" PS: java [JVM_OPTIONS?] -jar boot.jar [PROGRAM_ARGS?] 开发流程示例图 具体使用步骤详见协同开发支持

    2024年7月24日
    2.6K00
  • 首次登录修改密码和自定义密码规则等

    场景描述 在某些场景下,可能需要实现 用户首次登录强制修改密码的功能,或者存在修改平台默认密码等校验规则等需求;本文将讲解不改变平台代码的情况下,如何实现这些功能需求。 首次登录修改密码 方案概述 自定义User增加是否是第一次登录的属性,登录后执行一个扩展点。 判断是否是一次登录,如果是则返回对应的状态码,前端根据状态码重定向到修改密码的页面。修改完成则充值第一次登录的标识。 PS:首次登录的标识平台前端已默认实现 扩展PamirsUser(例如:DemoUser) /** * @author wangxian */ @Model.model(DemoUser.MODEL_MODEL) @Model(displayName = "用户", labelFields = {"nickname"}) @Model.Advanced(index = {"companyId"}) public class DemoUser extends PamirsUser { public static final String MODEL_MODEL = "demo.DemoUser"; @Field.Integer @Field.Advanced(columnDefinition = "bigint DEFAULT '0'") @Field(displayName = "公司ID", invisible = true) private Long companyId; /** * 默认true->1 */ @Field.Boolean @Field.Advanced(columnDefinition = "tinyint(1) DEFAULT '1'") @Field(displayName = "是否首次登录") private Boolean firstLogin; } 定义扩展点接口(实际项目按需要增加和删减接口的定义) import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; @Ext(PamirsUserTransient.class) public interface PamirsUserTransientExtPoint { @ExtPoint PamirsUserTransient loginAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient loginCustomAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient firstResetPasswordAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient firstResetPasswordBefore(PamirsUserTransient user); @ExtPoint PamirsUserTransient modifyCurrentUserPasswordAfter(PamirsUserTransient user); @ExtPoint PamirsUserTransient modifyCurrentUserPasswordBefore(PamirsUserTransient user); } 编写扩展点实现(例如:DemoUserLoginExtPoint) @Order(0) @Component @Ext(PamirsUserTransient.class) @Slf4j public class DemoUserLoginExtPoint implements PamirsUserTransientExtPoint { @Override @ExtPoint.Implement public PamirsUserTransient loginAfter(PamirsUserTransient user) { return checkFirstLogin(user); } private PamirsUserTransient checkFirstLogin(PamirsUserTransient user) { //首次登录需要修改密码 Long userId = PamirsSession.getUserId(); if (userId == null) { return user; } DemoUser companyUser = new DemoUser().queryById(userId); // 判断用户是否是第一次登录,如果是第一次登录,需要返回错误码,页面重新向登录 Boolean isFirst = companyUser.getFirstLogin(); if (isFirst) { //如果是第一次登录,返回一个标识给前端。 // 首次登录的标识平台已默认实现 user.setBroken(Boolean.TRUE); user.setErrorCode(UserExpEnumerate.USER_FIRST_LOGIN_ERROR.code()); return user; } return user; } @Override public PamirsUserTransient loginCustomAfter(PamirsUserTransient user) { return checkFirstLogin(user); } @Override…

    2024年5月25日
    5.9K00

Leave a Reply

Please Login to Comment