外部函数

为了能够在创建和使用自定义 DMSQL 程序时,使用其他语言实现的接口,DM8 提供了 C、JAVA 外部函数,这样即使外部函数在执行中出现了任何问题,都不会影响到服务器的正常执行。

无论是 C 外部函数还是 JAVA 外部函数,都需要将动态库或 jar 包上传到服务器端或在服务器端编译生成动态库或 jar 包,系统管理员应对动态库和 jar 包进行严格审查,以防止外部函数中包含病毒或恶意代码,引发安全问题。为了保证数据库的安全性和灵活性,DM 提供了 ini 参数 ENABLE_EXTERNAL_CALL 来开关外部函数功能,默认情况下,数据库会关闭外部函数的创建和执行功能。

需要注意的是,DM 不支持 C 或 JAVA 外部函数存放在 ASM 文件系统上的调用。

10.1 C 外部函数

C 外部函数是使用 C、C++ 语言编写,在数据库外编译并保存在.dll、.so 共享库文件中,被用户通过 DMSQL 程序调用的函数。

C 外部函数的执行一般通过代理 dmap 工具进行,此时为了执行 C 外部函数,需要先启动 dmap 服务。dmap 执行程序在 DM8 安装目录的 bin 子目录下,直接执行即可启动 dmap 服务。

同时,DM 结构化的 C 外部函数支持结合 INI 参数 EFC_USE_AP 进行性能优化,当指定参数值为 0 时,结构化的 C 外部函数会在 dmserver 内部调用,不再和 dmap 进行通信,可以提高函数的执行效率。

当用户调用 C 外部函数时,服务器操作步骤如下:首先,确定调用的(外部函数使用的)共享库及函数;然后,通知代理进程工作。代理进程装载指定的共享库,并在函数执行后将结果返回给服务器。

10.1.1 生成动态库

DM8 提供两种方案编写 C 外部函数:

  • DM 结构化参数

该方案中,用户必须使用 DM8 提供的编写 C 外部函数动态库的接口,严格按照如下格式书写外部函数的 C 代码。

C 外部函数格式

de_data 函数名(de_args *args)
{
	C语言函数实现体;
}

参数

  1. <de_data> 返回值类型。de_data 结构体类型如下:
struct de_data{
	int  null_flag;   //参数是否为空,1表示非空,0表示空
	union	       //只支持int、double(或精度大于24的float)、char类型。其中float类型在系统内部被转化为double类型执行,相关接口请使用double类型的接口
    {
		int    v_int;
		double  v_double;
		char   v_str[];
	}data;
};
  1. <de_args> 参数信类型。de_args 结构体类型如下:
struct de_args
{
	int        n_args;      //参数个数
    de_data*      args;        //参数列表
};
  1. < C 语言函数实现体 > C 语言函数对应的函数实现体。

使用说明

  1. C 语言函数的参数可通过调用 DM8 提供的一系列 get 函数得到,同时可调用 set 函数重新设置这些参数的值;
  2. 根据返回值类型,调用不同的 return 函数接口;
  3. 必须根据参数类型、返回值类型,调用相同类型的 get、set 和 return 函数。当调用 de_get_str 和 de_get_str_with_len 得到字符串后,必须调用 de_str_free 释放空间;
  4. DM8 提供的编写 C 外部函数动态库的接口如表 10.1 所示。
