tuxedo与oracle数据库的连接_MQ, Tuxedo及OLTP讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  MQ, Tuxedo及OLTP讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 4494 | 回复: 0   主题: tuxedo与oracle数据库的连接        下一篇 
Xuziwen
注册用户
等级:上尉
经验:721
发帖:66
精华:0
注册:2012-7-4
状态:离线
发送短消息息给Xuziwen 加好友    发送短消息息给Xuziwen 发消息
发表于: IP:您无权察看 2014-9-12 9:52:03 | [全部帖] [楼主帖] 楼主

Tuxedo 与数据库互联

2.2.1 概述

    在两层的 C/S结构中,客户端直接访问数据库,当采用TUXEDO中间件后,形成三层结构。这时,客户端

不直接访问数据库,而是改为调用中间件TUXEDO服务端上的服务,由TUXEDO服务端访问数据库,并把结果返回

给客户端。TUXEDO服务端可以和ORACLE在同一台服务器上,也可以在不同的机器上。如果在不同的机器上,在

TUXEDO的服务端所在的机器上要安装一个ORACLE的客户端。 TUXEDO服务端与ORACLE数据库连接有两种方式:

1. 不通过 XA 接口直接互连。适用于整个系统只有一个数据库的情况。

2. 通过 XA 接口互连,对整个系统有一个或多个数据库都适用,建议采用。

2.2.1 系统说明

操作系统:WINDOW XP + SP2

TUXEDO 版本:9.0 安装目录 c:\bea\tuxedo9.0

ORACLE 版本:10g 安装目录 c:\oracle

开发工具:VS 2005, VC 6.0

所有配置文件、源代码文件和执行程序所在目录:d:\zwtest

我根据自己的测试设置的环境变量,仅供在编译程序和启动tuxedo服务出错时比较

1.用户环境变量

    APPDIR,值为 :d:\zwtest

    TUXCONFIG,值为:d:\zwtest\tuxconfig

    INCLUDE,值为:

C:\oracle\product\10.2.0\db_1\OCI\include;
C:\Program Files\Microsoft Visual Studio 8\VC\include;


2.系统环境变量

    INCLUDE,值为:

C:\Program Files\Microsoft Visual Studio 8\VC\include;
C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include;
C:\Program Files\Microsoft Visual Studio\VC98\MFC\Include;
C:\Program Files\Microsoft Visual Studio\VC98\Include;
C:\Program Files\Microsoft Visual Studio 8\VC\include;


    LD_LIBRARY_PATH,值为: 

$LD_LIBRARY_PATHTUXDIR/lib


    LIB,值为:

C:\Program Files\Microsoft Visual Studio 8\VC\lib;
C:\tools\xerces-c_2_3_0-win32\xerces-c_2_3_0-win32\lib;
C:\Program Files\Microsoft Visual Studio\VC98\Lib;


    path,值为:

%TUXDIR%\bin;
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE;
C:\oracle\product\10.2.0\db_1\BIN;
C:\Program Files\Microsoft Visual Studio 8\VC\bin;


    TUXDIR,值为:C:\bea\tuxedo9.0

2.2.2 配置的步骤

2.2.2.1 ORACLE的配置

1.以 dba 的权限进入sqlplus(默认口令是oracle)

C:\>sqlplus / as sysdba


2.搜索一下 xaview.sql 所在的位置(本例是在 C:\oracle\product\10.2.0\db_1\RDBMS\ADMIN\ 下)

运行:

SQL>@C:\oracle\product\10.2.0\db_1\RDBMS\ADMIN\xaview.sql;


如果一切正长则会提示创建了两个视图

3.授权

SQL>grant select on v$xatrans$ to public with grant option;
SQL>grant select on v$pending_xatrans$ to public with grant option;


4. 用 system 用户(缺省口令是manager,请根据实际情况修改)连接并授权

SQL>connect system/manager;
SQL>grant select any table to public;


5. 以system的身份建表,并插入2条测试记录

SQL>create table zwtest (name varchar2(8), age number);
SQL>insert into zwtest values('zhang', 30);
SQL>insert into zwtest values('liu', 27);
SQL>commit;


