网路冷眼@BlogJava

熙熙攘攘一闲人 以冷静的眼光观察技术
posts - 88, comments - 193, trackbacks - 0, articles - 28
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Chapter 14. REST API

Table of Contents

Repository(仓库)
Get Deployments(获取部署)
Get Deployment Resource(获取部署资源)
Delete Deployment(删除一个部署)
Delete Deployments(删除多个部署)
Engine(引擎)
Get Process Engine(获取流程引擎)
Processes(流程)
List Process Definitions(列出多个流程定义)
Get Process Definition(获取流程定义)
Get Process Definition Form(获取流程定义的格式)
Start Process Instance(启动流程实例)
Tasks(任务)
Get Task Summary(获取任务概要)
List Tasks(列出多个任务)
Get Task(获取一个任务)
Get Task Form(获取任务格式)
Perform Task Operation(执行任务操作)
Identity(身份认证)
Login(登录)
Get User(获取一个用户)
List User's Groups(列表用户所在的组)
Get Group(获取用户组)
List Group Users(列表多个组用户)
Management(管理)
List Jobs(列出多个作业)
Get Job(获取一个作业)
Execute Job(执行一个作业)
Execute Jobs(执行多个作业)
List Database Tables(列表显示数据库表)
Get Table Meta Data(获取库表元数据)
Get Table Data(获取库表数据)

<EXPERIMENTAL> ... the whole REST interface is still experimental

<EXPERIMENTAL> ...整个REST接口仍处于实验性阶段。

