UTA使用说明_uhf无线麦克风怎么用

UTA使用说明_uhf无线麦克风怎么用1项目介绍1.1概述1.1.1什么是UTA?UTA是UMX-Thrift协议网关插件.负责在采用UMX协议的程序和Thrift服务之间做透明转换,如接入nw.js客户端访问现有的thrift服务

 

 

1      项目介绍

 

1.1    概述

1.1.1  什么是UTA?

UTA是UMX-Thrift协议网关插件.

负责在采用UMX协议的程序和Thrift服务之间做透明转换,如接入nw.js客户端访问现有的thrift服务。

 

 

1.1.2  原理

应用协议定义可以和thrift服务对应。

uta作为thrift的客户端。

利用cpg的c++反射能力,实现基于配置的2种服务协议的自动映射。

 

C++反射技术有多种,但有的需要c++11编译器支持,经实践,cpgf可满足应用需求。

 

l  cpgf

http://www.cpgf.org/document/metagen-cpp-operator-wrapper.html

 

l  RTTR

http://www.rttr.org/doc/rttr-0-9-5/tutorial_page.html

 

l  camp/ponder

https://github.com/tegesoft/camp

 https://github.com/billyquith/ponder is a newproject carried by Billy Quith, that brings CAMP to a new life and a new level.Please prefer Ponder to Camp, you’ll get rid of Boost, you’ll benefit fromC++11 and you’ll get good support from Billy.

 

l  XCppRefl – C++ Reflection Library

http://www.extreme.indiana.edu/reflcpp/

 

l  cppreflect

http://www.garret.ru/cppreflection/docs/reflect.html

 

l  boost Reflect Library 0.1.0

http://bytemaster.github.io/boost_reflect/group__boost__reflect__introduction.html

 

1.1.3  环境

基于uta开发服务需要安装以下软件:

软件名称

版本

下载

jdk

1.8.0_91

http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

thrift

0.9.3

https://thrift.apache.org/download

cpgf

1.5.6

http://www.cpgf.org/

Rhino

1.7.7.1

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino

doxygen:

1.3.9.1

http://www.stack.nl/~dimitri/doxygen/index.html

 

1.2    目录结构

uta插件程序目录结构如下图:

UTA使用说明_uhf无线麦克风怎么用

 UTA使用说明_uhf无线麦克风怎么用

services目录下存储的工具文件包括:

文件名

说明

thrift-0.9.3.exe

thrift编译器

js.jar

即Rhino-1.7.7.1.jar

metagen.js

cpgf元数据生成包

doxygen.exe

doxygen工具

doxytag.exe

doxywizard.exe

 

每个服务在services目录下创建一个子目录,每个服务的目录下有2个子目录:

l  output:存储cpgf生成的C++类型元信息文件

l  xml:存储doxygen生成的XML输出文件

 

服务thrift定义文件保存在服务目录下,thrift编译产生的文件也在服务目录下。

 

SLBase是公共类型定义,包括异常(SLThrfitException)和默认返回类型(ThiftCommonReturn)定义,该文件被业务服务定义文件包含(include)。

include “../SLBase/SLBase.thrift”

 

SLBase.thrift内容如下:

namespace cpp com.skylink.thrift.common

namespace java com.skylink.thrift.common

 

// 异常

exception SLThriftException{

         1: required string code;

         2: optional string message;

}

 

#默认返回类型

struct ThriftCommonReturn {

         1: required string retCode;

         2: optional string retMsg;

         3: optional string result;

}

 

SLBase已编译,生成类型元信息,并加入到uta工程。

若需要修改,则需要重新执行thrift编译,metagen的过程,见下文的实践步骤。

 

1.3    项目说明

uta插件工程visual c++ 2010工程结构如下图:

 UTA使用说明_uhf无线麦克风怎么用UTA使用说明_uhf无线麦克风怎么用

为每个服务在services下建立对应的Filter.

编译/链接选项:

C/C++|General|Additional Include Directories

 

增加

.;V:\vendor\cpgf\include;V:\thrift-0.9.3\thrift-0.9.3\lib\cpp\src;V:\thrift-0.9.3\thrift-0.9.3\lib\cpp\src\thrift;V:\libevent-2.0.22-stable\include;V:\libevent-2.0.22-stable\WIN32-Code;V:\boost\boost_1_55_0;