表10.1 DM8支持的编写C外部函数动态库的接口
函数类型 函数名 功能说明
get int de_get_int(
de_args *args,
int arg_id
);
第arg_id 参数的数据类型为整型,从参数列表args 中取出第arg_id参数的值
double de_get_double(
de_args *args,
int arg_id
);
第arg_id 参数的数据类型为double 类型,从参数列表args 中取出第arg_id参数的值
char* de_get_str(
de_args *args,
int arg_id);
第arg_id参数的数据类型为字符串类型,从参数列表args中取出第arg_id参数的值。
二者区别:
de_get_str使用服务器自动申请的空间,使用完毕后需要de_str_free释放空间。
de_get_str_with_space使用第三方申请的空间(通过v_str_in和v_str_in_len传入),使用完毕后不需要de_str_free释放空间。
v_str_in为已申请内存的字符串空间,由调用者分配,其大小不能少于v_str_in_len。若已分配长度等于0,v_str_in和返回值均返回NULL。若分配空间不够,则只返回部分字符串。
v_str_in_len为空间长度,包含结尾0。如果v_str_in_len小于实际参数加结尾0长度,则截断输出
char* de_get_str_with_space (
de_args* args,
int arg_id,
byte* v_str_in,
int v_str_in_len
);
char* de_get_str_with_len(
de_args *args,
int arg_id,
int* len
);
第arg_id参数的数据类型为字符串类型,从参数列表args中取出第arg_id参数的值以及字符串长度。
二者区别:
de_get_str_with_len使用服务器自动申请的空间,需要使用de_str_free释放空间。
de_get_str_with_len_and_space使用第三方申请的空间(通过v_str_in和v_str_in_len传入),不需要使用de_str_free释放空间。
v_str_in为已申请内存的字符串空间,由调用者分配,其大小不能少于v_str_in_len。若已分配长度等于0,v_str_in和返回值均返回NULL。若分配空间不够,则只返回部分字符串。
v_str_in_len为空间长度,包含结尾0。如果v_str_in_len小于实际参数加结尾0长度,则截断输出。
len为实际返回长度,不包含结尾0
char* de_get_str_with_len_and_space (
de_args* args,
int arg_id,
byte* v_str_in,
int v_str_in_len,
int * len
);
set void de_set_int(
de_args *args,
int arg_id,
int ret
);
第arg_id 参数的数据类型为整型,设置参数列表args 的第arg_id参数的值为 ret
void de_set_double(
de_args *args,
int arg_id,
double ret
);
第arg_id 参数的数据类型为double 类型,设置参数列表args 的第arg_id参数的值为 ret
void de_set_str(
de_args *args,
int arg_id,
char* ret
);
第arg_id 参数的数据类型为字符串类型,设置第arg_id 参数的值为ret
void de_set_str_with_len(
de_args *args,
int arg_id,
char* ret, int len
);
第arg_id 参数的数据类型为字符串类型,将字符串ret 的前len个字符赋值给参数列表 args的第arg_id 参数
void>  de_set_null(
de_args *args,
int arg_id
);
设置参数列表args 的第arg_id个参数为空
return de_data de_return_int(
int ret
);
返回值类型为整型
de_data de_return_double(
double ret
);
返回值类型为double 型
de_data  de_return_str(
char* ret
);
返回值为字符串类型
de_data de_return_str_with_len(
char* ret,
int len
);
返回字符串ret 的前len个字符
de_data  de_return_null(); 返回空值
de_str_free void de_str_free(
char* str
);
调用de_get_str 函数后,需要调用此函数释放字符串空间
de_is_null int de_is_null(
de_args *args,
int arg_id
);
判断参数列表args 的第arg_id个参数是否为空

注:参数个数 arg_id 的起始值为 0。

  • 标量类型参数

该方案中,用户不必引用 DM 提供的外部函数接口,可以按照标准的 C 风格编码,使用 C 标量类型作为参数类型。使用该方案编写的 C 函数,只能在使用 X86 CPU 的 64 位非 Windows 系统中,被数据库引用作为外部函数。

C 外部函数格式

返回类型函数名(参数列表)

返回类型函数名(参数列表)
{
	C语言函数实现体;
}

使用说明

  1. 返回类型及参数列表中参数的数据类型只支持 int、double(或精度大于 24 的 float)以及 char*类型。其中 float 的用法和 double 完全一样;
  2. 参数列表中不支持 out 型参数;
  3. 如果参数列表中有 char*的参数,不必在函数中对其进行释放;为了安全考虑,最好只对其进行只读操作;
  4. 如果返回 char*类型,返回值必须使用 malloc 申请空间,且必须有结尾 0,不允许直接返回常量或返回参数列表中传入的字符类型参数。

10.1.2 C 外部函数创建

语法格式

CREATE [OR REPLACE] FUNCTION [IF NOT EXISTS] [<模式名>.]<函数名>[(<参数列表>)]
RETURN <返回值类型>
EXTERNAL '<动态库路径>' [<引用的函数名>] USING < C | CS >;

参数

  1. < 函数名 > 指明被创建的 C 外部函数的名字;
  2. < 模式名 > 指明被创建的 C 外部函数所属模式的名字,缺省为当前模式名;
  3. < 参数列表 > 指明 C 外部函数参数信息,如果是使用 DM 结构化参数编写的 C 函数,参数模式可设置为 IN、OUT 或 IN OUT(OUT IN),缺省为 IN 类型;而使用标量类型参数编写的 C 函数则参数模式只能是 IN 类型。参数类型、个数都应和动态库里定义的一致;
  4. < 返回值类型 > 必须和动态库里定义的一致;
  5. < 动态库路径 > 用户按照 DM 规定的 C 语言函数格式编写的 DLL 文件生成的动态库所在的路径;动态库分为 64 位和 32 位两种,使用的时候要和操作系统一一对应。例如,64 位的操作系统要用 64 位的动态库;
  6. < 引用的函数名 > 指明 < 函数名 > 在 < 动态库路径 > 中对应的函数名。如果不使用双引号,则系统会自动将函数名转换成大写字母,如果使用双引号则不会进行大小写转换。建议使用双引号将函数名括起来,避免由于大小写问题而导致函数名不匹配。示例如下:……EXTERNAL '/mnt/mylibtest.so' "myy_add" USING CS;
  7. USING 子句指明函数的类型,如果是 DM 结构化参数的 C 函数,类型为 C;标量类型参数的 C 函数,类型为 CS。

图例

C 外部函数创建

C 外部函数创建

语句功能

创建自定义 C 外部函数。

使用说明

  1. 三权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;四权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;
  2. 创建或执行外部函数需要设置 INI 参数 ENABLE_EXTERNAL_CALL 为 1;
  3. < 引用函数名 > 如果为空,则默认与 < 函数名 > 相同;
  4. < 动态库路径 > 分为.dll 文件(windows)和.so 文件(linux)两种;
  5. 外部函数定义的返回值类型与对应 C 语言函数的返回值类型必须一致,否则会导致返回值不可预测甚至系统异常。

10.1.3 举例说明

例如,编写(C 语言)外部函数 C_CONCAT,用于将两个字符串连接。下面针对两种编写 C 外部函数的方案,分别进行描述。

一 可以使用 DM 结构化参数方案来完成。

首先 生成动态库。

第一步,使用 Microsoft Visual Studio 2010 新建空项目 newp,位于 d:\xx\tt 文件夹中。新建完毕后,若本机操作系统为 x64,因为 VS 默认的解决方案平台不是 x64,需要在解决方案平台下拉框选择“配置管理器-活动方案解决平台-新建-键入或选择新平台-x64”。在 d:\xx\tt\newp\newp 文件夹中,直接拷入 dmde.lib 动态库和 de_pub.h 头文件。dmde.lib 和 de_pub.h 位于安装文件 x:...\dmdbms\include 中。

第二步,在 newp 项目中,添加头文件。将已有 de_pub.h 头文件添加进来,同时,添加新的 tt.h 头文件。tt.h 文件内容如下:

#include "de_pub.h"
#include "string.h"
#include "stdlib.h"

第三步,在 newp 项目中,添加源文件。名为 tt.c。tt.c 内容如下:

#include "tt.h"
de_data C_CONCAT(de_args *args)
{
	de_data  de_ret;
	char* str1 = NULL;
	char* str2 = NULL;
	char* str3 = NULL;
	int len1 = 0;
	int len2 = 0;

	if(!de_is_null(args, 0))
	{
        str1 = (char*)de_get_str(args, 0);    //从参数列表中取第0个参数
        len1 = strlen(str1);
	}
	if(!de_is_null(args, 1))
	{
        //从参数列表中取第1个参数的值以及长度
        str2 = (char*)de_get_str_with_len(args, 1, (udint4*)&len2); 
	}

	str3 = (char*)malloc(len1 + len2);
	memcpy(str3, str1, len1);
	memcpy(str3 + len1, str2, len2);
	//调用get函数得到字符串之后,需要调用此函数释放字符串空间
	if(!de_is_null(args, 0))
	{
        de_str_free((sdbyte*)str1); 
	}
	if(!de_is_null(args, 1))
	{
        de_str_free((sdbyte*)str2);
	}
	de_ret = de_return_str_with_len((udbyte*)str3, len1 + len2); //返回字符串
	free(str3); 
	return de_ret;
}

