全栈工程师开发手册 (作者:栾鹏)
架构系列文章


kong安装部署以及kong-dashboard参考:https://blog.csdn.net/luanpeng825485697/article/details/85287291

插件概述

插件之于kong,就像Spring中的aop功能。

在请求到达kong之后,转发给后端应用之前,你可以应用kong自带的插件对请求进行处理,合法认证,限流控制,黑白名单校验,日志采集等等。同时,你也可以按照kong的教程文档,定制开发属于自己的插件。

kong的插件分为开源版和社区版,社区版还有更多的定制功能,但是社区版是要收费的。

目前,KONG开源版本一共开放28个插件,如下:

acl、aws-lambda、basic-auth、bot-detection、correlation-id、cors、datadog、file-log、galileo、hmac-auth、http-log、ip-restriction、jwt、key-auth、ldap-auth、loggly、oauth2、rate-limiting、request-size-limiting、request-termination、request-transformer、response-ratelimiting、response-transformer、runscope、statsd、syslog、tcp-log、udp-log

以上插件,主要分五大类,Authentication认证,Security安全,Traffic Control流量控制,Analytics & Monitoring分析&监控,Logging日志,其他还有请求报文处理类。

熔断request-termination插件

该插件用来定义指定请求或服务不进行上层服务,而直接返回指定的内容.用来为指定的请求或指定的服务进行熔断.

这样再访问指定的服务就会返回403错误,消息为So long and thanks for all the fish!

可以参考:https://docs.konghq.com/hub/kong-inc/request-termination/

限流rate-limiting插件

安装rate-limiting插件

可以不用配置redis,不过要设置限制方法,我设置了每秒不超过2次。

当请求超过2次,会出现

{
"message": "API rate limit exceeded"
}

说明:

根据年、月、日、时、分、秒设置限流规则,多个限制同时生效。

比如:每天不能超过10次调用,每分不能超过3次。

当一分钟内,访问超过3次,第四次就会报错。

当一天内,访问次数超过10次,第十一次就会报错。

IP黑白名单ip-restriction限制插件

IP限制插件,是一个非常简单的插件,可以设置黑名单IP,白名单IP这个很简单。

规则:

IP黑白名单,支持单个,多个,范围分段IP(满足CIDR notation规则)。多个IP之间用逗号,分隔。

CIDR notation规范如下:

10.10.10.0/24 表示10.10.10.*的都不能访问。

关于CIDR notation的规则,不在本文讨论范围内,请自行查阅https://zh.wikipedia.org/wiki/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1

1.设置黑名单IP

在这里,我将我自己的IP设置成黑名单.

似乎我安装的kong-dashboard黑白名单写反了。

基本认证Basic Authentication插件

在Consumers 页面,添加Basic Auth

输入用户名和密码,我这里设置为luanpeng luanpeng。计算认证头。获取luanpeng:luanpeng字符串的base64编码。

可以直接在linux下输出

$ echo "luanpeng:luanpeng"|base64bHVhbnBlbmc6bHVhbnBlbmcK

在插件页面,设置Basic Auth 绑定目标service,这样请求目标service就需要在http头中添加

Authorization          Basic bHVhbnBlbmc6bHVhbnBlbmcK

设置Basic Auth表单域参数介绍:

表单域名称 默认值 描述
name(必填) 插件名称,在这里该插件名称为:basic-auth
config.hide_credentials(选填) false boolean类型,告诉插件,是否对上游API服务隐藏认证信息。如果配置true,插件将会把认证信息清除,然后再把请求转发给上游api服务。
config.anonymous(选填) String类型,用来作为匿名用户,如果认证失败。如果空,当请求失败时,返回一段4xx的错误认证信息。

key认证key-Auth插件

该插件很简单,利用提前预设好的关键字名称,如下面设置的keynote = apices,然后为consumer设置一个key-auth 密钥,假如key-auth=test@keyauth。

在请求api的时候,将apikey=test@keyauth,作为一个参数附加到请求url后,或者放置到headers中。

在插件页面添加key-auth插件

配置consumer key-auth

key-auth两种方式可通过校验

curl http://xxx.xx.xx.xx:xxx/xxx -H 'apikey: luanpeng'
http://xxx.xxx.xxx.xxx:xxx/xxx?apikey=luanpeng