Linker|General|Additional Directories

增加

V:\boost\boost_1_55_0\boost\bin\vc10\lib;V:\thrift-0.9.3\thrift-0.9.3\lib\cpp\Release;V:\openssl\openssl-1.0.2h\openssl-1.0.2h\out32dll;V:\libevent-2.0.22-stable;

Linker|Input|Additional dependencies

增加

libthriftnb.lib,libthrift.lib,ssleay32.lib,libeay32.lib,libevent.lib

 

工程需要加入cpgf的以下文件:

.cpgf\src下所有的cpp文件

.cpgf\src\metatraists下的gmetaobjectlifemanager.cpp,gmetaobjectlifemanager_iobject.cpp

.cpgf\src\thirdparty\jsoncpp下的所有cpp文件

 

 

1.4    cpgf修改

1.4.1  gstdint.h

thrift可能包含了vc stdint.h,cpgf在gstdint.h中也定义了.编译报以下错误:

d:\vendor\cpgf\include\cpgf\gstdint.h(129): error C2371: ‘int_fast8_t’ : redefinition; different basic types

1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\stdint.h(33) : see declaration of ‘int_fast8_t’

增加宏控制解决:

#ifdef STANDALONE_CPGF

typedef int8_t    int_fast8_t;

typedef int16_t   int_fast16_t;

typedef int32_t   int_fast32_t;

typedef int64_t   int_fast64_t;

typedef uint8_t   uint_fast8_t;

typedef uint16_t  uint_fast16_t;

typedef uint32_t  uint_fast32_t;

typedef uint64_t  uint_fast64_t;

#endif

 

1.4.2  gmetadata_vector.h

增加size方法反射。

template <typename T, typename MetaDefine, typename Policy>

void doBuildMetaData_vector(const GMetaDataConfigFlags & config, MetaDefine define, const Policy & policy)

{

         metadata_internal::buildMetaData_CommonContainer<T>(config, define, policy);

         metadata_internal::buildMetaData_CommonIterators<T>(config, define, policy);

 

         define

                   .CPGF_MD_TEMPLATE _method(“assign”, (void (T::*)(typename T::size_type, const typename T::value_type &)) &T::assign, policy)

                   .CPGF_MD_TEMPLATE _method(“at”, (typename T::reference (T::*)(typename T::size_type)) &T::at, policy)

                   .CPGF_MD_TEMPLATE _method(“at”, (typename T::const_reference (T::*)(typename T::size_type) const) &T::at, policy)

                   .CPGF_MD_TEMPLATE _method(“back”, (typename T::reference (T::*)()) &T::back, policy)

                   .CPGF_MD_TEMPLATE _method(“back”, (typename T::const_reference (T::*)() const) &T::back, policy)

                   .CPGF_MD_TEMPLATE _method(“capacity”, &T::capacity)

                   .CPGF_MD_TEMPLATE _method(“front”, (typename T::reference (T::*)()) &T::front, policy)

                   .CPGF_MD_TEMPLATE _method(“front”, (typename T::const_reference (T::*)() const) &T::front, policy)

                   .CPGF_MD_TEMPLATE _method(“size”, (typename T::size_type (T::*)() const) &T::size)                  

                   .CPGF_MD_TEMPLATE _method(“max_size”, (typename T::size_type (T::*)() const) &T::max_size)

                   .CPGF_MD_TEMPLATE _method(“pop_back”, (void (T::*)()) &T::pop_back)

                   .CPGF_MD_TEMPLATE _method(“push_back”, (void (T::*)(const typename T::value_type &)) &T::push_back, policy)

                   .CPGF_MD_TEMPLATE _method(“reserve”, (void (T::*)(typename T::size_type)) &T::reserve)

                   .CPGF_MD_TEMPLATE _method(“resize”, selectFunctionByArity2(&T::resize), policy)

                   .CPGF_MD_TEMPLATE _method(“resize”, (void (T::*)(typename T::size_type)) &T::resize, policy)

 

                   .CPGF_MD_TEMPLATE _operator<typename T::reference (GMetaSelf, typename T::size_type)>(mopHolder[0], policy)

                   .CPGF_MD_TEMPLATE _operator<typename T::const_reference (const GMetaSelf &, typename T::size_type)>(mopHolder[0], policy)

 

#if CPGF_MD_STL_QUIRK_CONST_ITERATOR()              

                   .CPGF_MD_TEMPLATE _method(“insert”, (typename T::iterator (T::*)(typename T::const_iterator, const typename T::value_type &)) &T::insert, policy)

                   .CPGF_MD_TEMPLATE _method(“insert”, (void (T::*)(typename T::const_iterator, typename T::size_type, const typename T::value_type &)) &T::insert, policy)

#else

                   .CPGF_MD_TEMPLATE _method(“insert”, (typename T::iterator (T::*)(typename T::iterator, const typename T::value_type &)) &T::insert, policy)

                   .CPGF_MD_TEMPLATE _method(“insert”, (void (T::*)(typename T::iterator, typename T::size_type, const typename T::value_type &)) &T::insert, policy)

#endif

         ;

}

 