第四步,在 newp 项目的源文件中,添加模块定义文件 tt.def,内容如下:

LIBRARY	"tt.dll"
EXPORTS
	C_CONCAT

第五步,配置项目属性。

在 Microsoft Visual Studio 2010 界面上,右击 newp 项目-属性,点击打开。

在“配置属性—链接器—输入”中添加附加依赖项 dmde.lib。

在“配置属性—常规”中:输出目录设为 D:\xx\tt\;配置类型选择动态库(.dll);字符集选择使用多字节字符集。

第六步,生成 newp 项目。右击 newp 项目-生成。得到 D:\xx\tt\newp.dll 文件。

至此,外部函数的使用环境准备完毕。

其次,创建并使用外部函数。

第一步,启动数据库服务器 dmserver、启动 DMAP、启动 DIsql。dmserver、DIsql 等工具位于安装目录 x:...\dmdbms\bin 中。

需要先开启系统允许创建外部函数的开关。通过设置 DM.INI 参数 ENABLE_EXTERNAL_CALL=1 开启。设置语句如下:

SF_SET_SYSTEM_PARA_VALUE('ENABLE_EXTERNAL_CALL',1,0,2);

设置完毕后需重启数据库服务器,参数才能生效。

第二步,在 DIsql 中,创建外部函数 MY_CONCAT。语句如下:

CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR) 
RETURN VARCHAR
EXTERNAL 'd:\xx\tt\newp.dll' "C_CONCAT" USING C;

第三步,调用 C 外部函数。语句如下:

select MY_CONCAT ('hello ', 'world!');

第四步,查看结果:

hello world!

二 使用 C 标量类型参数方案完成,不过需要注意该方案仅支持使用 X86 CPU 的 64 位非 Windows 系统。

生成动态库,这里改用 LINUX 操作系统作为示例。

第一步,创建 C 文件 test.c,编写 C 函数。

#include <string.h>
#include "stdlib.h"
char*  C_CONCAT (char*	str1, char* str2)
{
    char* str3 = NULL;
    int len1 = 0;
    int len2 = 0;
    len1 = strlen(str1);
    len2 = strlen(str2);
    str3 = (char*)malloc(len1 + len2 + 1);	//要多一个字节作为结尾0
    memcpy(str3, str1, len1);
    memcpy(str3 + len1, str2, len2);
    str3[len1 + len2] = 0;  //必须有结尾0
    return str3;
}

第二步,生成动态库。

gcc -o /mnt/libtest.so -fPIC -shared test.c

获得 libtest.so 文件。

至此,外部函数的使用环境准备完毕。

其次,创建并使用外部函数。

第一步,启动数据库服务器 dmserver,启动 DMAP、启动 DIsql。

第二步,在 DIsql 中,创建外部函数 MY_CONCAT。语句如下:

CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR) 
RETURN VARCHAR
EXTERNAL '/mnt/libtest.so' "C_CONCAT" USING CS;

第三步,调用 C 外部函数。语句如下:

select MY_CONCAT ('hello ', 'world!');

第四步,查看结果:

hello world!

10.2 JAVA 外部函数

JAVA 外部函数是使用 JAVA 语言编写,在数据库外编译生成的 jar 包,被用户通过 DMSQL 程序调用的函数。

JAVA 外部函数的执行都通过代理 dmagent 工具进行,为了执行 JAVA 外部函数,需要先启动 dmagent 服务。dmagent 执行程序在 DM8 安装目录的 tool/dmagent 子目录下,其使用说明文档可参看该目录下的《readme》文档。

当用户调用 JAVA 外部函数时,服务器操作步骤如下:首先,确定调用(外部函数使用的)jar 包及函数;然后,通知代理进程工作。代理进程装载指定的 jar 包,并在函数执行后将结果返回给服务器。

需要注意的是,进行 JAVA 外部函数调用应保证当前用户可以运行 JAVA 命令,否则会导致调用失败。

10.2.1 生成 jar 包

用户必须严格按照 JAVA 语言的格式书写代码,书写完成后生成 jar 包。

10.2.2 JAVA 外部函数创建

语法格式

