DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: tkzcol
今日帖子: 5
在线用户: 31
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 0:18:33
标题:
mormot2框架小白入门避坑贴 浏览:2080
加入我的收藏
楼主: mormot2是一个很强大的框,网上能搜到的有内容的文章很少,此贴算抛砖引玉,希望有更多的人能能使用上它,有不正确的地方请多指正。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 0:45:21
1楼: 先从数据库说一说个人遇到的坑。
一、com资源释放的问题。
如果是进行后端web服务开发,建议使用ODBC连接方式,副作用就要装odbc驱动(可从微软下载最新18版本).这样在服务线程中不会有coinitxxx, couinitxxxx的问题。
如果想用oledb驱动,那么就要要进行com的counit....,com的初始化工作mormot架框在newsafeconnect中已经做了,但线程在回收入池的时候,需要couninitxxxx,这必须手动代码实现,这在框架文档和源代码中都有说明。如果觉得每个方法中都要写一下麻烦,可以在一个线程回调的全局方法(事件)中加一行代码,具体是哪个方法记不清了。
二、odbc调用储存过程的问题
如果使用ODBC方式,那么在存储过程的代码中,一定要加上set nocount on, 这个问题困扰我一段时间。

ODBC连接方式,我感觉最好用的方式是这样的:
var dsn := rawbytestring('FILEDSN='+ExtractFilePath(ParamStr(0))+'db.dsn');
  var db := TSqlDBODBCConnectionProperties.Create('', dsn, '', '');
这和古老的ado很像,此外,用户名和密码可以出现在这个dsn文件中。

另外一种方式就是直接在 控制面板 里的odbc中先创建好连接,比如先创建了一个名字是321的连接,再写下面这样的代码:
TSqlDBOdbcConnectionProperties.Create('321', '', 'sa', '12345');   //已经建好321连接

还有其它的方式
TODBCProp.Create('', 'DRIVER=ODBC Driver 18 for SQL Server;UID=sa;PWD=12345;Trusted_Con_nection=No;SERVER=192.168.1.50;MARS_Con_nection=yes;DATABASE=MyDB', '', '');

TODBCProp.Create('', 'DSN=123;UID=sa;PWD=Sa12345;DATABASE=MyDB', '', ''); 

要注意的一个属性:
[ODBC]
DRIVER=ODBC Driver 18 for SQL Server
UID=sa
TransparentNetworkIPResolution=Disabled
Region_al=No
TrustServerCertificate=Yes
Encrypt=Optional   //这个属性
LANGUAGE=简体中文
DATABASE=MyDB
WSID=JJW-PC
APP=Microsoft? Windows? Operating System
Trusted_Con_nection=No
SERVER=192.168.1.50
PWD=12345

如果想调出系统的odbc连接对话框,需要设置这个属性
TSqlDBOdbcConnectionProperties.SqlDriverConnectPrompt := TRUE;
----------------------------------------------
学无止境
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2024/1/27 0:53:23
1楼: 内容呢?
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 1:04:13
2楼: 如果已经通过TSqlDBXXXXDBMSSQLConnectionProperties建立了数据库连接,那么就可以直接执行sql来返回结果了,例如调用ExecuteInlined(RawUtf8(sql), true)返回一个ISqlDBRows接口,再  
  while true do
  begin
    var jsonStr := string(rows.StepAsJson());
    if jsonStr = '' then Exit;
    ...
  end;
或者读取num字段的值 
rows.ColumnInt('num');
或者直接把json数据写到一个stream中,
var strStream := TStringStream.Create('', TEncoding.UTF8, True);
FetchAllToJson(strStream, True);
返回的接口带来的一个好处是不用try  finally end 来释放了,代码更清爽。

TSqlDBXXXXDBMSSQLConnectionProperties有多种方法来执行sql,每个方法怎么用实践中再根据情况调用。

另外,mormot框架很好为解决了从数据到json的问题的。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 1:41:24
3楼: 前面大体上说了下数据库的基本使用,接着再说一下http服务。
mormot框架中,mormot.xxx.server的单元不只一个,这里先提一下核心类
THttpServerGeneric,它是其它几个server的父类,mormot2框架是跨平台的,THttpRestServer内部使用的都是它的子类,具体使用哪个子类,可以在create的参数中可以指定。
这里考虑跨平台的问题,就拿THttpServer来说,(如果是windows系统,最好使用THttpApiServer)。

和THttpServer一起干活的两个重要的类:TUriRouter和THttpServerRequestAbstract
TUriRouter封装了路由,是THttpServer的一下属性:Router. 而 THttpServerRequestAbstract封装了request和response。
如果想让httpserver跑起来处理请求,就需要添加路由,添加路由有多种方式,这里仅说一种方法。
HttpServer.Route.Run(
[urmGet],   //方法类型, get post delete等
'\home',    //url
DoRequest); 
DoRequest方法的定义:
  TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object;
这个写一些示例代码,是个人刚开始接触mormot时候的实现方法:

//基类
  TAbstractRoute = class
    function DoRequest(Ctxt: THttpServerRequestAbstract): Cardinal; virtual; abstract;
  end;

//贴标签用
  TRouteAttr = class(TCustomAttribute)
  private
    FUrl: string;
  protected
    function GetUrl: string; virtual;
  public
    constructor Create(AUrl: string); overload;
    property Url: string read GetUrl;
  end;

  //设备连接
  [TGet('home')]  //url
  TGetXXXXX = class(TAbstractRoute)
  public
    procedure DoResponse(SN: string); override;
  end;

//创建路由
  Result := TMyHttpServer.Create(UTF8String(Port.ToString));

//  Result.OnConnect := OnStartEvent;
//  Result.OnDisconnect := OnStopEvent;
//  Result.OnRequest := RequestEvent;

  var List := TList<TAbstractRoute>.Create;

  List.Add(TGetXXXXX1.Create);
  List.Add(TGetXXXXX2.Create);
  ...

  var LCtx := TRttiContext.Create;
  for var R in List do
  begin
    var LType := LCtx.GetType(R.ClassInfo);
    for var LAttr in LType.GetAttributes do
    begin
      if LAttr is TGet then
        Result.AddRoute(TUriRouterMethod.urmGet, TRouteAttr(LAttr).Url, R)
      else if LAttr is TPost then
        Result.AddRoute(TUriRouterMethod.urmPost, TRouteAttr(LAttr).Url, R)
      else
        raise Exception.Create('URL方法类型错误。');
    end;
  end;

//添加路由
procedure TMyHttpServer.AddRoute(MethodType: TUriRouterMethod; AUrl: string; Value: TAbstractRoute);
begin
  FRoutes.Add(Value);
  FHttpServer.Route.Run([MethodType], RawByteString(AUrl), Value.DoRequest);
end;
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 1:47:06
4楼: 说一下取url中参数的坑
Ctxt.UrlParam(RawUTF8('XXXX='.ToUpper), val)

1、取参数值的时候要带上 = 
2、要参数名要大写

THttpServerRequestAbstract的incontent是请求body的数据。
outContent是返回的数据。
TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object;
的返回值就是response的code,  比如200, 404, 50x等

至于json2object或object2json就不写,用mormot框架的或用delphi自带的都行。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 1:54:30
5楼: ThttpServer在mormot2框架中相对业来说比较原始底层的,个人觉得已经很好用了,但mormot并不限于此,还提供了更高一级的restserver框架,使用起来也不算复杂,先贴上代码,后续再写。
  FRestServer := TRestServerFullMemory.CreateWithOwnModel([], False, 'root');
  FRestServer.CreateMissingTables();
  var f := FRestServer.ServiceDefine(TMyService, [IMyService], sicShared).
    SetOptions([], [optExecLockedPerInterface, optNoLogInput, optNoLogOutput]); // thread-safe fConnected[]
  f.ByPassAuthentication := true;
  f.ResultAsJsonObjectWithoutResult := True;  
  f.ResultAsJsonObject := False;
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 10:41:11
6楼: mormot的文档最新到1.18,页数有2K+。里面有很多SOA,DDD,MVC以及mormot在此基础上如何设计的描述。

简单的和datasnap对比一下, THttpServer相当于WebBroker,HttServer的router相当于webModule的Actions。而restServer相当于datasnap rest中的webmodule上的那些ds开头的组件。

datasnap框架的大体写法是先定义自己类,然后与dsserver绑定,这个过程中并没有orm相关的东西,需要自己写代码进行orm。在mormot中是有orm这一块东西的(可选的,看是否涉及到数据库)

简单来说restserver主要作用有两个:一是路由,二是操纵orm。restserver也有个router,是resturiRouter,不是uriRouter。