2      TestService服务开发实践

以下以实现TestService服务的过程描述基于uta开发的过程。

实际使用时把TestService替换成真实的服务名。

 

下文中命令默认都是在services目录下执行。

2.1    创建服务IDL文件

把应用协议用thrift IDL语法描述。

编辑IDL文件,保存在TestService目录下。

 

测试用的3个协议选自红包服务(RedPackService),能涵盖以下特性:

l  支持请求和确认消息的参数和行集

l  支持无返回请求(返回类型为void)

l  支持多参数方法

l  支持参数或返回值类型成员为结构类型

 

3个协议分别是:

l  查询商家账户余额(3503)

l  新增协议(3522)

l  查询协议(3524)

 

完整的TestService.thrift文件如下:

include “../SLBase/SLBase.thrift”

 

namespace cpp com.skylink.thrift.test_service

namespace java com.skylink.thrift.test_service

 

#支付方式

enum PayType

{

         CASH=1,

         WECHAT,

         ALIPAY

}

 

 

#合同支付设置

struct PaySet

{

         1: optional PayType payType;  //支付类型

         2: optional double payRatio;  //PayRatio

}

 

#合同特例商品信息

struct ProtGoodsItem

{

         1: optional i32 goodsId;   //商品ID

         2: optional string goodsName; //商品名称

         3: optional string spec; //规格

         4: optional string barcode; //条码

         5: optional double packPrice;//件装价

         6: optional string packUnit;//件装单位

         7: optional double rpVal;  //订货红包

         8: optional double feeVal;  //服务费

         9: optional i32 fid;  //厂家ID

         10: optional string fName; //厂家名称

         11: optional double rpVal2;  //厂家订货红包

         12: optional double feeVal2; //厂家服务费

}

 

#红包协议信息

struct ProtInfo

{

         1: optional i32 eid;   //供应商ID

         2: optional string ename; //供应商名称

         3: optional i16 applyFlag;         //红包对象(bit0-门店 bit1-批发商)  1:门店 2:批发商 3:门店和批发商

         4: optional i16 enableFlag;       //余额不足是否允许订货

         5: optional string beginDate;  //有效期开始日期

         6: optional string endDate;     //有效期截止日期

         7: optional i16 status;      //状态

         8: optional i16 rpBase;    //默认订货红包计算方式

         9: optional double rpVal;  //默认订货红包数

         10: optional i16 feeBase;  //默认智店宝服务费计算方式

         11: optional double feeVal;  //默认智店宝服务费数

         12: optional double minRecharge;  //最小充值金额

         13: list<PaySet> paysetList;  //支付设置

         14: list<ProtGoodsItem> goodsList;  //特例商品

         15: optional i32 specialNum;  //特例商品个数

}

 

 

///

#查询商家账户余额应答

struct QueryAccountBalanceResponse

{

         1: required string retCode;

         2: optional string retMsg;

         3: optional string result;

        

         4: optional i32 eid;  //供应商ID

         5: optional double balance;  //账户余额

}

 

 

#查询红包协议应答

struct QueryProtResponse

{

         1: required string retCode;

         2: optional string retMsg;

         3: optional string result;

         4: ProtInfo protInfo;

}

 