CREATE [OR REPLACE] FUNCTION [IF NOT EXISTS] [<模式名>.]<函数名>[(<外部函数参数列表>)] 
RETURN <返回值类型>
EXTERNAL '<jar包路径>' [AND] <引用的JAVA函数名>[(<JAVA函数参数列表>)] USING JAVA;
<外部函数参数列表>::=<外部函数参数声明> {,<外部函数参数声明>}
<外部函数参数声明>::=<外部函数参数名> [<外部函数参数模式>]<外部函数参数类型>
<JAVA函数参数列表>::=<JAVA函数参数声明> {,<JAVA函数参数声明>}
<JAVA函数参数声明>::=<JAVA函数参数类型>

参数

  1. < 函数名 > 被创建的 JAVA 外部函数的名字;
  2. < 模式名 > 被创建的 JAVA 外部函数所属模式的名字,缺省为当前模式名;
  3. < 外部函数参数列表 > JAVA 外部函数参数信息;
  4. < 外部函数参数模式 > 可设置为 IN、OUT 或 IN OUT(OUT IN),缺省为 IN 类型。参数类型、个数都应和 jar 包里的一致;
  5. < 外部函数参数类型 > 目前支持的函数参数类型:int、字符串(char、varchar、varchar2)、bigint、double/float/binary_float、binary/varbinary、clob、blob,分别对应 java 类型:int、string、 long、double、byte[]、string、byte[];
  6. < 返回值类型 > 必须 jar 包里定义的一致;
  7. <jar 包路径 > 用户按照 JAVA 语言格式编写的源码生成的 jar 包,及其依赖的 jar 包所在的相对路径或绝对路径;
  8. < 引用的 JAVA 函数名 > 指明 < 函数名 > 在 <jar 包路径 > 中对应的 JAVA 函数名,函数调用通过 JAVA 反射实现,建议调用函数访问限定符为 public,另外,所属类须不含构造函数或包含无参的构造函数。若 < 函数名 > 包含包名,则包名(或.包名)与类名的分隔符要用.或/。而类与方法的分隔符要用.。若不指定 < 引用函数名 >,则后续使用 JAVA 外部函数时会报错。一个正确的 JAVA 函数名示例如下:……EXTERNAL '/data/sda/test.jar' "com.test.package1.test.pass(java.lang.String,java.lang.String[])" USING JAVA;;
  9. <JAVA 函数参数列表 > 用于指定 JAVA 函数的参数列表。缺省情况下,系统自动定位参数。只有未指定 < 外部函数参数模式 > 或 < 外部函数参数模式 > 中所有参数模式均为 IN 时,<JAVA 函数参数列表 > 才可缺省;
  10. <JAVA 函数参数声明 > 由一个或多个 <JAVA 函数参数类型 > 组成。<JAVA 函数参数类型 > 书写时必须包括 JAVA 类的完整名称。例如:……EXTERNAL '/data/sda/test.jar' "com.test.package1.test.pass(java.lang.String,java.lang.String[])" USING JAVA;。

图例

JAVA 外部函数创建

JAVA 外部函数创建.png

语句功能

创建自定义 JAVA 外部函数。

使用说明

  1. 当 < 引用的 JAVA 函数名 >[(<JAVA 函数参数列表 >)]的长度超过 128 个字节并且不超过 32767 个字节时,不能使用双引号,需要改用单引号括起来,并且需要指定 AND 关键字。例如:EXTERNAL '/data/sda/test.jar' AND 'com.test.package1.test.pass(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)' USING JAVA;
  2. 三权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;四权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;
  3. 创建或执行外部函数需要设置 INI 参数 ENABLE_EXTERNAL_CALL 为 1;
  4. 只能执行 agent_home/resources/ap 目录下的 jar 包。

10.2.3 举例说明

例 写(JAVA 语言)外部函数:testAdd 用于求两个数之和,testStr 用于在一个字符串后面加上 hello。

首先,生成 jar 包。

第一步,使用 eclipse 创建新项目 newp,位于 F:\workspace 文件夹中。

第二步,在 newp 项目中,添加包。右击 newp,新建(new)一个 package,命名(name)为 com.test.package1。

