4.4 Oinone的分布式体验进阶

在分布式开发中,每个人基本只负责自己相关的模块开发。所以每个研发就都需要一个环境,比如一般公司会有(N个)项目环境、1个日常环境、1个预发环境、1个线上环境。在整项目环境的时候就特别麻烦,oinone的好处是在于每个研发可以通过boot工程把需要涉及的模块都启动在一个jvm中进行开发,并不依赖任何环境,在项目开发中,特别方便。但当公司系统膨胀到一定规模,大到很多人都不知道有哪些模块,或者公司出于安全策略考虑,或者因为启动速度的原因(毕竟模块多了启动的速度也会降下来)。本文就给大家介绍oinone与经典分布式组织模式的兼容性

一、模块启动的最小集

我们来改造SecondModule模块,让该模块的用户权限相关都远程走DemoModule

Step1 修改SecondModule的启动工程application-dev.yml文件

除了base、second_core两个模块保留,其他模块都去除了。

pamirs:
    boot:
    init: true
    sync: true
    modules:
      - base
      - second_core

图4-4-1 SecondModule的application-dev.yml仅配置两个模块

Step2 去除boot工程的依赖

去除SecondModule启动工程的pom依赖

<!-- <dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-resource-core</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-user-core</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-auth-core</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-message-core</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-international</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-business-core</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-apps-core</artifactId>
</dependency> -->

图4-4-2 去除boot工程多余的依赖

Step3 重启SecondModule

这【远程模型】和【远程代理】均能访问正常

image.png

图4-4-3 远程模型和远程代理菜单均能访问正常

Step4 SecondModule增加对模块依赖

我们让SecondModule增加用户和权限模块的依赖,期待效果是:SecondModule会对用户和权限的访问都会走Dome应用,因为Demo模块的启动工程中包含了user、auth模块。

  1. 修改pamirs-second-api的pom文件增加对user和auth的api包依赖
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-user-api</artifactId>
</dependency>
<dependency>
  <groupId>pro.shushi.pamirs.core</groupId>
  <artifactId>pamirs-auth-api</artifactId>
</dependency>

图4-4-4 修改pamirs-second-api的pom文件

  1. 修改SecondModule类,增加依赖定义
@Module(
    dependencies = {ModuleConstants.MODULE_BASE, AuthModule.MODULE_MODULE, UserModule.MODULE_MODULE}
)

图4-4-5 配置SecondModule的依赖

Step5 修改RemoteTestModel模型

为RemoteTestModel模型增加user字段

@Field.many2one
@Field(displayName = "用户")
private PamirsUser user;

图4-4-6 为RemoteTestModel模型增加user字段

Step6 重启系统看效果

  1. mvn install pamirs-second工程,因为需要让pamirs-demo工程能依赖到最新的pamirs-second-api包

  2. 重启pamirs-second和pamirs-demo

  3. 两个页面都正常

image.png

图4-4-7 示例效果一

image.png

图4-4-8 示例效果二

二、PmetaOnline的NEVER指令(开发时环境共享)

我们在4.1.2【模块之启动指令】一文中介绍过 “-PmetaOnline指令”,该参数用于设置元数据在线的方式,如果不使用该参数,则profile属性的默认值请参考服务启动可选项。-PmetaOnline参数可选项为:

  • NEVER - 不持久化元数据,会将pamirs.boot.options中的updateModule、reloadMeta和updateMeta属性设置为false
  • MODULE - 只注册模块信息,会将pamirs.boot.options中的updateModule属性设置为true,reloadMeta和updateMeta属性设置为false
  • ALL - 注册持久化所有元数据,会将pamirs.boot.options中的updateModule、reloadMeta和updateMeta属性设置为true

oinone的默认模式下元数据都是注册持久化到DB的,但当我们在分布式场景下新开发模块或者对已有模块进行本地化开发时,做为开发阶段我们肯定是希望复用原有环境,但不对原有环境照成影响。那么-PmetaOnline就很有意义。让我们还没有经过开发自测的代码产生的元数据仅限于开发本地环境,而不是直接影响整个大的项目环境