#红包服务接口

service TestService

{

         #新增协议

         SLBase.ThriftCommonReturn AddProt(1: ProtInfo protInfo) throws (1: SLBase.SLThriftException excp);

         #查询商家账户余额

         QueryAccountBalanceResponse QueryAccountBalance(1: i32 eid) throws (1: SLBase.SLThriftException excp);

         #查询协议详情

         QueryProtResponse QueryProt(1: i32 eid,2: i32 countSpecialFlag) throws (1: SLBase.SLThriftException excp);

        

}

 

2.2    编译thrift文件

在services目录下执行

thrift-0.9.3.exe -gen cpp:no_default_operators=1 -out .\TestService .\TestService\TestService.thrift

 

在TestService目录下会产生以下所需文件:

文件名

说明

TestService.cpp

 

TestService.h

 

TestService_constant.cpp

 

TestService_constant.h

 

TestService_types.cpp

 

TestService_types.h

 

 

 

2.3    创建Doxygen文件

执行

doxygen -g ./TestService/TestService_Doxyfile

生成配置文件。

 

修改TestService_Doxyfile的以下配置项

PROJECT_NAME           = TestService

PROJECT_NUMBER         = 1.0

OUTPUT_DIRECTORY       = D:\workdb\server\trunk\Server\xone_plugin\uta\services\TestService

 

INPUT                  = D:\workdb\server\trunk\Server\xone_plugin\uta\services\TestService

FILE_PATTERNS          = *.h

 

GENERATE_XML           = YES

GENERATE_HTML          = NO

GENERATE_LATEX         = NO

INPUT或者指定文件名:

INPUT                  = TestService.h TestService_types.h TestService_constants.h

 

用doxygen GUI frontend加载TestService_Doxyfile,执行Start,在xml目录下生成Doxygen文档。

2.4    创建TestService.js文件

在TestService目录下创建metagen配置文件。

内容如下:

var config = {

         projectID:”TestService”,

          mainSourceFile:”register_meta_TestService”,

          headerOutput : “./TestService/output”,

          sourceOutput:”./TestService/output”

};

 

全部的配置项见代码cpgf\tools\metagen\tool\src\org\cpgf\metagen\config.java

2.5    生成类型元信息

在services目录下执行:

java -cp js.jar -jar ./metagen.jar  –xml ./TestService/xml/index.xml –config ./TestService/TestService.js

生成的文件输出到output子目录下。

 

对thrift生成的每个.h文件生成类型元信息,文件包括:

文件名

说明

meta_TestService.cpp

 

meta_TestService.h

 

meta_TestService_constants.cpp

 

meta_TestService_constants.h

 

meta_TestService_types.cpp

 

meta_TestService_types.h

 

register_meta_TestService.cpp

 

register_meta_TestService.h

 

 

2.6    修改metagen生成的文件

需要对metagen生成的文件进行手动修改。

 

修改meta_TestService.cpp文件:

#include “D:/workdb/server/trunk/Server/xone_plugin/uta/services/TestService/TestService.h”

修改为:

#include “./services/TestService/TestService.h”

 

增加:

using namespace com::skylink::thrift::test_service;

 

 

修改register_meta_TestService.h

增加下文创建的STL vector类型元文件注册代码。

using namespace cpgf;

 

GDefineMetaInfo createMetaClass_PaySet_Vector();

GDefineMetaInfo createMetaClass_ProtGoodsItem_Vector();

 

template <typename Meta>

void registerMain_TestService(Meta _d)

{

    _d._class(createMetaClass_Global_testservice_constants());

    _d._class(createMetaClass_Global_testservice_types());

    _d._class(createMetaClass_PaySet());

    _d._class(createMetaClass_PayType());

    _d._class(createMetaClass_ProtGoodsItem());

    _d._class(createMetaClass_ProtInfo());

    _d._class(createMetaClass_QueryAccountBalanceResponse());

    _d._class(createMetaClass_QueryProtResponse());

    _d._class(createMetaClass_TestServiceClient());

    _d._class(createMetaClass_TestServiceConcurrentClient());

    _d._class(createMetaClass_TestServiceConstants());

 

 

         _d._class(createMetaClass_PaySet_Vector());

         _d._class(createMetaClass_ProtGoodsItem_Vector());

}

 