TRestServerFullMemory.CreateWithOwnModel([], False, 'root');

第一个参数是ormModel, 第二个参数设定是否验证,第三个参数是设定restrouter的根路径.
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 10:56:40
7楼: 创建完restServer后下面的这行代码一定要带上(具体原因展开说就太多了),
FRestServer.CreateMissingTables();
主要说下ServiceDefine方法,
  var f := FRestServer.ServiceDefine(TMyService, [IMyService], sicShared).
    SetOptions([], [optExecLockedPerInterface, optNoLogInput, optNoLogOutput]); 
这个方法主要作用相当于datasnap中的TDSServerClass到具体类的绑定。
首先要写一个接口,继承IInvokable,(不一定非要从这个接口继承,见mormot方法或ddemo)
  IMyService = interface(IInvokable)
    ['{C828803C-77DA-44E7-A599-0569DD52D157}']
    function GetTime: string
  end;
注册接口是必须的:
initialization
  TInterfaceFactory.RegisterInterfaces([
    TypeInfo(IAccessControllerService)
    ]);

然后再写一个类实现接口方法
  TMyService = class(TInterfacedObject, IMyService)
    function GetTime: string;
  end;

接再把restserver注入到httpserver.
FHttpServer := TRestHttpServer.Create(APort, [FRestServer], '+');

以上代码就相当于完成了datasnap的全部,不同之处就是从IDE向导一步步的创建改为手动建。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 11:09:00
8楼:  f.ResultAsJsonObjectWithoutResult := True;  
 f.ResultAsJsonObject := False;
这两行的代码用途,主要是控制返回结果的json格式。
在datasnap中,好像默认返回的json格式是[result: [xxx]],mormot同样类似。

关于路由重定向的三个方法:
TRestHttpServer.Route.Rewrite
TRestHttpServer.RootRedirectToUri
TRestHttpServer.DomainHostRedirect
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 14:37:16
9楼: 补充说一个路由重定向的坑,先从一个request请求开始。
  TRestHttpServer = class(TSynPersistent)
  protected
    fShutdownInProgress: boolean;
    fHttpServer: THttpServerGeneric;

TRestHttpServer并不是一个真正httpServer,它内部使用了THttpServerGeneric(它是一个真正在的httpServer类),此外,TRestHttpServer.Create时注册进来了一个RestServer,它先把THttpServerGeneric.onRequest事件接过来(赋值给自己的Request方法),然后再把request请求转发给restserver.uri方法,RestServer再转到Service完全一次调用请求。
之前说过,restServer有个RestUriRouter, httpServer有个UriRouter, 所以一次http请求会先经过RestUriRouter处理,如果未处理,再由UriRouter处理。
TRestHttpServer在create的时候,会先判断能不能用THttpApiServer,如果是winddows平台,并且可以创建THttpApiServer, 那么fHttpServer就是THttpApiServer,否则就是其它httpServer(THttpServerGeneric的其它子类)。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 15:03:10
10楼: A_HttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', useBidirSocket);

B_HttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', HTTP_DEFAULT_MODE);

上面创建了两个TRestHttpServer实例,在windows平台下,你会发现下面的重定向对于A_HttpServer可行,而B_HttpServer不可行。

HttpServer.Route.Rewrite(urmGet, '/GetTime', urmGet, '/root/MyService/GetTime');

主要原因是httpApiServer使用了httpapi.dll(详见mormot.lib.winhttp)的AddUrl注册uri,导致 /GetTime请求根本就没进来。

解决方案(解决方案不只这一种)
FHttpServer := TRestHttpServer.Create(APort, [FRestServer], '+', HTTP_DEFAULT_MODE, 32, secNone, '/'{aAdditionalUrl});
就是把aAdditionalUrl也带上。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/1/27 15:45:18
11楼: 关于开启websocket
调用TRestHttpServer.WebSocketsEnable即可,并不是所有的THttpServerGeneric子类都可以,只有属于HTTP_BIDIR = [useBidirSocket, useBidirAsync]的类才可以。

关于使用orm,标准创建过种大体上这样,

  FDBCon := TSqlDBOdbcConnectionProperties.Create('','FILEDSN=d:\db.dsn', '', '');

  FOrmModel := TOrmModel.Create([TPerson, TCar], 'root');

  OrmMapExternalAll(FOrmModel, FDBCon, []);

  FRestServer := TRestServerDB.Create(FOrmModel);

  FRestServer.ServiceDefine(TPersonService, [IPersonService], TServiceInstanceImplementation.sicShared);

  FRestServer.CreateMissingTables();

  FHttpServer := TRestHttpServer.Create('8000', [FRestServer], '+', useHttpSocket);
  FHttpServer.AccessControlAllowOrigin := '*';

注意事项:
1、 TRestServerDB 代替了 TRestServerFullMemory
2、DTO类继承TOrm,如:TPerson = class(TOrm)
3、把Orm注册到TOrmModel,
4、把OrmModel与数据库关键起来
5、把OrmModel注册到RestServer中
其它细节可以参考mormot的demo

个人对于mormot2摸索大体上如此,框架中其它好玩的东西并未涉及。以上所写的内容都比较肤浅,仅仅是针对datasnap做了比较泛的对比,希望能给想使用mormot的人一点点引导。才疏学浅,让大伙见笑了。
----------------------------------------------
学无止境
作者:
男 lufangyu (伟人录) ★☆☆☆☆ -
盒子活跃会员
2024/1/29 8:29:01
12楼: 都不想打扰楼主的分享,太有爱了。
----------------------------------------------
-
作者:
男 bdl1 (bdl1) ▲▲▲▲▲ -
普通会员
2024/1/29 9:45:19
13楼: 感谢楼主分享!!!
----------------------------------------------
-我的博客
作者:
男 homejun (homejun) ★☆☆☆☆ -
盒子活跃会员
2024/1/29 12:08:26
14楼: 好资料,楼主多多研究、多多分享!
----------------------------------------------
-delphi新资讯站 http://www.delphigear.cn
作者:
男 dacsd (ddd) ★☆☆☆☆ -
盒子活跃会员
2024/1/29 13:45:03
15楼: 好资料,多多分享!
----------------------------------------------
-
作者:
男 blade (百刃刀客) ★☆☆☆☆ -
盒子活跃会员
2024/1/29 22:30:40
16楼: 感谢楼主!
----------------------------------------------
-
作者:
男 ritapl (ritapl) ★☆☆☆☆ -
盒子活跃会员
2024/1/30 10:27:40
17楼: 这样的文章很有价值,我用的还是1.18用好几年了,该升级了
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2024/2/2 16:15:54
18楼: 赞楼主。虽然这个框架我没用过。
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/2/2 18:28:28
19楼: 先谢谢楼上兄弟们回复. 
补充一个接口调用的注意事项:
  IMyService = interface(IInvokable)
    ['{C828803C-77DA-44E7-A599-0569DD52D157}']
    function QueryUser(Id, Code: Integer): TServiceCustomAnswer;
    function AddUser(Id: Integer; const userInfo: TUserRec): TServiceCustomAnswer;
  end;
1、返回类型TServiceCustomAnswer(框架自带的一个record),可以解决[result:xxxx]的问题,返回结果不会有result项,你返回什么就是什么。
2、const userInfo: TUserRec,非简单类型时要加上const/var/out修饰。
3、get/post调用的细节,如果get方式,参数从?paramA=xx?paramB=xx取,如果是post方式,post中url = xxx?paramA=xx?paramB=xx 的参数会被忽略,框架只会从body中取参数值。
下面举个例子:
调用第一个方法:
fetch('http://127.0.0.1:8088/root/MyService/QueryUser?Id=88&Code=99&#39;)
第二个方法的post调用(get也可以,但比较麻烦,不解释):
const r = fetch('http://127.0.0.1:8088/root/MyService/AddUser&#39;, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          Id: 75,
          userInfo: {
          No:'1', Name: "aaa", Password:123456, Code :888087
          }
        })
    })
----------------------------------------------
学无止境
作者:
男 sxqwhxq (步惊云) ★☆☆☆☆ -
普通会员
2024/2/3 21:01:57
20楼: 中间件我只用rtc,原生、跨平台、开源,无论win/linus/android/ios/x86/arm/loongarch都可以用,不过很感谢楼主。
----------------------------------------------
-
作者:
男 lfyey121 (lfyey121) ★☆☆☆☆ -
普通会员
2024/2/4 14:15:07
21楼: mormot 使用variant进行json的解析,序列化、反序列化功能确实强大,最新增加的IDocList,IDocDict  是对TdocVariantData的二次包装,实现了python中的list,dict的功能,更方便了。就像js,python等动态语言处理一样。
----------------------------------------------
-
作者:
男 newsxy (呼呼) ★☆☆☆☆ -
盒子活跃会员
2024/2/5 10:09:44
22楼: 感谢楼主!
----------------------------------------------
-
作者:
男 ghs_79 (ghs) ★☆☆☆☆ -
盒子活跃会员
2024/2/21 8:31:54
23楼: 楼主大爱,我等默默看了好几遍。
----------------------------------------------
Delphi爱好者。
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/2/23 10:20:17
24楼: 补充websocket的使用。