第三步,在 package1 包中,添加类文件。右击 src,新建(new)一个 class,命名(name)为 test。Modifiers 选择 public。class 文件内容如下:

package com.test.package1;  
public class test {
	public static int testAdd(int a, int b) {
		return a + b;
	}
	public static String testStr(String str) {
		return str + " hello";
	}
}

第四步,生成 jar 包。在 newp 项目中,右击,选中 EXPORT,选择 Java\JAR file,取消.classpath 和.project 的勾选。目标路径 JAR file 设置为:E:\test.jar,然后 finish。

第五步,查看 E 盘中 test.jar。已经存在。

第六步,在外部函数对应的 dmagent 的 resources 目录下,创建一个 ap 文件夹(若 resources 目录下已有 ap 文件夹则不用创建),将 test.jar 拷入其中。

至此,外部函数的使用环境准备完毕。

其次,创建并使用外部函数。

第一步,启动数据库服务器 dmserver,启动 DM 管理工具。

第二步,在 DM 管理工具中,创建外部函数 MY_INT 和 MY_chr,语句如下:

CREATE OR REPLACE FUNCTION MY_INT(a int, b int) 
RETURN int
EXTERNAL 'E:\develop\tool\agent\resources\ap\test.jar' "com.test.package1.test.testAdd" USING java;

CREATE OR REPLACE FUNCTION MY_chr(s varchar) 
RETURN varchar
EXTERNAL 'E:\develop\tool\agent\resources\ap\test.jar' "com.test.package1.test.testStr" USING java;

第三步,调用 JAVA 外部函数,语句如下:

select MY_INT(1,2);
select MY_chr('abc');

第四步,查看结果,分别为:

3
abc hello

10.3 DMAP 使用说明

DMAP(DM Assit progress)作为数据库管理系统的辅助进程,提供 C 外部函数、备份还原等功能的执行。

10.3.1 启动 DMAP

安装 DM 数据库以后,DMAP 服务会自动启动。如果需要手动启动,有两种途径:一是启动 DM 服务查看器中的 DmAPService;二是通过手动启动 DMAP 执行码实现,DMAP 执行码位于 DM 安装目录的 bin 子目录下。除此之外,LINUX 下,还可以调用 bin 目录下的 DmAPService 脚本启动 DMAP 服务。

默认不带参数启动 DMAP 时,DMAP 监听端口号为 4236,与 DM 服务器 INI 参数 EXTERNAL_AP_PORT 的默认值一致。

也可以使用参数 dmap_ini 指定 DMAP.INI 配置文件启动 DMAP,如下例所示:

dmap dmap_ini=d:\dmap.ini

其中,DMAP.INI 可配置 dmap 的端口号,如下所示:

AP_PORT=4236

需要注意的是,当使用 DMAP.INI 对 AP_PORT 进行配置时,DM 服务器 INI 参数 EXTERNAL_AP_PORT 的配置值应与 AP_PORT 一致。

10.3.2 使用 DMAP 执行 C 外部函数

例 创建并使用 C 外部函数 MY_CONCAT。

CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR) 
RETURN VARCHAR
EXTERNAL 'D:\testroot\for_dmserver\smoketest_data\dameng\detest64.dll' C_CAT USING C; 
/
select MY_CONCAT ('hello ', 'a');

查询结果如下:

行号       MY_CONCAT('hello','a')
--------- ----------------------
1          hello a

10.3.3 DMAP 日志

DMAP 日志文件记录了 DMAP 工具产生的相关日志。DMAP 日志文件命名规则为:dm_dmap_年月.log。

DMAP 日志记录的信息格式为:

时间 + 日志类型(INFO/WARN/ERROR/FATAL)+ 进程(dmap)+ 进程 ID+ 线程名(线程名为空则使用 T 开头的线程 ID)+ 日志内容。

例 展示一段 dm_dmap_202211.log 日志信息。

2022-11-30 09:00:58.357 [INFO] dmap P0000015192 T0000000000000032076 dmap V8
2022-11-30 09:00:58.372 [INFO] dmap P0000015192 T0000000000000032076 dmap is ready
2022-11-30 09:00:58.372 [INFO] dmap P0000015192 T0000000000000032076 [for dem]SYSTEM IS READY.
微信扫码
分享文档
扫一扫
联系客服