删除从:

GDefineMetaInfo createMetaClass_TestServiceIf();

开始的所有函数定义及函数

template <typename Meta>

void registerMain_TestService(Meta _d)

中的引用。

 

这些是不需要反射的类型,必须删除的原因是有以下几种编译错误:

l  cpgf不支持位域:thrift生成的每个类型对应的XXX__isset类使用了位域

l  会生成类似以下的错误代码:

inline PaySet & opErAToRWrapper_PaySet__opAssign(PaySet * self, const PaySet &) {

    return (*self) =;

}

 

 

 

修改meta_TestServie.cpp

删除从createMetaClass_TestServiceIf开始的代码。

2.7    创建STL vector类型的类型元文件

对于vector<>成员需要建立类型元文件。

创建meta_TestService_vector.cpp文件,内容如下:

#include “./services/TestService/TestService_types.h”

#include “cpgf/metadata/stl/gmetadata_vector.h”

 

using namespace cpgf;

using namespace ::com::skylink::thrift::uta_test;

using namespace std;

 

GDefineMetaInfo createMetaClass_PaySet_Vector() {

    GDefineMetaGlobalDangle _d = GDefineMetaGlobalDangle::dangle();

         GDefineMetaClass<vector<PaySet> > define = GDefineMetaClass<vector<PaySet> >::declare(“PaySet_Vector”);

         buildMetaData_vector(0,define,GMetaPolicyDefault());

 

          _d._class(define);

 

         return _d.getMetaInfo();

}

 

GDefineMetaInfo createMetaClass_ProtGoodsItem_Vector() {

    GDefineMetaGlobalDangle _d = GDefineMetaGlobalDangle::dangle();

 

         GDefineMetaClass<vector<PaySet> > define = GDefineMetaClass<vector<PaySet> >::declare(“ProtGoodsItem_Vector”);

         buildMetaData_vector(0,define,GMetaPolicyDefault());

 

          _d._class(define);

 

         return _d.getMetaInfo();

}

 

 

2.8    把文件加入工程

加入上述步骤生成的文件,如下图

UTA使用说明_uhf无线麦克风怎么用

 UTA使用说明_uhf无线麦克风怎么用

2.9    编译

编译工程生成uta.dll文件。

 

 

2.10       配置

2.10.1             uta.conf

插件中配置服务配置文件。

一个插件可以部署多个服务,每个服务用一个xml文件描述。

<?xml version=”1.0″ encoding=”gb2312″ standalone=”yes”?>

<config>

<services>

<service>TestService.xml</service> <!– 服务文件名称 –>

</services>

</config>

 

TestService.xml是TestService服务的配置文件。

 

2.10.2             TestService.xml

<?xml version=”1.0″ encoding=”gb2312″ standalone=”yes”?>

<config>

<service> <!– thrift服务端配置 –>

         <url>127.0.0.1:9091</url> <!– 服务URL –>

         <name>TestService_1</name> <!– 服务名称 –>

</service>

<success_encode>SUCCESS</success_encode> <!–处理成功的字符编码 –>

<!– thrift服务类型名称 –>

<service_type_name>TestServiceClient</service_type_name>

<protocols>

<!– 协议映射到thrift方法调用 –>

<protocol>

<!– 默认:5 –>

<msgtype>5</msgtype>

<!– 协议编号 –>

<msgid>3522</msgid> 

<!– thrift方法 –>

<prototype>ThriftCommonReturn AddProt(ProtInfo)</prototype>

<!– 方法参数,返回值类型成员与协议参数(行集)名称映射 –>

<type_properties_map>

<type>

                   <name>ProtInfo</name>

                   <!– 属性名:参数名对,多个映射项用’,’分隔 –>

                   <map>eid:EID,applyFlag:ApplyFlag</map>

                   </type>

</type_properties_map>

</protocol>

 

<protocol>

<!– 查询商家账户余额 –>

<!– 默认:5 –>

<msgtype>5</msgtype>

<!– 协议编号 –>

<msgid>3503</msgid> 

<!– thrift方法 –>

<prototype>QueryAccountBalanceResponse QueryAccountBalance(i32 eid)</prototype>

</protocol>

 

<protocol>

<!– 查询协议 –>

<!– 默认:5 –>

<msgtype>5</msgtype>

<!– 协议编号 –>

<msgid>3524</msgid> 

<!– thrift方法 –>

<prototype>QueryProtResponse QueryProt(i32 eid,i32 countSpecialFlag)</prototype>

</protocol>

 

</protocols>

</config>

 

3      规范

3.1    UTA Thrift规格

l  一个协议只能对应到一个服务的一个方法

l  方法最多支持5个参数

l  方法参数类型支持bool,byte,i16,i32,i64,double,string,结构

l  容器类型仅支持list

l  返回值类型支持void,结构

l  协议参数名称,行集列名称大小写敏感,字母引导的由字母和数字组成(注意不能用下划线’_’)

 

3.2    开发规范

系统服务层次示意图如下:

 UTA使用说明_uhf无线麦克风怎么用

应用协议是面向客户端的,是图中通路1所使用的语言。

Thrift服务面向业务逻辑,是组中通路2所使用的语言。业务逻辑层按高内聚低耦合原则设计。

以促销方案的商品为例,促销服务只按商品编码处理和返回,是否显示为商品名称根据客户端需要可以在客户端或者接入与表示层提供,也可以利用各级缓存机制。

 

在系统设计阶段,从客户端驱动,首先分析出应用协议,在此基础上定义thrift服务。

应用协议与thrift服务没有一一对应的要求,如应用协议规定需要返回商品名称,则可由接入与表示层服务器分别调用促销服务和公共服务的商品资料查询实现。

 

thrift服务定义时按照以下的规范,在命名上与应用协议保持一致可以支持通用化处理。

 

3.3    应用协议定义规范

l  协议参数名称,行集列名称大小写敏感,字母引导的由字母和数字组成(注意不能用下划线’_’)

 

3.4    Thrift服务定义规范

l  变量命名约定

—字母引导的由字符和数字组成的C/C++标识符(不要使用下划线’_’),建议与应用协议保持一致(不一致时需在服务配置中映射)

—单个下划线的变量保留给系统变量,如分页控制

—双下划线变量保留给thrift内部使用

l  建议方法原型: 返回类型 方法名(请求类型)

协议请求作为请求类型,返回作为返回类型(默认ThriftCommonReturn)

返回类型和请求类型名称在方法名后面分别带”Response”,”Request”。

如AddSchema方法的原型为:

AddSchemaResponse AddSchema(AddSchemaRequest)

3.5    预定义变量

以下预定义变量及对应的协议参数已约定用途。

—返回变量

以下变量出现在请求的返回对象中.

变量名

类型

UMX参数

说明

retCode

string

#SC

处理结果码

retMsg

string

#SS

处理结果描述信息,出错时为错误描述信息

result

string

#RESULT

业务描述信息

 

—请求变量

以下变量出现在请求对象中.

变量名

类型

UMX参数

说明

_srcType

byte

#SRC_TYPE

客户端类型,即Yoop概要设计的端点类型

_srcId

i32

#SRC_ID

客户端ID

_srcIdHi

i32

#SRC_ID_HI

客户端ID高32位

 

 

 

 

_appId

i16

#APP_ID

应用ID

_appProperties

i16

#APP_PROPERTIES

应用属性

bit0-是否是移动

bit1-是否是零售

 

—分页变量

为统一分页处理而定义.

请求消息参数名

变量名

类型

UMX参数

说明

_pageFlag

bool

#PAGIZE.FLAG

是否需要分页

_pageNo

i32

#PAGIZE.PAGE_NO

页号(从1开始)

_pageSize

i16

#PAGIZE.PAGE_SIZE

页大小

_pageCountFlag

bool

#PAGIZE.COUNT_FLAG

是否返回总记录数,默认第1页自动返回记录总数

_pageNoDup

bool

#PAGIZE.PAGE_NO_DUP

是否在返回消息中包含页号

 

返回消息参数名

变量名

类型

UMX参数

说明

_pageTotal

i32

#PAGIZE.TOTAL

总记录数

 

今天的文章
UTA使用说明_uhf无线麦克风怎么用分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/60130.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注