这是最简单的方法一:
FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE);

FHttpServer.AccessControlAllowOrigin := '*';

  with FHttpServer.WebSocketsEnable('', '', True, [])^ do
  begin
    OnClientConnected := ClientConnectedHandle;
    OnClientDisconnected := ClientDisconnectedHandle;
  end;

//----------
procedure TForm1.ClientConnectedHandle(Sender: TObject);
begin
  FClients.Add(TWebSocketAsyncProcess(Sender));
end;

procedure TForm1.ClientDisconnectedHandle(Sender: TObject);
begin
  FClients.Remove(TWebSocketAsyncProcess(Sender));
end;

和datasnap的tcp/ip连接很类似.
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/2/23 10:22:16
25楼: 方法二:
  FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE);

  FHttpServer.AccessControlAllowOrigin := '*';
  TWebSocketAsyncServerRest(FHttpServer.HttpServer).ProcessClass := TWebSocketAsyncProcessEx;

//----------

  TWebSocketAsyncProcessEx = class(TWebSocketAsyncProcess)
  protected
    function ComputeContext(
      out RequestProcess: TOnHttpServerRequest): THttpServerRequestAbstract; override;
  public
    function CanGetFrame(TimeOut: cardinal; ErrorWithoutException: PInteger): boolean; override;
    function ReceiveBytes(P: PAnsiChar; count: PtrInt): integer; override;
    function SendBytes(P: pointer; Len: PtrInt): boolean; override;
    procedure SendFrameAsync(const Frame: TWebSocketFrame); override;
  end;

重写相应的方法。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/2/23 10:24:27
26楼: 方法三:
  FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE);

  FHttpServer.AccessControlAllowOrigin := '*';

  var wsServer := TWebSocketAsyncServerRest(FHttpServer.HttpServer);
  wsServer.OnWebSocketConnect := WSConnect;
  wsServer.OnWebSocketDisconnect := WSDisconnect;
  wsServer.OnWebSocketUpgraded := WSUpgraded;
  wsServer.OnWebSocketClose := WSClose;

//----------
procedure TForm1.WSClose(Protocol: TWebSocketProtocol);
begin
  TThread.Queue(nil, procedure begin
    Memo1.Lines.Add('Close');
  end);
end;

procedure TForm1.WSConnect(Sender: TWebSocketAsyncConnection);
begin
ted
  TThread.Queue(nil, procedure begin
    Memo1.Lines.Add('Connect');
  end);
end;

procedure TForm1.WSDisconnect(Sender: TWebSocketAsyncConnection);
begin
  TThread.Queue(nil, procedure begin
    Memo1.Lines.Add('Disconnect');
  end);
end;

function TForm1.WSUpgraded(Protocol: TWebSocketProtocol): integer;
begin
  Result := 200; //!!!重点
  TThread.Queue(nil, procedure begin
    Memo1.Lines.Add('Upgraded');
  end);
end;
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ★☆☆☆☆ -
普通会员
2024/2/23 10:28:10
27楼: 方法四:
  FHttpServer := TRestHttpServer.Create('8888', [FRestServer], '+', WEBSOCKETS_DEFAULT_MODE);

  FHttpServer.AccessControlAllowOrigin := '*';

  var wsServer := TWebSocketAsyncServerRest(FHttpServer.HttpServer);

  wsServer.WebSocketProtocols.Add(TMyWSProtocol.Create('myProtocol', 'jjw'));

//----------

  TMyWSProtocol = class(TWebSocketProtocol)
  protected
    procedure ProcessIncomingFrame(Sender: TWebSocketProcess;
      var request: TWebSocketFrame; const info: RawUtf8); override;
  public
    function Clone(const aClientUri: RawUtf8): TWebSocketProtocol; override;
  end;
重写这两个方法即可。
----------------------------------------------
学无止境
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行699.2188毫秒 RSS