如果选中key_in_body, 则必须在传递body的参数中加入{“apikey”:“xxxx”}来实现认证.

HMAC认证

先启动HMAC插件,设置绑定的service和rout,以启动hmac验证。然后在Consumers页面中Hmac credentials of Consumer设置中添加一个username和secret。

准备生成http的header中的签名。请求是使用该签名。这里附上python的调用包

# kong_hmac.pyimport base64
import hashlib
import hmac
import re
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktimedef create_date_header():now = datetime.now()stamp = mktime(now.timetuple())return format_date_time(stamp)def get_headers_string(signature_headers):headers = ""for key in signature_headers:if headers != "":headers += " "headers += keyreturn headersdef get_signature_string(signature_headers):sig_string = ""for key, value in signature_headers.items():if sig_string != "":sig_string += "\n"if key.lower() == "request-line":sig_string += valueelse:sig_string += key.lower() + ": " + valuereturn sig_stringdef md5_hash_base64(string_to_hash):m = hashlib.md5()m.update(string_to_hash)return base64.b64encode(m.digest())# sha1签名算法,字符串的签名,并进行base64编码
def sha1_hash_base64(string_to_hash, secret):h = hmac.new(secret, (string_to_hash).encode("utf-8"), hashlib.sha1)return base64.b64encode(h.digest())def generate_request_headers(username, secret, url, data=None, content_type=None):# Set the authorization header templateauth_header_template = ('hmac username="{}",algorithm="{}",headers="{}",signature="{}"')# Set the signature hash algorithmalgorithm = "hmac-sha1"# Set the date headerdate_header = create_date_header()  # 产生GMT格式时间# print('GMT时间:',date_header)# Set headers for the signature hashsignature_headers = {"date": date_header}# Determine request methodif data is None or content_type is None:request_method = "GET"else:request_method = "POST"# MD5 digest of the contentbase64md5 = md5_hash_base64(data)# Set the content-length headercontent_length = str(len(data))# Add headers for the signature hashsignature_headers["content-type"] = content_typesignature_headers["content-md5"] = base64md5signature_headers["content-length"] = content_length# Strip the hostname from the URLtarget_url = re.sub(r"^https?://[^/]+/", "/", url)# print('请求路径:',target_url)# Build the request-line headerrequest_line = request_method + " " + target_url + " HTTP/1.1"# print('request_line:',request_line)# Add to headers for the signature hashsignature_headers["request-line"] = request_line# Get the list of headersheaders = get_headers_string(signature_headers)  # 转化为list# print('签名的属性名称:',headers)# Build the signature stringsignature_string = get_signature_string(signature_headers)  # 获取要签名的字符串# print('要签名的字符串:',signature_string)# Hash the signature string using the specified algorithmsignature_hash = sha1_hash_base64(signature_string, secret)   # 签名# print('签名后字符串:',signature_hash)# Format the authorization headerauth_header = auth_header_template.format(username, algorithm, headers, signature_hash.decode('utf-8'))if request_method == "GET":request_headers = {"Authorization": auth_header, "Date": date_header}else:request_headers = {"Authorization": auth_header,"Date": date_header,"Content-Type": content_type,"Content-MD5": base64md5,"Content-Length": content_length,}return request_headers

调用该包,demo如下

# get示例
username = 'vesionbook'
secret = 'vesionbook'.encode('utf-8')url = 'http://192.168.11.127:30309/arctern'
request_headers = generate_request_headers(username, secret, url)
print('请求头:',request_headers)
r = requests.get(url, headers=request_headers)
print('Response code: %d\n' % r.status_code)
print(r.text)

jwt认证插件

先为Consumer消费者建立jwt凭证

在线JWT编码和解码https://jwt.io/

图中HEADER 部分声明了验证方式为 JWT,加密算法为 HS256

PAYLOAD 部分原本有 5 个参数

{"iss": "kirito",                   # Consumer的jwt中设置的key"iat": 1546853545,         #  签发时间戳"exp": 1546853585,       #  过期时间戳"nbf": 1546853585        # 生效日期"aud": "cnkirito.moe","sub": "250577914@qq.com",
}

这里面的前五个字段都是由 JWT 的标准(RFC7519)所定义的。

  • iss: 该 JWT 的签发者,(验证的时候判断是否是签发者)
  • sub: 该 JWT 所面向的用户,(验证的时候判断是否是所有者)
  • aud: 接收该 JWT 的一方,标识令牌的目标受众。(验证的时候判断我是否是其中一员)
  • exp(expires): 什么时候过期,这里是一个 Unix 时间戳,精确到s, ,它必须大于jwt的签发时间
  • iat(issued at): 在什么时候签发的,精确到s的时间戳, claims_to_verify配置参数不允许设置iat
  • nbf:定义jwt的生效时间
  • jti:jwt唯一身份标识,主要用来作为一次性token来使用,从而回避重放攻击

iss 这一参数在 Kong 的 Jwt 插件中对应的是curl http://127.0.0.1:8001/consumers/kirito/jwt 获取的用户信息中的 key 值。

而其他值都可以选填.

在页面上VERIFY SIGNATURE中填入自己的secret, 也就是在kong的dashboard中消费者创建jwt证书时的secret.

我们使用 jwt 官网(jwt.io)提供的 Debugger 功能快速生成我们的 Jwt, 由三个圆点分隔的长串便是用户身份的标识了.

打开kong的jwt插件

在key_claim_name中定义存储key的字段名称. 我们是使用的iss字段.
cookie_names表示如果使用cookie传递证书, 则cookie中的名称.
claims_to_verify表示验证证书中哪些字段, 我这里验证证书的发布时间和过期时间.

然后在header中携带证书信息就可以了.

Jwt 也可以作为 QueryString 参数携带在 get 请求中

curl http://localhost:8000/hello/hi?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2Y252WVNGelRJR3lNeHpLU2duTlUwdXZ4aXhkWVdCOSJ9.3iL4sXgZyvRx2XtIe2X73yplfmSSu1WPGcvyhwq7TVE

如果在插件配置中设置了cookie_names为luanpeng-cookie

则在发送中

--cookie luanpeng-cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJYSnFRMXpSQVhUWk52dlNHZ1Nsb1FyejczOFBqT0hFZyIsImV4cCI6MTUyNTc5MzQyNSwibmJmIjoxNTI1Nzc1NDI1LCJpYXQiOjE1MjU3NzU0MjV9.0Cv8rJkXTMNKAvPTOBV1w0UYVhRx3XRb6xJofxloRuA

不同配置下,可能返回证书未生效, 证书已过期, 或者返回正常结果

通常用户需要自己写一个服务去帮助 Consumer 生成自己的 Jwt,自然不能总是依赖于 Jwt 官方的 Debugger,当然也没必要重复造轮子(尽管这并不难),可以考虑使用开源实现,在jwt官网上Libraries for Token Signing/Verification部分 根据自己使用的语言,选择对应的包,来实现证书生成器. 最好可以直接集成到api网关中.

这里用python实现了一个简单的签名生成器


import sys
import osdir_common = os.path.split(os.path.realpath(__file__))[0] + '/../'
sys.path.append(dir_common)   # 将根目录添加到系统目录,才能正常引用common文件夹from aiohttp import web
import asyncioimport logging
import uvloop
import time,datetimeimport jwtasyncio.set_event_loop_policy(uvloop.EventLoopPolicy())routes = web.RouteTableDef()# 返回客户的json信息
def write_response(status,message,result):response={'status':status,      # 状态,0为成功,1为失败'message':message,    # 错误或成功描述。字符串'result':result     # 成功的返回结果,字典格式}return response@routes.get('/')
async def hello(request):return web.Response(text="Hello, world")# 签名
@routes.post('/sign')
async def sign(request):   # 异步监听,只要一有握手就开始触发try:data = await request.json()    # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数except Exception as e:logging.error("image file too large or cannot convert to json")return web.json_response(write_response(1,"image file too large or cannot convert to json",{}))logging.info('license sign request start, data is %s,%s' % (data, datetime.datetime.now()))if "username" not in data or 'password' not in data:logging.error("username or password not in data")return web.json_response(write_response(2, "username or password not in data", {}))payload = {"iss": data['username'],"iat": int(time.time()),"exp": int(time.time()) + 60*60,   # 有效期一个小时}encoded_jwt = jwt.encode(payload, data['password'], algorithm='HS256')encoded_jwt = encoded_jwt.decode('utf-8')logging.info('license sign request finish %s, %s' % (datetime.datetime.now(),encoded_jwt))header = {"Access-Control-Allow-Origin": "*", 'Access-Control-Allow-Methods': 'GET,POST'}result = write_response(0, "success",encoded_jwt)# 同时放在cookie中header['cookie']='--cookie aicloud-cookie='+encoded_jwtreturn web.json_response(result,headers=header)# 校验
@routes.post('/check')
async def check(request):   # 异步监听,只要一有握手就开始触发try:data = await request.json()    # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数except Exception as e:logging.error("image file too large or cannot convert to json")return web.json_response(write_response(1,"image file too large or cannot convert to json",{}))logging.info('license check request start, data is %s,%s' % (data,datetime.datetime.now()))if "username" not in data or 'password' not in data or 'sign' not in data:logging.error("username or password or sign not in data")return web.json_response(write_response(2, "username or password or sign not in data", {}))encoded_jwt = data['sign'].encode('utf-8')payload = jwt.decode(encoded_jwt, data['password'], algorithms=['HS256'])if payload['iss']!=data['username']:logging.error("iss in sign != username")return web.json_response(write_response(3, "username error", {}))elif payload['iat']>time.time():logging.error("sign not effective")return web.json_response(write_response(4, "sign not effective", {}))elif payload['exp']<time.time():logging.error("sign lose effectiveness")return web.json_response(write_response(5, "sign lose effectiveness", {}))logging.info('license check request finish %s, %s' % (datetime.datetime.now(),encoded_jwt))header = {"Access-Control-Allow-Origin": "*", 'Access-Control-Allow-Methods': 'GET,POST'}result = write_response(0, "success", {})return web.json_response(result,headers=header)if __name__ == '__main__':logger = logging.getLogger()logger.setLevel(logging.INFO)   # 最低输出等级app = web.Application(client_max_size=int(1024))    # 创建app,设置最大接收图片大小为2Mapp.add_routes(routes)     # 添加路由映射web.run_app(app,host='0.0.0.0',port=8080)   # 启动applogging.info('server close:%s'% datetime.datetime.now())

ACL授权插件

该插件相当于授权插件,授权必须建立在认证的基础上,认证和授权是相互独立的。

ACL策略插件

策略分组规则:

1).为用户分配授权策略组

2).为api添加授权策略分组插件。

3).只有拥有api授权策略分组的用户才可以调用该api。

4).授权策略分组,必须建立在认证机制上,该策略生效的前提,api至少要开启任意一个auth认证插件。

如果为同一service启用的授权和认证,则光认证是不行的。必须还要授权。将用户设置为授权组。

上面的设置以后,只有属于白名单组的用户才能访问该service,但是究竟哪些用户属于这些组呢,这需要去Consumers页面设置。

如果想限制某些用户访问某些路径,可以在路由处添加几个路由匹配,对不同的路由匹配设置授权

链路跟踪Zipkin插件

Zipkin 是一款开源的分布式实时数据追踪系统。其主要功能是聚集来自各个异构系统的实时监控数据,用来追踪微服务架构下的系统延时问题。应用系统需要向 Zipkin 报告数据。Kong的Zipkin插件作为zipkin-client就是组装好Zipkin需要的数据包,往Zipkin-server发送数据。

所以首先要部署一个zipkin服务端:参考https://blog.csdn.net/luanpeng825485697/article/details/85772954

部署结束后打开http://xx.xx.xx.xx:9411/api/v2/spans?servicename=test看是否能正常打开

启动zipkin插件:

在插件页面启动插件配置参数

config.http_endpoint :Zipkin接收数据的地址,配置http://xx.xx.xx.xx:9411/api/v2/spans
config.sample_ratio : 采样的频率。设为0,则不采样;设为1,则完整采样。默认为0.001也就是0.1%的采样率, 再调试阶段建议设置采样率为1.

zipkin插件会每次请求,打上如下标签,推送到zipkin服务端

  • span.kind (sent to Zipkin as “kind”)
  • http.method
  • http.status_code
  • http.url
  • peer.ipv4
  • peer.ipv6
  • peer.port
  • peer.hostname
  • peer.service

可以参考:https://github.com/Kong/kong-plugin-zipkin

启用后,此插件会以与zipkin兼容的方式跟踪请求。

代码围绕一个opentracing核心构建,使用opentracing-lua库来收集每个Kong阶段的请求的时间数据。该插件使用opentracing-lua兼容的提取器,注入器和记者来实现Zipkin的协议。

提取器和注射器

opentracing“提取器”从传入的请求中收集信息。如果传入请求中不存在跟踪ID,则基于sample_ratio配置值概率地生成一个跟踪ID 。

opentracing“injector”将跟踪信息添加到传出请求中。目前,仅对kong代理的请求调用注入器; 它不尚未用于请求到数据库,或通过其他插件(如HTTP日志插件)。

日志

目前在Kong的 free plugins中,比较常用的有这么三个:Syslog、File-Log以及Http-Log,下面对这三种插件逐一分析一下。

Syslog

顾名思义,这个插件是把Kong中记录的日志给打印到系统日志中,开启插件之后只需要指定需要使用的API,无需做多余的配置,即可在/var/log/message中发现对应的日志信息,d 但是系统日志鱼龙混杂,如果需要用到ELK等日志分析工具时,需要做一次数据清洗工作。

File-Log

与Syslog一样,File-log的配置也很方便,只需要配置日志路劲就行,开启插件之后,会在对应的对应产生一个logFile。Syslog中提到需要做一些日志清洗工作,但是换成了File-log乍一看好像解决了之前的痛点,实则不然,官方建议这个插件不适合在生产环境中使用,会带来一些性能上的开销,影响正常业务。

Http-Log

http-log是我比较推荐的,它的原理是设置一个log-server地址,然后Kong会把日志通过post请求发送到设置的log-server,然后通过log-server把日志给沉淀下来,相比之前两种插件,这一种只要启一个log-server就好了,出于性能考虑,我用Rust实现了一个log-server,有兴趣可以参考看一下。

prometheus可视化

kong自带的prometheus插件,metrics比较少, 可以网上查一下丰富版的prometheus插件.

比如:https://github.com/yciabaud/kong-plugin-prometheus

现在用这个插件替换kong自带的插件.

最方便的安装方式,一般linux机器上都会自带 luarocks(lua包管理程序),这样一来我们只要把 Plugins 所在的文件夹给移动到服务器的任意目录,然后在该目录下,执行luarocks make 这样一来插件便会自动安装到系统中,不过需要注意的是,此时插件还需要进行手动开启,首先进入/etc/kong/目录,然后cp kong.conf.default kong.conf, 这里注意一定要复制一份单独的kong.conf文件,不能直接对kong.conf.default进行修改,这样是不生效的,然后取消plugin = bundled前面的注释,在这一行后面增加你的插件名,这里注意插件名是不包含前缀 kong-plugin的,重启Kong即可在可视化界面里发现

plugins = bundled,prometheus

在使用新插件之前,需要更新一下数据库:

bash ./resty.sh kong/bin/kong  migrations up -c kong.conf

爬虫控制插件bot-detection

备注:

config.whitelist :白名单,逗号分隔的正则表达式数组。正则表达式是根据 User-Agent 头部匹配的。
config.blacklist :黑名单,逗号分隔的正则表达式数组。正则表达式是根据 User-Agent 头部匹配的。

这个字段是用来匹配客户端身份的, 比如是浏览器还是模拟器, 还是python代码.

这个插件已经包含了一个基本的规则列表,这些规则将在每个请求上进行检查。你可以在GitHub上找到这个列表 https://github.com/Kong/kong/blob/master/kong/plugins/bot-detection/rules.lua.

kong插件应用(熔断 限流,黑白名单,认证(basic,key,jwt,hmac,),授权,加密,zipkin链路跟踪,日志, prometheus可视化, 爬虫控制插件)相关推荐

  1. 36、熔断-限流-降级

    目录 一.服务雪崩 二.熔断-限流-降级概述 三.熔断限流技术选型 四.sentinel限流 1 - 基于QPS限流 2 - Throttling策略 3 - sentinel预热/冷启动 五.sen ...

  2. 「微服务系列 13」熔断限流隔离降级

    我们知道微服务分布式依赖关系错综复杂,比方说前端的请求转化为后端调用的服务请求,一个前端请求会转为成很多个后端调用的服务请求,那么这个时候后台的服务出现不稳定或者延迟,如果没有好的限流熔断措施,可能会 ...

  3. Spring Cloud Alibaba配置实例nacos+sentinel+dubbo实行服务注册、配置中心、熔断限流

    通过Spring Cloud Alibaba相关组件nacos+sentinel+dubbo实行服务注册.配置中心.熔断限流等功能 1.本机安装nacos和sentinel-dashboard服务端 ...

  4. 【Kong】网关-rate-limiting限流

    目录 路由添加限流插件 测试结果 路由添加限流插件 ROUTES-->[路由NAME/ID]-->Plugins-->ADD Plugin 每秒1个请求 测试结果 postman的R ...

  5. 系统降级熔断限流和排队

    这类问题的主要原因在于系统压力太大.负载太高,导致无法快速处理业务请求,由此引发更多的后续问题.最常见的情况就是,数据库慢查询将数据库的服务器资源耗尽,导致读写超时,业务读写数据库时要么无法连接数据库 ...

  6. SpringCloud 第八期 Sentinel 熔断限流

    sentinel可以作为监控平台使用,下载jar包运行 官网说明文档,有中文 Wiki - Gitee.com sentinel下载地址 https://github.com/alibaba/Sent ...

  7. android熔断,限流熔断技术

    Hystrix 作为Spring Cloud官方默认的熔断组件,停止开发,Hystrix官方推荐使用Resilience4j: Netflix的 Hystrix 是一个帮助解决分布式系统交互时超时处理 ...

  8. php熔断,限流、熔断、降级

    mservice 可以方便的对资源进行限流.包括Http.Thrift. ## 一.安装 ``` composer require clevephp/la-limiting ``` ## 二.配置 在 ...

  9. Kong 网关 | Rate Limiting 限流

    一.简介 限流允许开发人员规定在几秒.几分钟.几小时.几天.几个月或几年的时间内可以发出多少HTTP请求. 二.实例 1.配置服务 2.配置路由 3.为路由配置限流 访问: http://www.ja ...

最新文章

  1. 史上最完整的Maven 教程
  2. Java使用JAX-WS来写webservice时 Unable to create JAXBContext
  3. python环境变量配置_21 python环境的配置
  4. 64位ubuntu 12.04下如何解决中文乱码的问题
  5. 体验VSTS源代码管理之一
  6. javascript中基本包装、算数运算符、递增递减运算符、赋值运算符、比较运算符、逻辑运算符、运算符优先级
  7. java 人脸识别jar包_java版天网人脸识别系统,获取视频流人脸识识别推送服务器展示...
  8. Laravel 日志权限问题
  9. shell菜鸟学习之echo命令
  10. hyper-v redhat使用新网卡
  11. 双11当天,淘宝搜索如何让CPU使用率降45%,GMV提1% | 论文
  12. word利用宏批量调整图片大小
  13. 《全数据时代的炼金师》读书笔记(一)
  14. 电脑更改开机密码和用户名
  15. Docker以及DockerHub的使用
  16. VS2005 C++ 项目怎样添加“依赖”、“库目录”和“包含目录”
  17. rsa 长密钥实现及测试
  18. 国际期货投机有什么风险?目前正大怎么样?
  19. 无为,无我,无欲,居下,清虚,自然
  20. 基于Springboot的电商系统开题报告

热门文章

  1. python爬虫怎么赚钱-利用Python爬虫轻松挣外快的几个方法(值得收藏)
  2. python模式识别
  3. mysql代码的核心类_mysql源码---核心类 (1)线程类
  4. LIRe提供的图像检索算法的速度
  5. git gui怎么拉取项目代码_这些Git命令都不会,还是不要去面试了
  6. java 对象赋值给scala_将Scala变量转换为Java对象… varargs
  7. delphi基于html页面和xml实现表单填写,如何从xml文件读取数据,并以delphi语言显示在文本框中...
  8. android 高德地图动画,点动态样式-基本功能-示例中心-Loca API 示例 | 高德地图API...
  9. linux 配置远程日志服务器配置,配置远程日志服务器—实现日志的集中管理
  10. golangsha1解码_如何阅读Golang的源码?