Simon.Sun 发表于 2023-10-8 10:24:27

服务端命令作为WebAPI供三方调用和跨域问题解决

本帖最后由 Simon.Sun 于 2023-10-8 10:25 编辑

活字格的服务端命令是系统间集成交互的大杀器,服务端命令不光可以通过发送 HTTP 请求命令去调用第三方系统的接口,重要的一点是服务端命令可以作为 WebAPI 的方式将活字格应用的能力开放出去,第三方通过 HTTP 请求协议调用这些接口,就可以达到对接的目的。

既然是 HTTP 接口的调用,调用方肯定需要知道,接口的请求方式,请求的路径,请求参数,返回的结果。那么接下来先说明服务端命令作为接口对外调用时,这四个方面的规格是怎么样的。
注:后续内容多和 HTTP 协议有关,如果对于 HTTP 不了解的,推荐大家先看下下面的 HTTP 协议介绍:HTTP 教程 | 菜鸟教程 (runoob.com)

基本使用
1. 请求方式
截止目前,活字格服务端命令作为 WebAPI 对外调用支持的请求方式为:GET 和 POST(也可以设置为 GET/POST,这样服务端命令就能够同时支持 GET 和 POST)。
2. 请求路径
服务端命令创建好后,其就已经具备了其对外访问的 URL,格式为: <应用地址>/ServerCommand/<服务端命令名称>。
举个例子,服务端命令命名为:GetExample
如果是本地调试,请求路径为:
http://localhost:10765/Forguncy/ServerCommand/GetExample如果发布到了活字格服务器,且发布的应用名称为 Rest,发布的端口为 8080,服务器的路径为 www.example.com,请求路径为:
http://www.example.com:8080/Rest/ServerCommand/GetExample3. 请求参数
服务端命令的请求参数有两种类型,基础类型、数组类型(只发送变更数据)。参数在调用传输时,根据参数文本的格式不同,请求处理的方式也有所区别。服务端命令作为接口支持的参数格式(Content-Type)类型有两种:application/x-www-form-urlencoded(参数会以 k1=v1&k2=v2 这样键值对的形式传递) 和 application/json(参数会以 JSON 字符串格式进行传递)。
比如说服务端命令的参数是这样设计的(有 BaseParam 和 ArrayParam 两个参数,其中 ArrayParam 为数组类型):
如果我们的请求格式为 application/json,我们会这样传递参数,请求参数为放到请求体的 Body 上,且为 JSON 格式:{      "BaseParam": {                "Name": "Alice",                "Age": 2      },      "ArrayParam": [                {                        "Name": "Alice",                        "Age": 2                },                {                        "Name": "Bob",                        "Age": 3                }}如果我们的请求格式为 application/x-www-form-urlencoded,需要这样传递参数:
BaseParam=%7B%22Name%22%3A%22Alice%22%2C%22Age%22%3A2%7D&ArrayParam=%5B%7B%22Name%22%3A%22Alice%22%2C%22Age%22%3A2%7D%2C%7B%22Name%22%3A%22Bob%22%2C%22Age%22%3A3%7D%5D
URL 解码后:BaseParam={"Name":"Alice","Age":2}&ArrayParam=[{"Name":"Alice","Age":2},{"Name":"Bob","Age":3}]

这时候参数会以 key-value 的方式传输,且经过了 URL 编码。也正因为是 key-value 的形式,像 JSON 这种带有格式的文本是没办法传输的,需要将 JSON 压缩成一行的 JSON 字符串,服务端命令在处理时需要反序列化,才能够正确处理传递的数据(详见下面的 Demo 里的 BasePost_Urlencoded 服务端命令)。
PS:从上面两个示例可以看出,虽然服务端命令的参数区分了基础类型和数组类型,其实在作为接口提供给第三方调用时,其作用是等价的,意味着基础类型的参数也可以传递数组类型的 JSON 数据,只要服务端命令按照数组的规则处理即可。

如果我们的服务端命令数组参数设置了只发送变更数据,则要求我们在传递参数时处理好新增、修改和删除的数据。还是那上图的服务端命令参数举例,只是这里勾选了只发送变更数据,比如:

那么调用方在传递数据时就需要按照变更数据的固定格式组织好数据,比如(以 application/json 格式举例):{      "BaseParam": {                "Name": "Alice",                "Age": 2      },      "ArrayParam": {                "AddRows": [                        {                              "Name": "Alice",                              "Age": 2                        }                ],                "EditRows": [                        {                              "ID": 1,                              "Name": "Alice",                              "Age": 3,                        }                ],                "DeleteRows": [                        {                              "ID": 1,                        }                ]      }}其中:

[*]ArrayParam 对应服务端命令的参数名称,其值是一个对象,表示变更的数据,完整情况下会包含 AddRows、EditRows、DeleteRows 三个对象数组。
[*]AddRows 表示待添加的数据,需要包含我们添加数据的各项属性;
[*]EditRows 表示待修改的数据,除了修改的各项属性外,还需要包含修改的主键(这里为 ID),来表明我们修改修改那一条数据;
[*]DeleteRows 表示待删除的数据,属性只需要主键即可(这里为 ID),来表明我们需要删除那一条数据。
4. 返回码和返回结果
服务端命令可以额外定义返回值,这样在使用返回命令时就可以额外返回一些内容。比如这里额外定义了命令 Result,这样在返回命令里出了默认的返回码和返回信息外,还有我们刚才添加的 Result 项目:
对于返回命令的返回码,服务端命令的规则是,返回码为 0,表示服务端执行成功,对应 HTTP 200 返回码;只要不是 0 都代表服务端执行失败,对应 HTTP 的 500 返回码。
我可以将返回的数据放到返回信息和自定义的返回值中,那么这两者有什么区别呢?
区别就在于,返回信息的数据格式为 JSON 字符串(调用方使用时需要序列化拿到 JSON对象),返回值的数据为 JSON 对象,下面这张图能更好地说明问题:

示例和 Demo这里针对客户信息做了查询、分页查询、新增、修改、删除和变更接口,并且用 JQuery 的 Ajax 模拟了前端调用。以分页查询接口举例,服务端命令里使用设置变量命令,并设置查询和跳过行数来分页查询数据,如下:

在调用时,这里写了对应的模板命令,用 JS 命令去模拟第三方前端代码调用,如下:

其他的接口可以看下面的 Demo。


常见问题解决
1. 没有接口调用权限,返回 401为了方便测试,这里将所有的服务端命令设置的权限设置设置为任何人(匿名)可以访问。但在实际使用时,我们会将服务端命令设置为具体的角色或登录用户可以使用,这样第三方在调用接口时会遇到没有权限 401 的错误。这个时候需要我们做 OAuth 认证。详情操作,大家可以看这个视频教程:活字格 Restful API 鉴权 - 葡萄城学院 - 葡萄城开发者社区 (grapecity.com.cn)

2. key 不存在,The given key 'XXX' was not present in the dictionary出现这个错误的常见原因是因为在调用接口时,服务端命令的参数没有传输完整。比如服务端命令定义了 Name 和 Age 两个参数,在调用时如果其中某个参数没有显示传递,则会出现上述错误。
另外一种情况相对少见一些,当请求格式为 application/x-www-form-urlencoded 时,这时候如果我们在传输参数值对数组或一个对象时,则会出问题。比如说,服服务端命令有 Name 和 Age 两个参数,我们调用的代码如下(Ajax):
const settings = {"url": "http://localhost:10765/Forguncy/ServerCommand/Query","method": "GET","headers": {    "content-type": "application/x-www-form-urlencoded"},"data": {    "Name": {      "A":"Alice"    },    "Age": }};
$.ajax(settings).done(function (response) {console.log(response);});
这时候,虽然传递的参数包含了 Name 和 Age,但是这时候我们去看请求是实际传递的参数:Name%5BA%5D=Alice&Age%5B%5D=10&Age%5B%5D=11(URL 解码为:Name=Alice&Age[]=10&Age[]=11)
可以看到,这时候参数的 key 不是简单的 Name 和 Age,所以这里会出现 key 找不到的错误。所以对于这种属性值是对象或数组这种复杂类型的,建议用 application/json 这种格式传递请求参数。
3. 跨域第三方在用代码调用时,浏览器可能会遇到 CORS 的错误,简单来讲就是浏览器处于网站安全考虑,浏览器通过脚本发起 HTTP 请求时,如果是简单请求(比如请求参数格式为 (application/x-www-form-urlencoded),则会直接请求服务端;如果是复杂请求(比如请求参数格式为 application/json),就会先向服务端发起一次预检验请求,请求的方式为 OPTIONS,如果预检验请求通过了,则会发起真实的请求获取数据。当浏览器发现请求是跨域的,会根据服务端的跨域同源策略来决定是否拦截服务端的返回结果或发起真实的数据请求。
那么怎么样的请求才算跨域呢?
一个请求地址的组成分为四个部分:协议、IP地址或域名、端口、资源地址。当请求的协议、IP地址或域名、端口这三个部分和当前 URL 中有任意一个或多个部分不一致时,发起的请求就是跨域的。PS:关于 CORS、简单请求和复杂请求详细介绍可以参考 MDN 的文档:跨源资源共享(CORS) - HTTP | MDN (mozilla.org)

那么当活字格的服务端命令对外调用时,怎么解决跨域问题呢?
1. 使用 Nginx 代理,添加 CORS 相关的头,告诉浏览器服务端允许跨域访问我的资源,Nginx 配置文件类似如下:
upstream rest-server {      server localhost:22356;    }server {      listen       80;      listen       [::]:80;      server_namerest-server;
      # Load configuration files for the default server block.      include /etc/nginx/default.d/*.conf;
      location ^~ /Rest/ {            add_header Access-Control-Allow-Origin *;            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';            add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
            if ($request_method = 'OPTIONS') {               return 204;             }            proxy_pass http://rest-server/Rest/;            proxy_redirect default;      } }
2. 使用后端代理,就拿活字格来说,我们可以在请求站点的服务端命令里使用发送 HTTP 请求命令来调用目标接口,然后前端调用同源的服务端命令接口。PS:之所以服务端可以跨域调用,是因为同源策略是浏览器的策略,其他非浏览器环境则没有此策略。这也是一些 HTTP 图形化客户端(比如 Postman、ApiPost)可以直接跨域请求而不报错的原因。

3. 开发活字格中间件,思路和 Nginx 代理的思路类似,从服务端添加请求头来解决跨域问题。开发中间件可以参考帮助手册,自定义中间件开发(Beta) - 活字格V9帮助手册 - 葡萄城产品文档中心 (grapecity.com.cn)这里也做了一个跨域的中间件,将 DLL 添加到本地工程里,就会在在请求服务端命令时添加 CORS 相关的响应头,从而解决跨域问题。


另外这三种解决跨域的方案更加推荐第一种方案,用 Nginx 去解决,因为 Nginx 除了解决跨域问题,还有很多其他作用,详情大家可以看下面这个帖子哈:
为什么说“每个活字格服务器都需要一个nginx”?https://gcdn.grapecity.com.cn/showtopic-186662-1-1.html(出处: 葡萄城开发者社区)
页: [1]
查看完整版本: 服务端命令作为WebAPI供三方调用和跨域问题解决