Activiti includes a REST API to the engine that will be deployed to your server when you run the setup script. The REST API uses JSON format (http://www.json.org) and is built upon the Spring Webscripts (http://www.springsurf.org).

当你运行setup脚本时,Activiti包括了可以部署到服务器上的引擎REST API。REST API使用JSON格式(http://www.json.org)并在Spring Webscripts((http://www.springsurf.org)上构建。

It is possible to browse the REST API if you point your browser to http://localhost:8080/activiti-rest/service/index and login as one of the admin users (kermit). If you click the "Browse by Web Script Package" link you will get an overview of the packages that are deployed on the server and you can easily navigate into a package to see the REST API calls that are defined for that package. The "Alfresco JavaScript Debugger" can be ignored since Java is used instead of JavaScript to implement the logic.

如果你把浏览器指向http://localhost:8080/activiti-rest/service/index 并登录为一个管理员用户(kermi),可能浏览REST API。如果你点击“Browse by Web Script Package“连接,你将得到那些部署到服务器山包的概要,你能够轻易地浏览定义在那个包的REST API调用。因为使用Java替代Javascript实现这个逻辑,所以能够忽略 "Alfresco JavaScript Debugger" 。

Each REST API call has its individual authorization level and you must be logged in as a user to invoke a REST API call (except for the /login service). Authentication is done using Basic HTTP Authentication, so if you logged in as an admin (i.e. kermit) to browse the REST API, as described above, you should be able to perform all calls as described below.

每个REST API调用具有它单个的认证级别,为了调用REST API方法,你必须一个用户登录(除了/login服务)。因为使用基本的HTTP认证( Basic HTTP Authentication)来完成,所以如果你以一个管理员登录(例如kermit)可以浏览以上的REST API,那么你应当能够完成如下的所有的调用。

The API follows normal REST API conventions using GET for read operations, POST for creating objects, PUT for updating and performing operations on already created objects and finally DELETE for deleting objects. When performing a call that affects multiple objects POST is used on all such operations for consistency and making sure that an unlimited number of objects may be used. The reason for using POST is that the HTTP DELETE method doesn't implicitly allow request bodies and therefore, a call using DELETE, in theory, could get it's request body stripped out by a proxy. So to be certain this doesn't happen we use POST, even when PUT could have been used to update multiple objects, for consistency.

API遵从通常的REST API约定:用GET读取操作,用POST建立对象,用PUT在已经建立的对象上更新和执行操作,最后用DELETE删除对象。为了一致性,并且可以使用无限的对象,当执行影响多个对象的调用时,使用POST。使用POST的原因,是HTTP DELETE方法不允许显式使用请求包体。理论上使用DELETE的调用可能得到被代理裁剪的请求包体。所以,在某种程度上,我们使用POST就不会发生这种情况。甚至当当可能使用PUT来更新多个对象时,为了保证一致性,也采用POST。

The base URL for invoking a REST call is http://localhost:8080/activiti-rest/service/. So for example to list the process definitions in the engine point your browser to: http://localhost:8080/activiti-rest/service/process-engine

调用REST方法的基本URL是http://localhost:8080/activiti-rest/service/。所以,为了列出引擎里面的流程定义,请将浏览器指向:http://localhost:8080/activiti-rest/service/process-engine

Please look below to see what REST API calls that currently are available. Please consider the "API" sections as a "one line hint" to what functionality of the core API that is used to implement the REST API call.

请查看下面的当前可获得的REST API调用。请参考"API"部分作为对core API具有的功能性的“一行提示(one line hint)"。核心API用来实现REST API 调用。

Repository(仓库)

Get Deployments(获取部署)

Returns a paginated list deployments that can be sorted by "id", "name" or "deploymentTime".

返回可以以分页列表显示的部署。部署能够以"id", "name"或 or "deploymentTime"排序

  • Request: GET /deployments?start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getRepositoryService().createDeploymentQuery().listPage()

  • Response:

    {
        "data": [
        {
        "id": "10",
        "name": "activiti-examples.bar",
        "deploymentTime": "2010-10-13T14:54:26.750+02:00"
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }
Get Deployment Resource(获取部署资源)

Returns a resource from the deployment. Example: /deployment/10/resource/org/activiti/examples/bpmn/usertask/FinancialReportProcess.bpmn20.xml

从部署获取资源。例如:/deployment/10/resource/org/activiti/examples/bpmn/usertask/FinancialReportProcess.bpmn20.xml

  • Request: GET /deployment/{deploymentId}/resource/{resourceName}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getRepositoryService().getResourceAsStream(deploymentId, resourceName)

  • Response:

    I.e a .bpmn20.xml file, an image or whatever type of file the deployment resource contained.
Delete Deployment(删除一个部署)

Deletes a deployment.

删除一个部署.

  • Request: DELETE /deployment/{deploymentId}?cascade={cascade?}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getRepositoryService().deleteDeployment(deploymentId)

  • Response:

    {
        "success": true
        }
Delete Deployments(删除多个部署)

Deletes multiple deployment.

删除多个部署。

  • Request: POST /deployments?cascade={cascade?}

    {
        "deploymentIds": [ "10", "11" ]
        }
  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getRepositoryService().deleteDeployment(deploymentId)

  • Response:

    {
        "success": true
        }

Engine(引擎)

Get Process Engine(获取流程引擎)

Returns the process engine initialization details. If something went wrong during startup, details about the error will be given in the "exception" attribute in the response.

返回流程引擎初始化过程的细节。如果在启动阶段有错,细节将显示在响应里面的"异常"属性。

  • Request: GET /process-engine

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName)

  • Response:

    {
        "name": "default",
        "resourceUrl": "jar:file:\//<path-to-deployment>\/activiti-cfg.jar!\/activiti.properties",
        "exception": null,
        "version": "5.0-SNAPSHOT"
        }

Processes(流程)

List Process Definitions(列出多个流程定义)

Returns details about the deployed process definitions that can be sorted by "id", "name", "version" or "deploymentTime". The name of the BPMN2.0 XML process diagram is given in the "resourceName" attribute and can, in combination with the "deploymentId" attribute, be retrieved from the GET Deployment Resource REST API call above. If the process has a start form it is given in the "startFormResourceKey" attribute. The start form for a process can be retrieved from the GET Start Process Form REST API call.

返回有关已部署流程定义的细节。流程定义能够按"id", "name", "version" 或 "deploymentTime"排序。和“deploymentId"属性组合,在“resourceName"属性给定的BPMN 2.0 XML流程图的名称能够从GET Deployment Resource REST API 。以上的调用检索出来。一个流程的开始格式能够从GET Start Process Form REST Form REST API 调用检索出来。

  • Paginated Request: GET /process-definitions?start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getProcessService().findProcessDefinitions()

  • Paginated Response:

    {
        "data": [
        {
        "id": "financialReport:1",
        "key": "financialReport",
        "version": 1,
        "name": "Monthly financial report",
        "resourceName": "org/activiti/examples/bpmn/usertask/FinancialReportProcess.bpmn20.xml",
        "deploymentId": "10",
        "startFormResourceKey": null
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }
Get Process Definition(获取流程定义)

Returns details about a deployed process definition.

获取一个已部署流程定义的细节。

  • Request: GET /process-definition/{processDefinitionId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getProcessService().findProcessDefinitionById(processDefinitionId)

  • Response:

    {
        "id": "financialReport:1",
        "key": "financialReport",
        "version": 1,
        "name": "Monthly financial report",
        "resourceName": "org/activiti/examples/bpmn/usertask/FinancialReportProcess.bpmn20.xml",
        "deploymentId": "10",
        "startFormResourceKey": null
        }
Get Process Definition Form(获取流程定义的格式)

Returns a process definition's form.

返回一个流程定义的格式。

  • Request: GET /process-definition/{processDefinitionId}/form[?format=html|json]

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().getRenderedStartFormById(processDefinitionId)

  • Response:

    <user-defined-response>
Start Process Instance(启动流程实例)

Creates a process instance based on a process definition and returns details about the newly created process instance. Additional variables (from a form) may be passed using the body object. In other words placing attributes next to the "processDefinitionId" attribute.

创建一个基于流程定义的流程实例并返回新创建的流程实例的细节。通过body对象可以传送额外的变量(来自表单)。换句话说,将属性放置到"processDefinitionId"属性之后。

These additional variables may also be described using "meta data fields". A variable named "numberOfDays" with value "2" may be described as an int using an extra variable named "numberOfDays_type" set to "Integer" and to describe it as a required variable use an extra variable named "numberOfDays_required" set to "true". If no type descriptor is used the value will be treated as a String as long as its surrounded by '"'-characters. Its also possible to set the type to "Boolean".

这些额外的变量也可以使用"meta data fields"来描述。带有值为”2“的名为"numberOfDays"的变量可以描述为int。使用额外的名叫numberOfDays_type的变量设置为”Integer“。使用额外的设置为”true“的名叫”numberOfDays_required“的变量来作为需要的变量来描述。如果没有类型,一旦用""字符包围,在值里面使用的描述符将以字符串对待。

Note that if a value is submitted as true (instead of "true") it will be treated as a Boolean even if no descriptor is used. The same is also valid for number, i.e., 123 will become an Integer but "123" will become a String (unless a descriptor is defined). Note that no variables containing "_" in the name will be saved, they are only treated as meta data fields.

注意如果以true提交(代替"true"),甚至没有使用描述符,它将以Boolen值对待。同样对数值也一样,例如123将成为一个Integer,而”123“将成为字符串(除非定义一个描述符)。注意在名称里面包含"_" 的变量将不被保存,它们只是作为元数据的字段。

The reason for using these meta data fields is to make it possible using a standard HTML form to submit the values (since an HTML form submits everything as strings its not possible to distinguish the type of a value as in JSON). HTML submission will be supported in the near future. It is of course not an optimal solution to let the client send instructions to the server about which variables that are required and what type they have, but this is a temporary solution to enable simple form handling. We are currently looking for more proper solutions for forms, containing real meta models that can be used on the server to avoid using meta data fields like above. I.e. X-forms or Alfresco Forms. Please feel free to give suggestions or tips in the Activiti Forum.

使用这些元数据字段的理由是使得采用标准的HTML表单来提交这些值成为可能(因为HTML表单以字符串方式提交任何类型,而在JSON里面区分值类型是不可能的)。在不远的将来将支持HTML提交。当然,尽管让客户将有关需要那个变量及其类型的指令发送到服务器并不是优选,但是这是让简单表单处理的临时方案。我们现在正在寻找表单更合适的解决方案,包括为了避免使用上述元数据字段而在服务器端使用的真实的元模型。例如,X-forms 或者 Alfresco Forms.请在Activiti论坛上自由发表建议和提示。

  • Request: POST /process-instance

    {
        "processDefinitionId":"financialReport:1"
        }
  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getProcessService().startProcessInstanceById(processDefinitionId[, variables])

  • Response:

    {
        "id": "217",
        "processDefinitionId": "financialReport:1",
        "activityNames": ["writeReportTask"],
        "ended": true
        }

Tasks(任务)

Get Task Summary(获取任务概要)

Returns a task summary for a specific user: The number of tasks assigned the user, how many unassigned tasks that the user may claim and how many unassigned tasks there are per group that the user is a member of.

返回某一用户的任务概要:分配给用户的任务数目,用户可以领取的未分配任务的数目,以及用户所在每个组具有的未分配的任务数目。

  • Request: GET /tasks-summary?user={userId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().createTaskQuery().xxx().count()

  • Response:

    {
        "assigned": {
        "total": 0
        },
        "unassigned": {
        "total": 1,
        "groups":
        {
        "accountancy": 1,
        "sales": 0,
        "engineering": 0,
        "management": 0
        }
        }
        }
List Tasks(列出多个任务)

Returns a paginated list of tasks that can be sorted by: "id", "name", "description", "priority", "assignee", "executionId" or "processInstanceId". The list must be based on a user of a specific role: assignee (lists the tasks assigned to the user) or candidate (lists the tasks that the user may claim) or a candidate group (lists tasks that the members of the group may claim). If the task has a form it is given in the "formResourceKey" attribute. The form for a task can be retrieved from the GET Task Form REST API call.

返回能够按"id", "name", "description", "priority", "assignee", "executionId" 或 "processInstanceId"排序的多个任务的分页列表。这个列表必须基于某个角色的用户:受托人(列出分配给用户的任务)或者候选人(列出用户可以领取的任务)或者候选人组(列出组成员可以领取任务)。如果这个任务有一个表单,在"formResourceKey" 属性里给出。任务表单可以从 GET Task Form REST API调用中检索。

  • Paginated Request: GET /tasks?[assignee={userId}|candidate={userId}|candidate-group={groupId}]&start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().createTaskQuery().xxx().listPage()

  • Paginated Response:

    {
        "data": [
        {
        "id": 127,
        "name": "Handle vacation request",
        "description": "Vacation request by Kermit",
        "priority": 50,
        "assignee": null,
        "executionId": 118,
        "formResourceKey": "org/activiti/examples/taskforms/approve.form"
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }
Get Task(获取一个任务)

Returns details about the task with the task id

. 返回带有task id的任务有关的细节。

  • Request: GET /task/{taskId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().findTask(taskId)

  • Response:

    {
        "id": 127,
        "name": "Handle vacation request",
        "description": "Vacation request by Kermit",
        "priority": 50,
        "assignee": null,
        "executionId": 118,
        "formResourceKey": "org/activiti/examples/taskforms/approve.form"
        }
Get Task Form(获取任务格式)

Returns a task's form.

返回一个任务的格式。

  • Request: GET /task/{taskId}/form

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().getRenderedTaskForm(taskId)

  • Response:

    <user-defined-response>
Perform Task Operation(执行任务操作)

Performs an operation (claim or complete) on a task. For the "complete" operation additional variables (from a form) may be passed in the body. To read more about additional variables from forms, visit the Start Process Instance section

在任务上执行操作(领取或者完成)。针对“完成“操作,可以在包体里面传送的额外的变量(来自格式)。为了读取来自格式里面的额外变量,访问“启动流程示例( Start Process Instance)部分。

  • Request: PUT /task/{taskId}/[claim|complete]

    {}
  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getTaskService().xxx(taskId ...)

  • Response:

    {
        "success": true
        }

Identity(身份认证)

Login(登录)

Authenticates a user. If user and password doesn't match a response with status 403 is returned. If authentication is successful, a response with status 200 is returned.

认证一个用户。如果用户和密码不一致,返回一个状态为403的响应。如果认证成功,返回一个状态为200的状态。

  • Request: POST /login

    {
        "userId": "kermit",
        "password": "kermit"
        }
  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getIdentityService().checkPassword(userId, password)

  • Response:

    {
        "success": true
        }
Get User(获取一个用户)

Returns details about a user.

返回有关一个用户的细节。

  • Request: GET /user/{userId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getIdentityService().findUser(userId)

  • Response:

    {
        "id": "kermit",
        "firstName": "Kermit",
        "lastName": "the Frog",
        "email": "kermit@server.com"
        }
List User's Groups(列表用户所在的组)

Returns a paginated list groups belonging to a user that can be sort by "id", "name" or "type". To only get groups of a certain type use the "type" parameter.

返回属于一个用户的可分页列表的组。用户能够按 "id", "name" 或or "type"排序。只获取某一类型的组,使用“type“参数。

  • Paginated Request: GET /user/{userId}/groups[?type=groupType]?start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getIdentityService().xxx(userId[, groupType])

  • Paginated Response:

    {
        data: [
        {
        "id": "admin",
        "name": "System administrator",
        "type": "security-role"
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }
Get Group(获取用户组)

Returns details about a group.

返回有关组的细节。

  • Request: GET /group/{groupId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getIdentityService().findGroup(groupId)

  • Response:

    {
        "id": "admin",
        "name": "System administrator",
        "type": "security-role"
        }
List Group Users(列表多个组用户)

Returns details about a group's users that can be sorted by "id", "firstName", "lastName" or "email".

返回有关能够按 "id", "firstName", "lastName"或者 "email"排序的组用户的细节。

  • Paginated Request: GET /groups/{groupId}/users

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getIdentityService().findUsersByGroup(userId)

  • Paginated Response:

    {
        data: [
        {
        "id": "kermit",
        "firstName": "Kermit",
        "lastName": "the Frog",
        "email": "kermit@server.com"
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }

Management(管理)

List Jobs(列出多个作业)

Returns a paginated list of jobs that can be sorted by "id", "process-instance-id", "execution-id", "due-date", "retries" or some custom arbitrary property id. The list can also be filtered by process instance id, due date or if the jobs have retries, are executable or only have messages or timers.

返回多个作业的分页列表:可以按"id", "process-instance-id", "execution-id", "due-date", "retries" 或者一下定制的属性id排序。这个列表也能够按process instance id, due date进行过滤。列表是可执行的或者只具有消息或者定时器。

  • Paginated Request: GET /management/jobs?process-instance={processInstanceId?}&with-retries-left={withRetriesLeft=false}&executable={axecutable=false}&only-timers={onlyTimers=false}&only-messages={onlyMessage=false}&duedate-lt={iso8601Date}&duedate-ltoe={iso8601Date}&duedate-ht={iso8601Date}&duedate-htoe={iso8601Date}&start={start=0}&size={size=10}&sort={sort=id}&order={order=asc}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).createJobQuery().xxx().listPage()

  • Paginated Response:

    {
        "data": [
        {
        "id": "212",
        "executionId": "211",
        "retries": -1,
        "processInstanceId": "210",
        "dueDate": null,
        "assignee": null,
        "exceptionMessage": "Can\'t find scripting engine for \'groovy\'"
        }
        ],
        "total": 1,
        "start": 0,
        "sort": "id",
        "order": "asc",
        "size": 1
        }
Get Job(获取一个作业)

Returns details about a job.

返回一个作业有关的细节。

  • Request: GET /management/job({jobId}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).createJobQuery().id(jobId).singleResult()

  • Response:

    {
        "id": "212",
        "executionId": "211",
        "retries": -1,
        "processInstanceId": "210",
        "dueDate": null,
        "assignee": null,
        "exceptionMessage": "Can\'t find scripting engine for \'groovy\'",
        "stacktrace": "org.activiti.engine.ActivitiException: Can't find scripting engine for 'groovy'\n\tat ..."
        }
Execute Job(执行一个作业)

Executes a job.

执行一个作业。

  • Request: PUT /management/job/{jobId}/execute

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getManagementService().executeJob(jobId)

  • Response:

    {
        "success": true
        }
Execute Jobs(执行多个作业)

Executes multiple job.

执行多个作业。

  • Request: POST /management/jobs/execute

    {
        "jobIds": [ "212" ]
        }
  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getManagementService().executeJob(jobId)

  • Response:

    {
        "success": true
        }
List Database Tables(列表显示数据库表)

Returns meta data information about all database tables in the engine.

返回有关引擎里面所有数据库表的元数据信息。

  • Request: GET /management/tables

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getManagementService().getTableCount()

  • Response:

    {
        "data": [
        {
        "tableName": "ACT_GE_PROPERTY",
        "noOfResults": 2
        }
        ]
        }
Get Table Meta Data(获取库表元数据)

Returns meta data about a database table.

返回有关数据库表的元数据。

  • Request: GET /management/table/{tableName}

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getManagementService().getTableMetaData(tableName))

  • Response:

    {
        "tableName": "ACT_GE_PROPERTY",
        "columnNames": ["REV_","NAME_","VALUE_"],
        "columnNames": ["class java.lang.Integer", "class java.lang.String", "class java.lang.String"]
        }
Get Table Data(获取库表数据)

Returns a paginated list of database table data.

返回数据库表数据的分页列表。

  • Paginated Request: GET /management/table/{tableName}/data

  • API: ProcessEngines.getProcessEngine(configuredProcessEngineName).getManagementService().createTablePageQuery().tableName(tableName).start(start).size(size).orderXXX(sort).singleResult();

  • Paginated Response:

    {
        "data": [
        {
        "NAME_": "schema.version",
        "REV_": "1",
        "VALUE_": "5.0-SNAPSHOT"
        },
        {
        "NAME_": "next.dbid",
        "REV_": "4",
        "VALUE_": "310"
        }
        ],
        "total": 2,
        "start": 0,
        "sort": "NAME_",
        "order": "asc",
        "size": 2
        }

The whole REST interface is still experimental ... </EXPERIMENTAL>

整个REST接口仍处于实验阶段 ... </EXPERIMENTAL>


评论

# re: Activiti User Guide(Activiti用户指南)-Chapter 14. REST API  回复  更多评论   

2013-07-23 10:31 by yxk
这个文档写错了 。别照这个搞了 。http://www.activiti.org/userguide/#N12B41这个是对的

只有注册用户登录后才能发表评论。


网站导航: