Compare commits
No commits in common. "5925062d9de83c833c84898d7849a496f3d70505" and "a256a574e3c1a034c0f0d62a0fd4195a9067cee4" have entirely different histories.
5925062d9d
...
a256a574e3
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -74,7 +74,6 @@
|
||||
"lastmod",
|
||||
"lazyload",
|
||||
"linuxapi",
|
||||
"longluo",
|
||||
"loveness",
|
||||
"luoli",
|
||||
"luxon",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import mdx from '@astrojs/mdx';
|
||||
import zeabur from '@zeabur/astro-adapter/serverless';
|
||||
import node from '@astrojs/node';
|
||||
import { uploader } from 'astro-uploader';
|
||||
import { defineConfig, envField } from 'astro/config';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
@ -31,7 +31,6 @@ export default defineConfig({
|
||||
// Artalk Comment
|
||||
ARTALK_HOST: envField.string({ context: 'server', access: 'secret' }),
|
||||
},
|
||||
validateSecrets: true,
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
@ -47,7 +46,9 @@ export default defineConfig({
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string,
|
||||
}),
|
||||
],
|
||||
adapter: zeabur(),
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
markdown: {
|
||||
gfm: true,
|
||||
shikiConfig: {
|
||||
|
@ -186,7 +186,7 @@ const options: z.input<typeof Options> = {
|
||||
server: 'https://comment.yufan.me',
|
||||
size: 10,
|
||||
avatar: {
|
||||
mirror: 'https://gravatar.webp.se/avatar',
|
||||
mirror: 'https://weavatar.com/avatar',
|
||||
size: 120,
|
||||
},
|
||||
},
|
||||
|
2547
package-lock.json
generated
2547
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -41,46 +41,41 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^3.1.3",
|
||||
"@astrojs/mdx": "^3.1.2",
|
||||
"@astrojs/node": "^8.3.2",
|
||||
"@astrojs/rss": "^4.0.7",
|
||||
"@zeabur/astro-adapter": "^1.0.6",
|
||||
"astro": "^4.12.3",
|
||||
"drizzle-orm": "^0.32.1",
|
||||
"astro": "^4.11.5",
|
||||
"drizzle-orm": "^0.31.3",
|
||||
"fuse.js": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.4",
|
||||
"marked": "^13.0.3",
|
||||
"marked": "^13.0.2",
|
||||
"pg": "^8.12.0",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"ultrahtml": "^1.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.8.3",
|
||||
"@astrojs/check": "^0.8.0",
|
||||
"@biomejs/biome": "^1.8.3",
|
||||
"@napi-rs/canvas": "^0.1.53",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/pg": "^8.11.6",
|
||||
"@types/qrcode-svg": "^1.1.5",
|
||||
"@types/qrcode-svg": "^1.1.4",
|
||||
"@types/unist": "^3.0.2",
|
||||
"aplayer": "^1.10.1",
|
||||
"astro-uploader": "^1.1.3",
|
||||
"bootstrap": "^5.3.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-astro": "^0.14.0",
|
||||
"prettier-plugin-astro-organize-imports": "^0.4.9",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"resize-sensor": "^0.0.6",
|
||||
"rimraf": "^6.0.1",
|
||||
"rimraf": "^5.0.8",
|
||||
"sharp": "^0.33.4",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript": "^5.5.3",
|
||||
"unist-util-select": "^5.1.0"
|
||||
},
|
||||
"packageManager": "npm@10.8.2",
|
||||
"engines": {
|
||||
"node": "22.5.1"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 47 KiB |
4
renovate.json
Normal file
4
renovate.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"]
|
||||
}
|
@ -151,7 +151,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
|
||||
const email = event.target.value;
|
||||
if (email !== '' && email.includes('@')) {
|
||||
// Replace the avatar after typing the email.
|
||||
actions.avatar({ email }).then(({ data, error }) => {
|
||||
actions.avatar.safe({ email }).then(({ data, error }) => {
|
||||
if (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
@ -167,7 +167,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
|
||||
// Loading more comments from server.
|
||||
if (event.target === comments.querySelector('#comments-next-button')) {
|
||||
const { size, offset, key } = event.target.dataset;
|
||||
const { data, error } = await actions.comments({ offset: Number(offset), page_key: key });
|
||||
const { data, error } = await actions.comments.safe({ offset: Number(offset), page_key: key });
|
||||
if (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
@ -223,7 +223,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
|
||||
}
|
||||
request.rid = request.rid === undefined ? 0 : Number(request.rid);
|
||||
|
||||
const { data, error } = await actions.comment(request);
|
||||
const { data, error } = await actions.comment.safe(request);
|
||||
|
||||
if (error) {
|
||||
return handleActionError(error);
|
||||
@ -279,7 +279,7 @@ const likeButton = document.querySelector('button.post-like');
|
||||
|
||||
const increaseLikes = (count, permalink) => {
|
||||
count.textContent = Number.parseInt(count.textContent) + 1;
|
||||
actions.like({ action: 'increase', key: permalink }).then(({ data, error }) => {
|
||||
actions.like.safe({ action: 'increase', key: permalink }).then(({ data, error }) => {
|
||||
if (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
@ -295,7 +295,7 @@ const decreaseLikes = (count, permalink) => {
|
||||
return;
|
||||
}
|
||||
count.textContent = Number.parseInt(count.textContent) - 1;
|
||||
actions.like({ action: 'decrease', key: permalink, token }).then(({ data, error }) => {
|
||||
actions.like.safe({ action: 'decrease', key: permalink, token }).then(({ data, error }) => {
|
||||
if (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@ import options from '@/options';
|
||||
import { ARTALK_HOST } from 'astro:env/server';
|
||||
import _ from 'lodash';
|
||||
import { marked } from 'marked';
|
||||
import querystring from 'node:querystring';
|
||||
import * as querystring from 'node:querystring';
|
||||
import { ELEMENT_NODE, transform, walk } from 'ultrahtml';
|
||||
import sanitize from 'ultrahtml/transformers/sanitize';
|
||||
|
||||
|
@ -61,8 +61,3 @@
|
||||
homepage: https://blog.luoli.net
|
||||
poster: /images/links/luoli.net.jpg
|
||||
favicon: https://blog.luoli.net/usr/themes/MUltraMoe/images/favicon.png
|
||||
- website: LongLuo's Life Notes
|
||||
description: 每一天都是奇迹
|
||||
homepage: https://www.longluo.me
|
||||
poster: /images/links/longluo.me.jpg
|
||||
favicon: https://www.longluo.me/assets/logo/favicon-32x32.png
|
||||
|
@ -33,11 +33,3 @@ published: true
|
||||
无论过了多久,无论白天与黑夜,博客是永恒的长亭,任你来来去去,悲欢离合,它只在那里。不悲,不喜。
|
||||
|
||||
我说,我只想做个博客里面的守望者,我会陪你们一起,我们一起守望幸福,等待下一次遇见……
|
||||
|
||||
## 博客信息
|
||||
|
||||
1. 博客名称:且听书吟
|
||||
2. 博客简介:诗与梦想的远方
|
||||
3. 博客网址:https://yufan.me
|
||||
4. 博客 LOGO:https://yufan.me/logo.svg
|
||||
5. 博客 Banner:https://github.com/syhily/yufan.me/blob/astro/docs/poster/github-poster.png
|
||||
|
@ -16,9 +16,9 @@ summary: 还未从忙碌中回神,八月就这么不知不觉地过去,周
|
||||
|
||||
——题记
|
||||
|
||||
今年的八月,没有去年那般湿热,不知是不是因为毕业的日子早已离我远去,印象中的毕业季总是在热火朝天的季节里,热得烦躁不堪却也要坚持穿上累赘的学士服四处拍照。就和初到北京时住的地下室一样,湿漉漉的地板墙壁,湿漉漉的被单床铺,像是初面职场时内心焦急的大汗淋漓。
|
||||
今年的八月,没有去年那般湿热,不知是不是因为毕业的日子早已离我远去,印象中 里 的毕业季总是在热火朝天的季节里,热得烦躁不堪却也要坚持穿上累赘的学士服四处拍照。就和初到北京时住的地下室一样,湿漉漉的地板墙壁,湿漉漉的被单床铺,像是初面职场时内心焦急的大汗淋漓。
|
||||
|
||||
那时的我,面对着陌生的职场,从来没接触的知识和技能,内心焦躁不安并自我怀疑。那样烦躁的日子里,睡前唯一能做的事,就是强迫自己阅读几页《Java 编程思想》,每多掌握一点知识的时候,心中的焦虑便会被抚平一些。后来习惯了在公司通宵加班,大多时候,其实都是在看书,看设计模式,看关系数据库,看 NoSQL ,看并发编程……
|
||||
那时的我,面对着陌生的职场,从来没接触的知识和技能,内心焦躁不安并自我怀疑。那样烦躁的日子里,睡前唯一能做的事,就是强迫自己阅读几页《 Java 编程思想》,每多掌握一点知识的时候,心中的焦虑便会被抚平一些。后来习惯了在公司通宵加班,大多时候,其实都是在看书,看设计模式,看关系数据库,看 NoSQL ,看并发编程……
|
||||
|
||||
看了那么多还是看不清前路,公司的休息室睡起来不是很舒适,每每梦里迷迷糊糊好像看见许多面孔,醒来却头沉沉什么都想不起来。却又总会莫名其妙回想起很多旧时的事儿:六岁那年那只追着我三条街的狗;中秋节的外婆牌麻糍;爷爷去世时躺的那块旧门板。
|
||||
|
||||
@ -34,7 +34,7 @@ summary: 还未从忙碌中回神,八月就这么不知不觉地过去,周
|
||||
|
||||
领导对自己的不信任,同事对自己的抱怨,让自己一度绝望想要放弃。那时的我一个人负责全部项目的技术重构设计,项目组只有一个新入职的产品,一个才毕业的测试。全部的需求要我带着去过,测试也要我手把手教,代码只有我一个人写。算不清喝了多少咖啡,回家的次数也屈指可数。
|
||||
|
||||
记得有一次凌晨 4 点,Review 完明天要上线的代码,我却不知道该做什么好,就这么静静地坐到天亮,动都不想动。窗外,是一成不变的灯红酒绿,屋内,却是一片漆黑。我看着屏幕上的那个自己,陌生、迷茫……
|
||||
记得有一次凌晨 4 点, Review 完明天要上线的代码,我却不知道该做什么好,就这么静静地坐到天亮,动都不想动。窗外,是一成不变的灯红酒绿,屋内,却是一片漆黑。我看着屏幕上的那个自己,陌生、迷茫……
|
||||
|
||||
![この瞬間だけは汚れた街もマシになる](/images/2023/12/2023120213392110.jpg)
|
||||
|
||||
|
@ -40,7 +40,7 @@ Rest API,无论它的名字多么高大上,它本质还是一个 HTTP 请求
|
||||
|
||||
`API`设计时常会考虑版本这个概念,无论是在URI还是在请求参数里面,至少有一个地方得指明含版本,为什么呢?
|
||||
|
||||
这很简单,就和系统迭代一样,API也是快速迭代开发的。也许初期,你的老大脑袋一热,我们要上 API,于是你们就加班加点做,设定了一版请求协议。当时你们想,写完就能赚钱,必然带来一堆问题,比如说,代码难以维护,功能单一。后面这个`API`的第二版,你们要基于它去做一些新的变更,比如请求参数多了,返回内容多了,必然就有了第二版。但是又不能影响现有的业务。这个 `API` 基本的 `URI` 没变,但是会同时存在两个版本,老用户继续请求旧版,没问题,你无需动旧版?有需求的新用户,你要请求新版才能提供服务。这样通过版本来区分了不同的服务,便于以后的升级和维护。_(就像 BAE 那货可以毫无压力地废弃掉 2.0 的API)_
|
||||
这很简单,就和系统迭代一样,API也是快速迭代开发的。也许初期,你的老大脑袋一热,我们要上API,于是你们就加班加点做,设定了一版请求协议。当时你们想,写完就能赚钱,必然带来一堆问题,比如说,代码难以维护,功能单一。后面这个`API`的第二版,你们要基于它去做一些新的变更,比如请求参数多了,返回内容多了,必然就有了第二版。但是又不能影响现有的业务。这个`API`基本的`URI`没变,但是会同时存在两个版本,老用户继续请求旧版,没问题,你无需动旧版?有需求的新用户,你要请求新版才能提供服务。这样通过版本来区分了不同的服务,便于以后的升级和维护。_(就像BAE那货可以毫无压力地废弃掉2.0的API)_
|
||||
|
||||
**所以一开始设计API时,就要定义好版本。其次,版本化可以骗钱。** 我们可以满怀恶意地猜测,1.0为了抢用户,免费。2.0老牛逼了,你用的爽了,想更爽,付费。233333333
|
||||
|
||||
@ -66,63 +66,63 @@ PS,实际的架构远比这个复杂,截图选自《大型网站系统与Jav
|
||||
|
||||
**1、演变**
|
||||
|
||||
初期的 API 平台往往是上图左侧那种,某个庞大的业务系统希望暴露一套 API,于是大家就在这个系统上做,直接设计一套协议。但是,这样子带来的缺点十分明显,第一,它与业务联系太重,理想的API平台是通用的,不是只给你设计一个。第二,它不好扩展,每次变更都得和业务系统一同上线,糟糕的情况下代码还会影响原有正常的业务。第三,性能问题,理论上会降低原有应用的性能。
|
||||
初期的API平台往往是上图左侧那种,某个庞大的业务系统希望暴露一套API,于是大家就在这个系统上做,直接设计一套协议。但是,这样子带来的缺点十分明显,第一,它与业务联系太重,理想的API平台是通用的,不是只给你设计一个。第二,它不好扩展,每次变更都得和业务系统一同上线,糟糕的情况下代码还会影响原有正常的业务。第三,性能问题,理论上会降低原有应用的性能。
|
||||
|
||||
这种情况下,如果应用部署了多台机器,多个节点,我们就可以独立出来。也就是右边所示的 API Gateway,它做的事情本质上就是反向代理,将外部的请求校验完合法性之后反代至内部实际想要对外暴露服务的服务集群上。
|
||||
这种情况下,如果应用部署了多台机器,多个节点,我们就可以独立出来。也就是右边所示的API Gateway,它做的事情本质上就是反向代理,将外部的请求校验完合法性之后反代至内部实际想要对外暴露服务的服务集群上。
|
||||
|
||||
所以,这种场景下,API Gateway 也就如名称所说,就是一个入口。实际的 Rest API 的东西还是建立在各个业务子系统上,只是只需要提供最简单的服务,无需考虑授权等东西。用户管理,API注册发布,调用统计等,均由 API Gateway 实现处理。对于想要快速上线的开发人员而言,实在是一个不错的福音。
|
||||
所以,这种场景下,API Gateway也就如名称所说,就是一个入口。实际的Rest API的东西还是建立在各个业务子系统上,只是只需要提供最简单的服务,无需考虑授权等东西。用户管理,API注册发布,调用统计等,均由API Gateway实现处理。对于想要快速上线的开发人员而言,实在是一个不错的福音。
|
||||
|
||||
然而,当系统应用拆分到了 SOA 化之后,API 的架构由有了新的变革,我们有了注册中心的概念。因为 SOA 化,所以每个业务子系统其实都有了对外的统一接口,有了 ESB(注册中心)。实际的内部系统间请求也有了较好的路由、熔断等策略。
|
||||
然而,当系统应用拆分到了SOA化之后,API的架构由有了新的变革,我们有了注册中心的概念。因为SOA化,所以每个业务子系统其实都有了对外的统一接口,有了ESB(注册中心)。实际的内部系统间请求也有了较好的路由、熔断等策略。
|
||||
|
||||
在这种大背景下,API 平台对外暴露的 Rest API 无需底层的业务专门开发了。直接使用现有的内部接口,选择性暴露即可。问题点就在于,如何根据定义的Rest API请求,实际模拟内部的RPC协议请求。
|
||||
在这种大背景下,API平台对外暴露的Rest API无需底层的业务专门开发了。直接使用现有的内部接口,选择性暴露即可。问题点就在于,如何根据定义的Rest API请求,实际模拟内部的RPC协议请求。
|
||||
|
||||
某种程度上,这时候的API平台,已经不仅仅是 HTTP Rest 请求了。我们完全可以实现相同 RPC 协议的透传,比如你就是一个 Hessian 接口想对外暴露,我只需包上一层认证,直接注册于 API Gateway,外部 Hessian 请求直接透传至内部子系统。
|
||||
某种程度上,这时候的API平台,已经不仅仅是HTTP Rest请求了。我们完全可以实现相同RPC协议的透传,比如你就是一个Hessian接口想对外暴露,我只需包上一层认证,直接注册于API Gateway,外部Hessian请求直接透传至内部子系统。
|
||||
|
||||
在这个基础上的 Rest API 平台,才是灵活的,可扩展的,易于维护的。然而有得必有失,Mock 请求必然会有性能上的损耗,但是这个架构的公司,已经不在乎钱了,上 10 台虚机,不够加呗。
|
||||
在这个基础上的 Rest API 平台,才是灵活的,可扩展的,易于维护的。然而有得必有失,Mock请求必然会有性能上的损耗,但是这个架构的公司,已经不在乎钱了,上10台虚机,不够加呗。
|
||||
|
||||
**2、特点**
|
||||
|
||||
1. C/S 结构
|
||||
2. 无状态(API 平台无需存储业务状态,只做认证和转发)
|
||||
3. 有缓存,API 会对指定 URI 的请求转发做缓存,保证并发性,业务系统也对同样的请求针对性缓存。
|
||||
1. C/S结构
|
||||
2. 无状态(API平台无需存储业务状态,只做认证和转发)
|
||||
3. 有缓存,API会对指定URI的请求转发做缓存,保证并发性,业务系统也对同样的请求针对性缓存。
|
||||
4. 结构分层,每层间无法直接访问。
|
||||
|
||||
API 平台的背后,就是庞大的各个业务子系统。每个 API,就相当于一个业务子系统。API 平台要做的事,就非常清晰和简单。就是业务子系统注册发布 API,对外部请求校验计费,模拟请求内部业务子系统,对子系统结果包装序列化为 `JSON` 返回。
|
||||
API平台的背后,就是庞大的各个业务子系统。每个API,就相当于一个业务子系统。API平台要做的事,就非常清晰和简单。就是业务子系统注册发布API,对外部请求校验计费,模拟请求内部业务子系统,对子系统结果包装序列化为`JSON`返回。
|
||||
|
||||
## 2.3 交互流程
|
||||
|
||||
上面是一个简单的交互,简单显示了外部系统和内部系统通过 Rest API 的交互过程:开发者(企业)注册,申请 APP_KEY,开通 API。按照开发接入,请求签名。转发至后端调用返回结果,API 平台计费(预付费或者后收费),统计调用情况。
|
||||
上面是一个简单的交互,简单显示了外部系统和内部系统通过Rest API的交互过程:开发者(企业)注册,申请APP_KEY,开通API。按照开发接入,请求签名。转发至后端调用返回结果,API平台计费(预付费或者后收费),统计调用情况。
|
||||
|
||||
外部通信本质上还是 `HTTP`,那么必然存在了授权问题,生产的 API 平台是直接暴露于公网的,如果认证授权策略出现纰漏,影响是可怕的。
|
||||
外部通信本质上还是`HTTP`,那么必然存在了授权问题,生产的API平台是直接暴露于公网的,如果认证授权策略出现纰漏,影响是可怕的。
|
||||
|
||||
比如,这个 API 是群发短信,你要是没有好的授权体系,允许人随意推送。某个人想搞你,调用发布反共信息,你整个公司都会跨。`HTTP` 协议本质上没有这一块的内容,所以我们必然要在这上面考虑安全策略的内容。
|
||||
比如,这个API是群发短信,你要是没有好的授权体系,允许人随意推送。某个人想搞你,调用发布反共信息,你整个公司都会跨。`HTTP`协议本质上没有这一块的内容,所以我们必然要在这上面考虑安全策略的内容。
|
||||
|
||||
### 2.3.1 如何保证 Rest API 的安全性
|
||||
### 2.3.1 如何保证Rest API的安全性
|
||||
|
||||
如果单纯考虑加解密,或者签名方式来保证请求合法,其实是远远不够的。事实上,一个安全的 API 平台往往需要多方面一起考虑,保证请求安全合法。
|
||||
如果单纯考虑加解密,或者签名方式来保证请求合法,其实是远远不够的。事实上,一个安全的API平台往往需要多方面一起考虑,保证请求安全合法。
|
||||
|
||||
**1、是不是实际客户端的请求?**
|
||||
|
||||
1. 设计专门的私有请求头:定义独有的 Request headers,标明有此请求头的请求合法。
|
||||
2. 请求包含请求时间:定义时间,防止中间拦截篡改,只对指定超时范围内(如 10 秒)的请求予以响应。
|
||||
3. 请求URI是否合法:此 URI 是否在 API 平台注册?防止伪造 URI 攻击
|
||||
4. 请求是否包含不允许的参数定义:请求此版本的这个 URI 是否允许某些字段,防止注入工具。
|
||||
5. 部分竞争资源是否包含调用时版本(Etag):部分竞争资源,使用 If-Match 头提供。如用户资金账户查询 API,可以返回此时的账户版本,修改扣款时附加版本号(类似乐观锁设计)。
|
||||
1. 设计专门的私有请求头:定义独有的Request headers,标明有此请求头的请求合法。
|
||||
2. 请求包含请求时间:定义时间,防止中间拦截篡改,只对指定超时范围内(如10秒)的请求予以响应。
|
||||
3. 请求URI是否合法:此URI是否在API平台注册?防止伪造URI攻击
|
||||
4. 请求是否包含不允许的参数定义:请求此版本的这个URI是否允许某些字段,防止注入工具。
|
||||
5. 部分竞争资源是否包含调用时版本(Etag):部分竞争资源,使用If-Match头提供。如用户资金账户查询API,可以返回此时的账户版本,修改扣款时附加版本号(类似乐观锁设计)。
|
||||
|
||||
**2、API 平台是否允许你调用(访问控制)?**
|
||||
**2、API平台是否允许你调用(访问控制)?**
|
||||
|
||||
访问控制,主要是授权调用部分。API 都对外暴露,但是某些公共 API 可以直接请求,某些,需要授权请求。本质的目的,都是为了验证发起用户合法,且对用户能标识统计计费。
|
||||
访问控制,主要是授权调用部分。API都对外暴露,但是某些公共API可以直接请求,某些,需要授权请求。本质的目的,都是为了验证发起用户合法,且对用户能标识统计计费。
|
||||
|
||||
以 HMac Auth 为例,我们简单设计一个签名算法。开发者注册时获取 App Key、App Secret,然后申请部分 API 的访问权限,发起请求时:
|
||||
以HMac Auth为例,我们简单设计一个签名算法。开发者注册时获取App Key、App Secret,然后申请部分API的访问权限,发起请求时:
|
||||
|
||||
1. 所有请求参数按第一个字符升序排序(先字母后数字),如第一个相同,则看第二个,依次顺延。
|
||||
2. 按请求参数名及参数值相互连接组成一个字符串。param1=value1¶m2=value2...(其中包含 App Key 参数)
|
||||
3. 将应用密钥分别添加到以上请求参数串的头部和尾部: secret + 请求参数字符串 + secret。
|
||||
2. 按请求参数名及参数值相互连接组成一个字符串。param1=value1¶m2=value2...(其中包含App Key参数)
|
||||
3. 将应用密钥分别添加到以上请求参数串的头部和尾部:secret + 请求参数字符串 + secret。
|
||||
4. 对该字符串进行 SHA1 运算,得到一个二进制数组。
|
||||
5. 将该二进制数组转换为十六进制的字符串,该字符串为此次请求的签名。
|
||||
6. 该签名值使用 sign 系统级参数一起和其它请求参数一起发送给 API 平台。
|
||||
6. 该签名值使用sign系统级参数一起和其它请求参数一起发送给API平台。
|
||||
|
||||
服务端先验证`是不是实际客户端的请求`,然后按照 App Key 查找对应 App Secret,执行签名算法,比较签名是否一致。签名一致后查看此 App Key 对应的用户是否有访问此API的权限,有则放行。
|
||||
服务端先验证`是不是实际客户端的请求`,然后按照App Key查找对应App Secret,执行签名算法,比较签名是否一致。签名一致后查看此App Key对应的用户是否有访问此API的权限,有则放行。
|
||||
|
||||
执行成功后包装返回指定格式的结果,进行统计计费。
|
||||
|
||||
@ -132,38 +132,38 @@ API 平台的背后,就是庞大的各个业务子系统。每个 API,就相
|
||||
|
||||
### 3.1.1 系统需求
|
||||
|
||||
1. 支持 rest 类 API 接口动态发布及运营,包括但不限于:
|
||||
1. 支持rest类API接口动态发布及运营,包括但不限于:
|
||||
- 安全认证
|
||||
- 会话管理
|
||||
- 流量统计及限流
|
||||
- 计费收费
|
||||
- 熔断
|
||||
2. 支持现有子系统 RPC 协议的 API 动态发布及运营,外部请求透传。
|
||||
3. 支持 json、xml 响应报文,可以请求时选取所需报文格式。
|
||||
4. 支持动态直接将后端 SOA 服务暴露为 API。
|
||||
5. 支持动态将普通 Web 接口暴露为 API。
|
||||
6. 支持动态将 MQ 服务暴露为 API。
|
||||
7. 支持多个服务组合编排后暴露为 API。
|
||||
2. 支持现有子系统RPC协议的API动态发布及运营,外部请求透传。
|
||||
3. 支持json、xml响应报文,可以请求时选取所需报文格式。
|
||||
4. 支持动态直接将后端SOA服务暴露为API。
|
||||
5. 支持动态将普通Web接口暴露为API。
|
||||
6. 支持动态将MQ服务暴露为API。
|
||||
7. 支持多个服务组合编排后暴露为API。
|
||||
|
||||
### 3.1.2 业务需求
|
||||
|
||||
**1、API 管理**
|
||||
**1、API管理**
|
||||
|
||||
所有 API 可后台查询管理,包括动态发布、参数映射配置、后端服务接口配置、API 禁用、启用,多版本、分组、分级别等。
|
||||
所有API可后台查询管理,包括动态发布、参数映射配置、后端服务接口配置、API禁用、启用,多版本、分组、分级别等。
|
||||
|
||||
**2、应用管理**
|
||||
|
||||
后台管理开放平台接入的应用(第三方应用),包括查询、禁用、启用、审核。
|
||||
|
||||
**3、API 鉴权&授权**
|
||||
**3、API鉴权&授权**
|
||||
|
||||
1. 应用申请审核通过后生成公钥,开放平台需提供支持分布式系统的密钥管理
|
||||
2. 服务可设置为两个安全等级:需授权访问和无需授权访问(后者即任意客户端都可以发起调用),默认所有API都需授权访问。
|
||||
3. 非正常状态(禁用、停用、黑名单等)的应用直接抛异常不允许访问——**熔断机制**
|
||||
- 调用次数、调用频率、并发数可运行时控制,避免某请求量过大影响其他应用的调用。
|
||||
- 可对某个应用某个 API 设置强制熔断,所有请求无视阀值直接抛出异常。
|
||||
- 可对某个应用某个API设置强制熔断,所有请求无视阀值直接抛出异常。
|
||||
4. 易用性
|
||||
- 与 SOA 集成,SOA 服务一键发布到API平台。
|
||||
- 与SOA集成,SOA服务一键发布到API平台。
|
||||
- 支持后台动态发布API,而不是新上一个API就需上线一次。
|
||||
|
||||
**4、计费统计**
|
||||
@ -270,9 +270,9 @@ API 的所有服务请求域名是相同的,区别在于Request Path等。请
|
||||
|
||||
## 3.3 常见框架
|
||||
|
||||
1. Kong: [https://github.com/Mashape/kong](https://github.com/Mashape/kong)
|
||||
2. Zuul: [https://github.com/Netflix/zuul](https://github.com/Netflix/zuul)
|
||||
3. ROP: [https://github.com/itstamen/rop](https://github.com/itstamen/rop)
|
||||
1. Kong:[https://github.com/Mashape/kong](https://github.com/Mashape/kong)
|
||||
2. Zuul:[https://github.com/Netflix/zuul](https://github.com/Netflix/zuul)
|
||||
3. ROP:[https://github.com/itstamen/rop](https://github.com/itstamen/rop)
|
||||
4. Resty: [https://github.com/Dreampie/Resty](https://github.com/Dreampie/Resty)
|
||||
|
||||
# 四、优劣
|
||||
|
@ -19,6 +19,8 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
Loading…
Reference in New Issue
Block a user