2.2.2.2 TUXEDO的配置

1. 修改 tuxedo 安装路径下的udataobj目录下的RM文件,在以Oracle_XA:xaosw:开头的那行开头加上#注释掉

   如果是windows平台,就添加以下行:

Oracle_XA;xaosw;C:\oracle\product\10.2.0\db_1\RDBMS\XA\oraxa10.lib C:\oracle\product\10.2.0\db_1\precomp\LIB\orasql10.lib


   如果是unix平台,就添加:

Oracle_XA:xaosw:-L${ORACLE_HOME}/lib -lclntsh


注意:

   上述库文件的位置可能随版本不同而不同,修改时以在本地机器上的实际位置为准

2. 在TUXEDO 用户下创建TMS文件:TMS_ORA10g, TUXEDO通过 TMS_ORA10g 与 ORACLE数据库采用 XA 协议进行通讯

buildtms -o c:\bea\tuxedo9.0\bin\TMS_ORA10g -r Oracle_XA


注意:

    1.要是在windows平台下就在命令行窗口中运行,并确保c:\bea\tuxedo9.0\bin已经添加到系统的path环境变量中(可用echo %path% 查看)。

      这个过程中有可能会提示找不到一些头文件或库文件,一般都是所缺文件所在路径没有包含在path或INCLUDE或LIB的环境变量中引起的,

      可以搜索所缺文件的目录,把这个目录添加到对应的环境变量中。添加完后要打开一个新的命令行窗口才会生效。

    2.如果TUXEDO服务端与ORACLE数据库不在同一台服务器上,可能会提示找不到库文件 oraxaX.lib和 orasqlX.lib(文件名中的大写X表示对应

      的oracle版本号),可到ORACLE数据库的服务端对应目录下把这两个文件拷到本地机器ORACLE的客户端下的对应目录下。

3. 配置 ubbconfig

(1) 在 *MACHINES 中增加:

TLOADDEVICE = "/home/oracle/temp/simpdb/TLOG"
TLOGNAME=TLOG
TLOGSIZE=200


(2) 改 *GROUPS 的配置为:

*GROUPS
GROUP1 LMID=simple GRPNO=1
OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SesTm=600+MaxCur=5+LogDir=."
TMSNAME="TMS_ORA10g" TMSCOUNT=2


注意:scott/tiger 为《tuxedo 简易培训教程》上数据库所采用的用户及口令,在我的测试平台上导致tuxedo服务启动时 TMS_ORA10g无法启动,

      所及建议用 system用户和对应密码 替换 OPENINFO中的 scott/tiger。

      如果要试一下 scott 用户, 一般应先以system或sysdba用户登录sqlplus,然后对scott用户进行解锁:

alter user scott account unlock;


      然后才能以scott用户登录,再根据提示修改默认密码。

修改后的配置文件 ubbconfig 内容如下:

*RESOURCES
IPCKEY 123456
DOMAINID simpapp
MASTER simple
MAXACCESSERS 100
MAXSERVERS 50
MAXSERVICES 100
MODEL SHM
LDBAL N
*MACHINES
servername LMID=simple
APPDIR="D:\simapp"
TUXCONFIG="D:\simapp\tuxconfig"
TUXDIR="C:\bea\tuxedo9.0"
TLOGDEVICE="D:\simapp\TLOG"
TLOGNAME=TLOG
TLOGSIZE=100
*GROUPS
GROUP1 LMID=simple GRPNO=1
OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SesTm=600+MaxCur=5+LogDir=."
TMSNAME="TMS_ORA10g" TMSCOUNT=2
*SERVERS
DEFAULT:
CLOPT="-A"
exefilename SRVGRP=GROUP1 SRVID=1
*SERVICES


注:

    1) *MACHINES下的 servername 是指服务器所在的完整的计算机名

    2) exefilename 是服务端可执行程序名,不加后缀,如果写错了在启动tuxedo服务时会出现以下报错:

CMDTUX_CAT:816: ERROR: Cannot exec, executable file not found


    3) *GROUPS 的 OPENINFO中,如果用scott用户登录,则启动tuxedo服务时会出现以下报错:

exec TMS_ORA10g -A :
Failed.


用system用户登录则正常,可能和oracle相关的配置有关。但是以 system 用户登录后,在随后

        执行客户端示例程序时也会报错:

tpcall failed, tperrno=11, tperrtext=TPESVCFAIL - application level service failure


查看ULOG文件,提示 select from EMP failure, sqlcode=-942, sqlerr=ORA-00942: 表或视图不存在

        然后用system用户登录sqlplus,查询EMP表:

select * from EMP;


果然提示: ORA-00942: 表或视图不存在

        再用scott连接:

conn scott/aiuuai;


查询EMP表返回正确结果。

总结: 用示例程序测试时,要么正确配置 Oracle 使TMS可以通过scott用户连接到oracle,要么以system

               用户建新表作为被测的数据表,后者可能更容易些。

    4) 在应用配置里提到,*SERVICES 提供了应用的特殊交易的信息。包括负载平衡(LOAD)和数据缓冲类型检查(BUFTYPE)。

        如果全部都是缺省值则本节可以省略。所以,*SERVICES下没有内容

(3) 最后生成 tuxconfig:

tmloadcf -y ubbconfig


        系统自动生成一个 tuxconfig 文件, -y 的意思是无条件地覆盖已经存在的 tuxconfig 文件。

以下是我定义的 ubbconfig 内容:

*RESOURCES
IPCKEY 123456 #<Replace with a valid IPC Key>
DOMAINID zwtest
MASTER simple
MAXACCESSERS 10
MAXSERVERS 5
MAXSERVICES 10
MODEL SHM
LDBAL N
*MACHINES
APPDIR= "D:\zwtest" #"<Replace with the current directory pathname>"
TUXCONFIG= "D:\zwtest\tuxconfig" #<Replace with your TUXCONFIG Pathname>"
TUXDIR= "C:\bea\tuxedo9.0" #"<Directory where TUXEDO is installed>"
"HP12193906110" LMID=simple
TLOGDEVICE="D:\zwtest\TLOG"
TLOGNAME=TLOG
TLOGSIZE=100
*GROUPS
GROUP1 LMID=simple GRPNO=1 OPENINFO=NONE
OPENINFO="Oracle_XA:Oracle_XA+Acc=P/system/aiuuai+SesTm=600+MaxCur=5+LogDir=."
TMSNAME="TMS_ORA10g" TMSCOUNT=2
*SERVERS
DEFAULT:
CLOPT="-A"
testsvr SRVGRP=GROUP1 SRVID=1
*SERVICES


4.重命名下列文件,因为下列文件名与ORACLE带的文件名有冲突,所以要更改

(1) tuxedo安装路径 include目录下的:

sqlca.h 改为 sqlca.h.bbb

sqlcode.h 改为 sqlcode.h.bbb

sqlda.h 改为 vsqlda.h.bbb

(2) tuxedo安装路径 lib目录下的:

libsql.lib 改名为 libsql.lib.bbb

注意:

   在oracle10g下,只发现sqlca.h, sqlda.h和tuxedo有重名文件,实际修改时最好先

在oracle安装目录中查找 sqlca.h , sqlcode.h , sqlda.h , libsql.lib 这4个文件,

如果和tuxedo安装目录下有重名文件,则将tuxedo目录下的重名文件作以上的更名修改

5.用TMADMIN 创建 TLOG文件,TUXEDO用一个文件TLOG记录对数据库操作的日志。用于协调分布式数据库的提交与回滚。

D:\>tmadmin
>crdl -b 500 -z d:\zwtest\TLOG
>crlog -m simple
>q


2.2.2.3 服务端的程序:

(1) testsvr.pc ,功能:根据客户端传入的EMPNO到表EMP中取ENAME的值,并把它返回给客户端。

    但实际运行时会有错误返回,查ULOG发现是将从客户端收到的字符串转换为long型时有误,

    因而导致错误的empno,所以查不到对应的ename,oracle查询返回"未找到数据"的错误,进而

    使服务端返回交易失败的错误信息

/* testsvr.pc */
#include <stdio.h>
#include <atmi.h>
#include <userlog.h>
EXEC SQL INCLUDE sqlca;
EXEC SQL BEGIN DECLARE SECTION;
long al_empno=0;
char ac_ename[11]="";
EXEC SQL VAR ac_ename IS STRING(11);
EXEC SQL END DECLARE SECTION;
TEST(TPSVCINFO *rqst)
{
      /* recv data from client */
      al_empno = (FBFR32 *)rqst->data;
      EXEC SQL select ename into :ac_ename from EMP where empno=:al_empno;
      if (sqlca.sqlcode != 0)
      {
            userlog ("select from EMP failure, sqlcode=%ld, sqlerr=%s\n",
            sqlca.sqlcode, (char*)sqlca.sqlerrm.sqlerrmc);
            strcpy (rqst->data, sqlca.sqlerrm.sqlerrmc);
            tpreturn (TPFAIL, 0, rqst->data, 0, 0);
      }
      /* return the record set to client */
      strcpy (rqst->data, ac_ename);
      tpreturn (TPSUCCESS, 0, rqst->data, 0, 0);
}
***************************************************************************************


(2) 修改后的 testsvr.c, 经测试可以正常运行,返回结果

    并增加了一个将收到的字符串转换为大写并返回的服务(交易) UPPER

/* testsvr.pc */
#include <stdio.h>
#include <atmi.h>
#include <userlog.h>
#include <stdlib.h>
EXEC SQL INCLUDE sqlca;
EXEC SQL BEGIN DECLARE SECTION;
int al_age=0;
char ac_name[11]="";
EXEC SQL VAR ac_name IS STRING(11);
EXEC SQL END DECLARE SECTION;
GETNAME(TPSVCINFO *rqst)
{
      /* recv data from client */
      al_age = atoi (rqst->data);
      EXEC SQL select name into :ac_name from zwtest where age=:al_age;
      if (sqlca.sqlcode != 0)
      {
            userlog ("select from help failure, sqlcode=%ld, sqlerr=%s, age=%d\n",
            sqlca.sqlcode, (char*)sqlca.sqlerrm.sqlerrmc, al_age);
            strcpy (rqst->data, sqlca.sqlerrm.sqlerrmc);
            tpreturn (TPFAIL, 0, rqst->data, 0, 0);
      }
      /* return the record set to client */
      strcpy (rqst->data, ac_name);
      tpreturn (TPSUCCESS, 0, rqst->data, 0, 0);
}
UPPER(TPSVCINFO *rqst)
{
int i;
userlog("request string = %s\n", rqst->data);
for (i=0; i<rqst->len-1; i++)
rqst->data[i] = toupper(rqst->data[i]);
tpreturn (TPSUCCESS, 0, rqst->data, 0L, 0);
}


2.2.2.4 编译服务端程序

1.用ORACLE的 proc 把 testsvr.pc 文件预编译成 testsvr.c 文件

d:\zwtest\proc testsvr.pc include=%TUXDIR%\include


2.用buildserver把第一步生成的 testsvr.c 编译成可执行文件,注意 -r 后带的 Oracle_XA 要与 RM 文件中的一致。

d:\zwtest\buildserver -o testsvr -f testsvr.c -r Oracle_XA -s GETNAME, UPPER


2.2.2.5 编写客户端程序:

(1) testcli.c, 发送一个字符串给服务端,接收服务端传回的转换为大写的字串

/* testcli.c */
#include <stdio.h>
#include <string.h>
#include "atmi.h"
int main (int argc, char *argv[])
{
      long reqlen = 1024;
      char *reqbuf;
      if (argc != 2)
      {
            printf ("usage: testcli string\n");
            exit (0);
      }
      /* connect to tuxedo server */
      if (tpinit ((TPINIT *) NULL) == -1)
      {
            (void) fprintf (stderr, "Tpinit failed\n");
            exit (1);
      }
      /* allocate send buffer */
      reqbuf = (char *) tpalloc ("STRING", NULL, reqlen);
      if (reqbuf == (char *)NULL)
      {
            printf("tpalloc failed\n");
            tpterm();
      }
      strcpy (reqbuf, argv[1]);
      printf("reqbuf = %s\n", reqbuf);
      /* call server "UPPER" of tuxedo */
      if (tpcall ("UPPER", (char *)reqbuf, 0L, (char**)&reqbuf,
      (long *)&reqlen, 0 ) < 0)
      {
            printf ("tpcall failed, tperrno=%ld, tperrtext=%s\n",
            tperrno, tpstrerror(tperrno));
            tpfree (reqbuf);
            tpterm ();
            exit (1);
      }
      printf ("return = %s\n", reqbuf);
      tpfree (reqbuf);
      tpterm ();
      return (0);
}
****************************************************************************


(2) testdbcli.c,传递一个年龄值给服务端,服务端查oracle数据库后返回对应的姓名

/* testdbcli.c */
#include <stdio.h>
#include <string.h>
#include "atmi.h"
int main (int argc, char *argv[])
{
      char *reqbuf;
      int reqlen = 11;
      if (argc != 2)
      {
            printf ("usage: testdbcli age_number\n");
            exit (0);
      }
      /* connect to tuxedo server */
      if (tpinit ((TPINIT *) NULL) == -1)
      {
            (void) fprintf (stderr, "Tpinit failed\n");
            exit (1);
      }
      /* allocate send buffer */
      reqbuf = (char *) tpalloc ("STRING", NULL, reqlen);
      if (reqbuf == (char *)NULL)
      {
            printf("tpalloc failed\n");
            tpterm();
      }
      memset(reqbuf, 0, reqlen);
      strcpy(reqbuf, argv[1]);
      printf("reqbuf = %s\n", reqbuf);
      /* call server "GETNAME" of tuxedo */
      if (tpcall ("GETNAME", (char *)reqbuf, 0L, (char**)&reqbuf,
      (long *)&reqlen, 0 ) < 0)
      {
            printf ("tpcall failed, tperrno=%ld, tperrtext=%s\n",
            tperrno, tpstrerror(tperrno));
            tpfree (reqbuf);
            tpterm ();
            exit (1);
      }
      printf ("name = %s\n", reqbuf);
      tpfree (reqbuf);
      tpterm ();
      return (0);
}


2.2.2.6 编译客户端程序

d:\zwtest\buildclient -o testcli -f testcli.c
d:\zwtest\buildclient -o testdbcli -f testdbcli.c


2.2.2.7 启动 tuxedo

d:\zwtest\tmboot -y


如果显示提示信息:

Booting all admin and server processes in D:\zwtest\tuxconfig
INFO: BEA Tuxedo, Version 9.0, 32-bit, Patch Level (none)
INFO: Serial #: 650522264138-1796918373125, Expiration 2006-01-15, Maxusers 100
INFO: Licensed to: BEA Evaluation Customer
Booting admin processes ...
exec BBL -A :
process id=3960 ... Started.
Booting server processes ...
exec TMS_ORA10g -A :
process id=2692 ... Started.
exec TMS_ORA10g -A :
process id=2588 ... Started.
exec testsvr -A :
process id=2872 ... Started.
4 processes started.


就说明服务正确启动了, 这时就可以在客户端所在目录下运行客户端程序:

D:\zwtest>testcli.exe hello
reqbuf = hello
return = HELLO
D:\zwtest>testdbcli.exe 30
reqbuf = 30
name = zhang


如果提示:

Booting all admin and server processes in D:\zwtest\tuxconfig
INFO: BEA Tuxedo, Version 9.0, 32-bit, Patch Level (none)
INFO: Serial #: 650522264138-1796918373125, Expiration 2006-01-15, Maxusers 100
INFO: Licensed to: BEA Evaluation Customer
Booting admin processes ...
exec BBL -A :
process id=3920 ... Started.
Booting server processes ...
exec TMS_ORA10g -A :
Failed.
exec TMS_ORA10g -A :


并且程序停止不动,也不退出,好像死了一样

这就是TMS_ORA10g 在启动时发生了错误,本例中是由于前面提到的OPENINFO中使用scott用户引起的,

这样就会在当前目录生成一个 *.trc 文件,记录失败的原因。