PmetaOnline指令设置为NEVER(举例)

Step1 为DemoCore新增一个DevModel模型

package pro.shushi.pamirs.demo.api.model;

import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;

@Model.model(DevModel.MODEL_MODEL)
@Model(displayName = "开发阶段模型",summary="开发阶段模型,当PmetaOnline指令设置为NEVER时,本地正常启动但元数据不落库",labelFields={"name"})
public class DevModel  extends AbstractDemoCodeModel{

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

    @Field(displayName = "名称")
    private String name;

}

图4-4-9 为DemoCore新增一个DevModel模型

Step2 为DevModel模型配置菜单

@UxMenu("开发模型")@UxRoute(DevModel.MODEL_MODEL) class DevModelProxyMenu{}

图4-4-10 为DevModel模型配置菜单

Step3 启动Demo应用时指定-PmetaOnline

image.png

图4-4-11 启动Demo应用时指定-PmetaOnline

Step4 重启系统看效果

  1. 查看元数据

image.png图4-4-12 DB查看元数据变化

  1. 菜单与页面能正常操作

image.png

图4-4-13 开发模型菜单可正常操作

image.png

图4-4-14 开发模型详情页面可正常操作

Step5 Never模式需注意的事项

  1. 业务库需设定为本地开发库,这样才不会影响公共环境,因为对库表结构的修改还是会正常进行的

  2. 如果不小心影响了公共环境,需要对公共环境进行重启恢复

  3. 系统新产生的元数据(如:例子中的【开发模式】菜单)不受权限管控

三、分布式开发约定

设计约定

  1. 跨模块的存储模型间继承,在部署时需要跟依赖模块配置相同数据源。这个涉及模块规划问题,比如业务上的user扩展模块,需要跟user模块一起部署。

  2. 跨模块的多对多关系,配置中间表模型,在3.3.9【字段类型之关系与引用】一文中的“M2M字段”部分有详细介绍。

  3. 基础依赖:base。

  4. canal相关参考:4.1.10【函数之触发与定时】中提到的修改topic方案。

编码约定

  1. 跨模块间用service来调用,避免直接调用模型的数据管理器(如new 非本模块Model().queryPage等)。结构性代码可以用oinone提供研发辅助工具进行提效率

  2. 在3.4.1【构建第一个Function】一文中有提到,需要在API包中声明注解了@Fun注解的函数接口,并在接口的方法上加上@Function注解。

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9311.html

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

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日
下一篇 2024年5月23日