同时 tuxedo 的 ULOG.MMDDYY 文件中也会记录一些错误信息,以供进行错误分析。

假死的程序可以用 Ctrl+C 来终止,然后用 tmshutdown -y 来关闭已经启动的 BBL 和其他服务。

最后可以用 tmshutdown -y来检测所有服务是否已关闭,提示:

Shutting down all admin and server processes in D:\zwtest\tuxconfig
tmshutdown: internal error: CMDTUX_CAT:764: ERROR: can't attach to BB


就表示本机的服务已经被关闭,这时就可以去修改ubbconfig,重新生成tuxconfig,然后再启动服务。

需要对服务端程序进行修改时,也必须先tmshutdown -y,才能修改服务端程序并重新生成可执行程序。

=============================================================

折磨我两天的 tuxedo buildserver 编译服务启动挂起问题终于解决,不容易啊,为了让有过类似经历的人省下点时间,现记录如下:

故障现象:

准备将原系统 redflag4.0+tuxedo8.0+oracle 8.1.7的系统移植到

redfalg5.0+tuxedo8.1+oracle10g


开发环境:proc,gcc3.4.6

好,部署软件环境,修改环境变量,更改tuxedo的某些oracle的头文件,如slqda.h等...

在此不详细说明,网上有不少相关文档,可以查看.

经过一番折腾,buildserver终于将服务编译通过,happy!go on!

tmboot -y..........
CMDTUX_CAT:819: INFO: Process id=16218 Assume started (pipe).


查看ULOG,一些扯淡的话

142508.kaifa!BBL.14662.3086919904.0: 09-03-2004: Tuxedo Version 8.1, 32-bit, Patch Level 099
142508.kaifa!BBL.14662.3086919904.0: LIBTUX_CAT:1434: ERROR: Invalid trace specification; ignored
142508.kaifa!BBL.14662.3086919904.0: LIBTUX_CAT:1434: ERROR: Invalid trace specification; ignored
142508.kaifa!BBL.14662.3086919904.0: LIBTUX_CAT:577: ERROR: Unable to register because the slot is already owned by another process
142508.kaifa!BBL.14662.3086919904.0: LIBTUX_CAT:248: ERROR: System init function failed, Uunixerr =
142508.kaifa!BBL.14662.3086919904.0: CMDTUX_CAT:26: INFO: The BBL is exiting system
............
142508.kaifa!GWADM.14726.3086341824.0: LIBTUX_CAT:262: INFO: Standard main starting
142508.kaifa!DMADM.11747.3086805376.0: CMDGW_CAT:3250: ERROR: Cannot send config to GWADM group=LDMGRP_1
142508.kaifa!GWADM.14726.3086341824.0: LIBTUX_CAT:250: ERROR: tpsvrinit() failed


没用,继续找

tpsvrinit中连接数据库hang,问题就出在这

这个环境没有用xa连接,直接EXEC SQL CONNECT

好,拿sqlplus手工连接测试,没问题阿。

那好,直接写个带proc连接数据库的干净的(不加载一些乱七八糟的库)可执行文件,测试,成功。

那用buildclient写个可执行文件,测试,成功。

那就是buildserver不行阿 ,好,拿tuxedo提供的sample编译个服务,测试,失败。

原来跟我没关系阿,就是tuxedo和oracle之间的问题了

上网goooooooooooooogle

不好找阿,好不容易有点感觉了,得出猜测:tuxedo8.1和oracle10g 某些库冲突,tuxedo8.1 需要打补丁,(tuxedo9.1也有这中情况),可是这个补丁就是找不到阿,快崩溃了

期间请教了n个师傅无果,无意中又找到n+1个师傅,终于.......

问题出在这 ;

libclntsh.so.10.1,记住这个so

$ORACLE_HOME/lib下有这个动态链接库

同样/usr/lib下也有这个动态库,删掉,问题解决。

但是有点疑问,环境变量写死指向ORACLE的库了,ldd 可执行文件 也指向了ORACLE的库了,虽说/usr/lib下的优先级高,但是可执行文件在运行加载动态库时怎么找到/usr/lib下的冲突的这个库libclntsh.so.10.1的呢




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论