相关推荐

  • 企业个性化配置

    平台除了帮助企业快速解决业务需求外,还提供了企业文化、风格个性化能力,本章节详细介绍个性化配置。包括:登陆页配置、企业形象配置、系统风格配置。 系统配置 配置入口 配置入口:右上角登陆账号——系统配置——全局配置——登陆页配置、企业形象配置、系统风格配置。 登陆页配置 用户自定义登陆页的主题,一共有六个主题可选择,分别为: 图一:左侧是图片,右侧是登录输入框,Logo在左上角; 图二:右左侧是图片,右侧是登录输入框,Logo在左上角; 图三:底图是图片,登录输入框浮在页面左侧,Logo在左上角; 图四:底图是图片,登录输入框浮在页面中间,Logo在左上角; 图五:底图是图片,登录输入框浮在页面右侧,Logo在左上角; 图六:底图是图片,登录输入框浮在页面中间,Logo在登录框上方。 页面背景 上传登录页背景,支持上传jpg、png格式的图片或mp4、mov、avi格式视频。尺寸建议为1920*1080px,文件大小不超过50MB。 登录页logo:指配置登录页左上角的logo图标。 预览:配置后可在右侧电脑中点击全屏预览效果。 配置示例 企业形象配置 企业形象配置:可设置企业信息、业务应用导航栏logo、浏览器logo,设置完成之后,点击发布之后设置生效。 系统风格配置 系统风格配置,可设置主题(浅色、深色)、尺寸(大、中、小),配置后可全屏预览,发布后即可更换成对应的风格。 下载代码可进行自定义风格。

    2024年6月20日
    1.1K00
  • 第1章 揭开面纱,理解Oinone

    本章旨在从以下几个维度逐步揭开Oinone的面纱,让大家了解Oinone的初心与愿景,以及它是如何站在软件领域的巨人肩膀上,结合企业数字化转型的深入,形成全新的理念,帮助企业完成数字化转型。 具体来说,本章会从以下四个方面逐一展开: Oinone的初心与愿景:结合中国软件行业的发展与自身职业发展经历,探讨Oinone为何诞生以及其愿景是什么。 Oinone致敬西方软件行业的新贵odoo:介绍Oinone的灵感来源,探究Oinone与odoo的异同,以及如何从odoo中汲取经验。 从企业转型困境,引出Oinone新的思路:通过剖析企业数字化转型的困境,引出Oinone提出的全新思路,以及如何应对企业数字化转型的挑战。 行业对比,让您从不同视角理解Oinone:通过与同行业产品进行对比,从不同的视角深入理解Oinone的特点和优势。

    Oinone 7天入门到精通 2024年5月23日
    2.6K00
  • 4.1.12 函数之内置函数与表达式

    本文意在列全所有内置函数与表达式,方便大家查阅。 一、内置函数 内置函数是系统预先定义好的函数,并且提供表达式调用支持。 通用函数 数学函数 表达式 名称 说明 ABS 绝对值 函数场景: 表达式函数示例: ABS(number)函数说明: 获取number的绝对值 FLOOR 向下取整 函数场景: 表达式函数示例: FLOOR(number)函数说明: 对number向下取整 CEIL 向上取整 函数场景: 表达式函数示例: CEIL(number)函数说明: 对number向上取整 ROUND 四舍五入 函数场景: 表达式函数示例: ROUND(number)函数说明: 对number四舍五入 MOD 取余 函数场景: 表达式函数示例: MOD(A,B)函数说明: A对B取余 SQRT 平方根 函数场景: 表达式函数示例: SQRT(number) 函数说明: 对number平方根 SIN 正弦 函数场景: 表达式函数示例: SIN(number)函数说明: 对number取正弦 COS 余弦 函数场景: 表达式函数示例: COS(number)函数说明: 对number取余弦 PI 圆周率 函数场景: 表达式函数示例: PI() 函数说明: 圆周率 ADD 相加 函数场景: 表达式函数示例: ADD(A,B)函数说明: A与B相加 SUBTRACT 相减 函数场景: 表达式函数示例: SUBTRACT(A,B)函数说明: A与B相减 MULTIPLY 乘积 函数场景: 表达式函数示例: MULTIPLY(A,B)函数说明: A与B相乘 DIVIDE 相除 函数场景: 表达式函数示例: DIVIDE(A,B)函数说明: A与B相除 MAX 取最大值 函数场景: 表达式函数示例: MAX(collection) 函数说明: 返回集合中的最大值,参数collection为集合或数组 MIN 取最小值 函数场景: 表达式函数示例: MIN(collection) 函数说明: 返回集合中的最小值,参数collection为集合或数组 SUM 求和 函数场景: 表达式函数示例: SUM(collection)函数说明: 返回对集合的求和,参数collection为集合或数组 AVG 取平均值 函数场景: 表达式函数示例: AVG(collection)函数说明: 返回集合的平均值,参数collection为集合或数组 COUNT 计数 函数场景: 表达式函数示例: COUNT(collection)函数说明: 返回集合的总数,参数collection为集合或数组 UPPER_MONEY 大写金额 函数场景: 表达式函数示例: UPPER_MONEY(number)函数说明: 返回金额的大写,参数number为数值或数值类型的字符串 表4-1-12-1 数学函数 文本函数 表达式 名称 说明 TRIM 空字符串过滤 函数场景: 表达式函数示例: TRIM(text)函数说明: 去掉文本字符串text中的首尾空格,文本为空时,返回空字符串 IS_BLANK 是否为空字符串 函数场景: 表达式函数示例: IS_BLANK(text)函数说明: 判断文本字符串text是否为空 STARTS_WITH 是否以指定字符串开始 函数场景: 表达式函数示例: STARTS_WITH(text,start)函数说明: 判断文本字符串text是否以文本字符串start开始,文本为空时,按照空字符串处理 ENDS_WITH 是否以指定字符串结束 函数场景: 表达式函数示例: ENDS_WITH(text,start)函数说明: 判断文本字符串text是否以文本字符串end结束,文本为空时,按照空字符串处理 CONTAINS 包含 函数场景: 表达式函数示例: CONTAINS(text,subtext)函数说明: 判断文本字符串text是否包含文本字符串subtext,文本text为空时,按照空字符串处理 LOWER 小写 函数场景: 表达式函数示例: LOWER(text)函数说明: 小写文本字符串text,文本为空时,按照空字符串处理 UPPER 大写 函数场景: 表达式函数示例: UPPER(text)函数说明: 大写文本字符串text,文本为空时,按照空字符串处理 REPLACE 替换字符串 函数场景: 表达式函数示例: REPLACE(text,oldtext,newtext)函数说明: 使用文本字符串newtext替换文本字符串text中的文本字符串oldtext…

    Oinone 7天入门到精通 2024年5月23日
    1.1K00
  • 7.3.1 去除资源上传大小限制

    场景说明 全员营销标准软件产品对于视频上传限制为35M,该大小适合管理短视频类的素材。该软件产品的使用方为营销部门,营销部门不仅管理短视频素材,也负责管理电商商品的视频素材、公司团建的素材等,且不少素材的内存都远大于35M。 业务需求:将全员营销中的内容中心扩展成营销部门的内容统一管理中心,管理营销部门工作范围内的所有内容素材,且不受35M的限制。 实战训练 新增一个资源管理页面,替换原来的资源管理,并构建新的上传行为。 Step1 通过界面设计器,设计出必要管理页面 进入界面设计器,应用选择全员营销,模型通过搜索【素材】选择【Gemini素材】,点击添加页面下的直接创建 设置页面标题、模型(自动带上可切换)、业务类型(运营管理后续会扩展其他类型)、视图类型(表格)后点击确认按钮进入【内容中心-新素材管理】设计页面 进入页面设计器,对【内容中心-新素材管理】表格页面进行设计(更多细节介绍,请参考界面设计产品使用手册) a. 左侧为物料区:分为组件、模型。 ⅰ. 【组件】选项卡下为通用物料区,我们可以为页面增加对应布局、字段(如同在模型设计器增加字段)、动作、数据、多媒体等等 ⅱ. 【模型】选项卡下为页面对应模型的自定义字段、系统字段、以及模型已有动作 b. 中间是设计区域 c. 右侧为属性面板,在设计区域选择中组件会显示对应组件的可配置参数 在左侧【模型】选项卡下,分别把系统字段中的【素材名称】、【素材链接】、【素材来源】、【素材类型】、【更新时间】、【创建时间】等字段拖入设计区域的表格区 设置字段在表格中的展示组件,在设计区域切换【素材链接】展示组件为【超链接】 设置字段在表格中的展示组件的属性,在设计区域选中【素材名称】,在右侧属性面板中设置【标题】为【内容名称】 设置字段在表格中的展示组件的属性,在设计区域选中【创建时间】,在右侧属性面板中设置【标题】为【上传时间】 在左侧【模型】选项卡下,把动作分类下的提交类型【下载】和【删除】动作拖入中间设计区的动作区,并选择【删除】按钮,在右侧属性面板中设置【按钮样式】为【次要按钮】 在左侧【组件】选项卡下,把动作分类下的【跳转动作】拖入中间设计区的动作区,并在右侧属性面板中设置动作名称为【上传素材】,数据控制类型为【不进行数据处理】,打开方式为【弹窗打开】,【弹出内容】为【创建新页面】,【弹窗模型】通过搜索【素材】选择【Gemini素材代理-上传】,并点击保存 设计区选中【上传素材】按钮,点击【打开弹窗】设计【素材上传】的操作页面,此时会发现左侧【模型】选项卡下的当前模型切换成了【Gemini素材代理-上传】 a. 分别把系统字段中的【上传素材链接列表】拖入【Gemini素材代理-上传】的弹窗页面设计区。 b. 选中【上传素材链接列表】切换展示组件为【文件上传】 c. 选中【上传素材链接列表】并在右侧属性面板中 ⅰ. 设置【校验】分组下,设置【最大上传文件体积】为空,即不设置 ⅱ. 设置【校验】分组下,设置【限制上传文件类型】为打开,并勾选【图片】和【视频】 ⅲ. 设置【交互】分组下的宽度属性为【1】 在左侧【模型】选项卡下,把动作分类下的提交类型【上传素材】动作拖入中间设计区的动作区 在左侧【组件】选项卡下,把动作分类下的【客户端动作】类型拖入中间设计区的动作区,选中并在右侧属性面板中设置【动作名称】为返回,设置【客户端行为】为【关闭弹窗】,点击【保存】按钮来完成动作的基础设置 选中【返回】按钮、并在右侧属性面板中设置【按钮样式】为【次要按钮】 关闭弹窗返回主模型设计区 点击右上角【显示母版】进入页面最终展示形式,点击添加菜单项,并在输入框中输入【新内容中心】 点击菜单右侧设置图标,选择【绑定已有页面】,进行菜单与页面的绑定操作 在绑定页面中,模型选择【Gemini素材】,视图选择【内容中心-新素材管理】,点击确认按钮提交 最后别忘了点击右上角【发布】按钮对【内容中心-新素材管理】表格页面进行发布,回到界面设计器首页查看刚刚建好的表格页面 Step2 测试完成以后隐藏原【内容中心】菜单 进入【界面设计器】管理页面,通过点击【设计图标】进入任一页面的设计页面 点击右上角【显示母版】进入页面最终展示形式,找菜单【内容中心】点击菜单右侧设置图标,选择【隐藏菜单】,因为【内容中心】菜单是标准产品自带菜单,只能进行隐藏操作,无法进行如绑定页面和调整菜单顺序 Step3 回到全员营销应用,刷新页面体验效果 整个实战训练就到此结束,更多细节请参阅具体的产品使用手册

    2024年5月23日
    89200
  • 3.4.3.3 SPI机制-扩展点

    扩展点结合拦截器的设计,oinone可以点、线、面一体化管理Function 扩展点用于扩展函数逻辑。扩展点类似于SPI机制(Service Provider Interface),是一种服务发现机制。这一机制为函数逻辑的扩展提供了可能。 一、构建第一个扩展点 自定义扩展点(举例) 在我们日常开发中,随着对业务理解的深入,往往还在一些逻辑中会预留扩展点,以便日后应对不同需求时可以灵活替换某一小块逻辑。 在3.3.4【模型的继承】一文中的PetCatItemQueryService,是独立新增函数只作公共逻辑单元。现在我们给它的实现类增加一个扩展点。在PetCatItemQueryServiceImpl的queryPage方法中原本会先查询PetCatType列表,我们这里假设这个逻辑随着业务发展未来会发生变化,我们可以预先预留【查询萌猫类型扩展点】 Step1 新增扩展点定义PetCatItemQueryCatTypeExtpoint 扩展点命名空间:在接口上用@Ext声明扩展点命名空间。会优先在本类查找@Ext,若为空则往接口向上做遍历查找,返回第一个查找到的@Ext.value注解值,使用该值再获取函数的命名空间;如果未找到,则返回扩展点全限定类名。所以我们这里扩展点命名空间为:pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint 扩展点技术名称:先取@ExtPoint.name,若为空则取扩展点接口方法名。所以我们这里技术名为queryCatType package pro.shushi.pamirs.demo.api.extpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import java.util.List; @Ext public interface PetCatItemQueryCatTypeExtpoint { @ExtPoint(displayName = "查询萌猫类型扩展点") List<PetCatType> queryCatType(); } 图3-4-3-11 新增扩展点定义PetCatItemQueryCatTypeExtpoint Step2 修改PetCatItemQueryServiceImpl(用Ext.run模式调用) 修改queryPage,增加扩展点的使用代码。扩展点的使用有两种方式 方法一,使用命名空间和扩展点名称调用Ext.run(namespace, fun, 参数); 方法二,使用函数式接口调用Ext.run(函数式接口, 参数); 我们这里用了第二种方式 用PetCatItemQueryCatTypeExtpoint的全限定类名作为扩展点的命名空间(namespace) 用queryCatType的方法名作为扩展点的技术名称(name) 根据namespace+name去找到匹配扩展点实现,并根据规则是否匹配,以及优先级唯一确定一个扩展点实现去执行逻辑 package pro.shushi.pamirs.demo.core.service; ……省略依赖包 @Model.model(PetCatItem.MODEL_MODEL) @Component public class PetCatItemAction extends DataStatusBehavior<PetCatItem> { @Override protected PetCatItem fetchData(PetCatItem data) { return data.queryById(); } @Action(displayName = "启用") public PetCatItem dataStatusEnable(PetCatItem data){ data = super.dataStatusEnable(data); data.updateById(); return data; } @Function.Advanced(displayName = "查询模型数据的默认过滤条件", type = FunctionTypeEnum.QUERY, managed = true) @Function(openLevel = {LOCAL}) public String queryFilters() { StringBuilder sqlWhereCondition = new StringBuilder(); // List<PetCatType> typeList = new PetCatType().queryList(); List<PetCatType> typeList = Ext.run(PetCatItemQueryCatTypeExtpoint::queryCatType, new Object[]{}); if(!CollectionUtils.isEmpty(typeList)){ // sqlWhereCondition.append("type_id"); sqlWhereCondition.append(PStringUtils.fieldName2Column(LambdaUtil.fetchFieldName(PetCatItem::getTypeId))); sqlWhereCondition.append(StringUtils.SPACE).append(SqlConstants.IN).append(CharacterConstants.LEFT_BRACKET); for(PetCatType petCatType: typeList){ sqlWhereCondition.append(petCatType.getId()).append(CharacterConstants.SEPARATOR_COMMA); } sqlWhereCondition.deleteCharAt(sqlWhereCondition.lastIndexOf(CharacterConstants.SEPARATOR_COMMA)); sqlWhereCondition.append(StringUtils.SPACE).append(CharacterConstants.RIGHT_BRACKET); } return sqlWhereCondition.toString(); } ……省略其他函数 } 图3-4-3-12 修改PetCatItemQueryServiceImpl Step3 新增扩展点实现PetCatItemQueryCatTypeExtpointOne 扩展点命名空间要与扩展点定义一致,用@Ext(PetCatItemQueryCatTypeExtpoint.class) @ExtPoint.Implement声明这是在@Ext声明的命名空间下,且技术名为queryCatType的扩展点实现 package pro.shushi.pamirs.demo.core.extpoint; import pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.meta.api.session.PamirsSession; import java.util.List; @Ext(PetCatItemQueryCatTypeExtpoint.class) public class PetCatItemQueryCatTypeExtpointOne implements PetCatItemQueryCatTypeExtpoint { @Override @ExtPoint.Implement(displayName = "查询萌猫类型扩展点的默认实现") public List<PetCatType> queryCatType() { PamirsSession.getMessageHub().info("走的是第一个扩展点"); List<PetCatType> typeList = new PetCatType().queryList(); return typeList; } } 图3-4-3-13 新增扩展点实现PetCatItemQueryCatTypeExtpointOne Step4…

    2024年5月23日
    1.3K00

Leave a Reply

登录后才能评论