Compare commits

..

No commits in common. "5925062d9de83c833c84898d7849a496f3d70505" and "a256a574e3c1a034c0f0d62a0fd4195a9067cee4" have entirely different histories.

16 changed files with 827 additions and 1917 deletions

View File

@ -74,7 +74,6 @@
"lastmod", "lastmod",
"lazyload", "lazyload",
"linuxapi", "linuxapi",
"longluo",
"loveness", "loveness",
"luoli", "luoli",
"luxon", "luxon",

View File

@ -1,5 +1,5 @@
import mdx from '@astrojs/mdx'; import mdx from '@astrojs/mdx';
import zeabur from '@zeabur/astro-adapter/serverless'; import node from '@astrojs/node';
import { uploader } from 'astro-uploader'; import { uploader } from 'astro-uploader';
import { defineConfig, envField } from 'astro/config'; import { defineConfig, envField } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links'; import rehypeExternalLinks from 'rehype-external-links';
@ -31,7 +31,6 @@ export default defineConfig({
// Artalk Comment // Artalk Comment
ARTALK_HOST: envField.string({ context: 'server', access: 'secret' }), ARTALK_HOST: envField.string({ context: 'server', access: 'secret' }),
}, },
validateSecrets: true,
}, },
}, },
integrations: [ integrations: [
@ -47,7 +46,9 @@ export default defineConfig({
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string,
}), }),
], ],
adapter: zeabur(), adapter: node({
mode: 'standalone',
}),
markdown: { markdown: {
gfm: true, gfm: true,
shikiConfig: { shikiConfig: {

View File

@ -186,7 +186,7 @@ const options: z.input<typeof Options> = {
server: 'https://comment.yufan.me', server: 'https://comment.yufan.me',
size: 10, size: 10,
avatar: { avatar: {
mirror: 'https://gravatar.webp.se/avatar', mirror: 'https://weavatar.com/avatar',
size: 120, size: 120,
}, },
}, },

2545
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,46 +41,41 @@
] ]
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^3.1.3", "@astrojs/mdx": "^3.1.2",
"@astrojs/node": "^8.3.2",
"@astrojs/rss": "^4.0.7", "@astrojs/rss": "^4.0.7",
"@zeabur/astro-adapter": "^1.0.6", "astro": "^4.11.5",
"astro": "^4.12.3", "drizzle-orm": "^0.31.3",
"drizzle-orm": "^0.32.1",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"marked": "^13.0.3", "marked": "^13.0.2",
"pg": "^8.12.0", "pg": "^8.12.0",
"qrcode-svg": "^1.1.0", "qrcode-svg": "^1.1.0",
"ultrahtml": "^1.5.3" "ultrahtml": "^1.5.3"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/check": "^0.8.3", "@astrojs/check": "^0.8.0",
"@biomejs/biome": "^1.8.3", "@biomejs/biome": "^1.8.3",
"@napi-rs/canvas": "^0.1.53", "@napi-rs/canvas": "^0.1.53",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.6",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.0.0", "@types/node": "^20.14.10",
"@types/pg": "^8.11.6", "@types/pg": "^8.11.6",
"@types/qrcode-svg": "^1.1.5", "@types/qrcode-svg": "^1.1.4",
"@types/unist": "^3.0.2", "@types/unist": "^3.0.2",
"aplayer": "^1.10.1", "aplayer": "^1.10.1",
"astro-uploader": "^1.1.3", "astro-uploader": "^1.1.3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"patch-package": "^8.0.0", "prettier": "^3.3.2",
"prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.0",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-astro-organize-imports": "^0.4.9", "prettier-plugin-astro-organize-imports": "^0.4.9",
"prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-organize-imports": "^4.0.0",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"resize-sensor": "^0.0.6", "resize-sensor": "^0.0.6",
"rimraf": "^6.0.1", "rimraf": "^5.0.8",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"typescript": "^5.5.4", "typescript": "^5.5.3",
"unist-util-select": "^5.1.0" "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
View File

@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"]
}

View File

@ -151,7 +151,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
const email = event.target.value; const email = event.target.value;
if (email !== '' && email.includes('@')) { if (email !== '' && email.includes('@')) {
// Replace the avatar after typing the email. // Replace the avatar after typing the email.
actions.avatar({ email }).then(({ data, error }) => { actions.avatar.safe({ email }).then(({ data, error }) => {
if (error) { if (error) {
return handleActionError(error); return handleActionError(error);
} }
@ -167,7 +167,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
// Loading more comments from server. // Loading more comments from server.
if (event.target === comments.querySelector('#comments-next-button')) { if (event.target === comments.querySelector('#comments-next-button')) {
const { size, offset, key } = event.target.dataset; 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) { if (error) {
return handleActionError(error); return handleActionError(error);
} }
@ -223,7 +223,7 @@ if (typeof comments !== 'undefined' && comments !== null) {
} }
request.rid = request.rid === undefined ? 0 : Number(request.rid); 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) { if (error) {
return handleActionError(error); return handleActionError(error);
@ -279,7 +279,7 @@ const likeButton = document.querySelector('button.post-like');
const increaseLikes = (count, permalink) => { const increaseLikes = (count, permalink) => {
count.textContent = Number.parseInt(count.textContent) + 1; 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) { if (error) {
return handleActionError(error); return handleActionError(error);
} }
@ -295,7 +295,7 @@ const decreaseLikes = (count, permalink) => {
return; return;
} }
count.textContent = Number.parseInt(count.textContent) - 1; 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) { if (error) {
return handleActionError(error); return handleActionError(error);
} }

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@ import options from '@/options';
import { ARTALK_HOST } from 'astro:env/server'; import { ARTALK_HOST } from 'astro:env/server';
import _ from 'lodash'; import _ from 'lodash';
import { marked } from 'marked'; import { marked } from 'marked';
import querystring from 'node:querystring'; import * as querystring from 'node:querystring';
import { ELEMENT_NODE, transform, walk } from 'ultrahtml'; import { ELEMENT_NODE, transform, walk } from 'ultrahtml';
import sanitize from 'ultrahtml/transformers/sanitize'; import sanitize from 'ultrahtml/transformers/sanitize';

View File

@ -61,8 +61,3 @@
homepage: https://blog.luoli.net homepage: https://blog.luoli.net
poster: /images/links/luoli.net.jpg poster: /images/links/luoli.net.jpg
favicon: https://blog.luoli.net/usr/themes/MUltraMoe/images/favicon.png 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

View File

@ -33,11 +33,3 @@ published: true
无论过了多久,无论白天与黑夜,博客是永恒的长亭,任你来来去去,悲欢离合,它只在那里。不悲,不喜。 无论过了多久,无论白天与黑夜,博客是永恒的长亭,任你来来去去,悲欢离合,它只在那里。不悲,不喜。
我说,我只想做个博客里面的守望者,我会陪你们一起,我们一起守望幸福,等待下一次遇见…… 我说,我只想做个博客里面的守望者,我会陪你们一起,我们一起守望幸福,等待下一次遇见……
## 博客信息
1. 博客名称:且听书吟
2. 博客简介:诗与梦想的远方
3. 博客网址https://yufan.me
4. 博客 LOGOhttps://yufan.me/logo.svg
5. 博客 Bannerhttps://github.com/syhily/yufan.me/blob/astro/docs/poster/github-poster.png

View File

@ -16,9 +16,9 @@ summary: 还未从忙碌中回神,八月就这么不知不觉地过去,周
——题记 ——题记
今年的八月,没有去年那般湿热,不知是不是因为毕业的日子早已离我远去,印象中的毕业季总是在热火朝天的季节里,热得烦躁不堪却也要坚持穿上累赘的学士服四处拍照。就和初到北京时住的地下室一样,湿漉漉的地板墙壁,湿漉漉的被单床铺,像是初面职场时内心焦急的大汗淋漓。 今年的八月,没有去年那般湿热,不知是不是因为毕业的日子早已离我远去,印象中的毕业季总是在热火朝天的季节里,热得烦躁不堪却也要坚持穿上累赘的学士服四处拍照。就和初到北京时住的地下室一样,湿漉漉的地板墙壁,湿漉漉的被单床铺,像是初面职场时内心焦急的大汗淋漓。
那时的我面对着陌生的职场从来没接触的知识和技能内心焦躁不安并自我怀疑。那样烦躁的日子里睡前唯一能做的事就是强迫自己阅读几页《Java 编程思想》,每多掌握一点知识的时候,心中的焦虑便会被抚平一些。后来习惯了在公司通宵加班,大多时候,其实都是在看书,看设计模式,看关系数据库,看 NoSQL ,看并发编程…… 那时的我,面对着陌生的职场,从来没接触的知识和技能,内心焦躁不安并自我怀疑。那样烦躁的日子里,睡前唯一能做的事,就是强迫自己阅读几页《 Java 编程思想》,每多掌握一点知识的时候,心中的焦虑便会被抚平一些。后来习惯了在公司通宵加班,大多时候,其实都是在看书,看设计模式,看关系数据库,看 NoSQL ,看并发编程……
看了那么多还是看不清前路,公司的休息室睡起来不是很舒适,每每梦里迷迷糊糊好像看见许多面孔,醒来却头沉沉什么都想不起来。却又总会莫名其妙回想起很多旧时的事儿:六岁那年那只追着我三条街的狗;中秋节的外婆牌麻糍;爷爷去世时躺的那块旧门板。 看了那么多还是看不清前路,公司的休息室睡起来不是很舒适,每每梦里迷迷糊糊好像看见许多面孔,醒来却头沉沉什么都想不起来。却又总会莫名其妙回想起很多旧时的事儿:六岁那年那只追着我三条街的狗;中秋节的外婆牌麻糍;爷爷去世时躺的那块旧门板。
@ -34,7 +34,7 @@ summary: 还未从忙碌中回神,八月就这么不知不觉地过去,周
领导对自己的不信任,同事对自己的抱怨,让自己一度绝望想要放弃。那时的我一个人负责全部项目的技术重构设计,项目组只有一个新入职的产品,一个才毕业的测试。全部的需求要我带着去过,测试也要我手把手教,代码只有我一个人写。算不清喝了多少咖啡,回家的次数也屈指可数。 领导对自己的不信任,同事对自己的抱怨,让自己一度绝望想要放弃。那时的我一个人负责全部项目的技术重构设计,项目组只有一个新入职的产品,一个才毕业的测试。全部的需求要我带着去过,测试也要我手把手教,代码只有我一个人写。算不清喝了多少咖啡,回家的次数也屈指可数。
记得有一次凌晨 4 点Review 完明天要上线的代码,我却不知道该做什么好,就这么静静地坐到天亮,动都不想动。窗外,是一成不变的灯红酒绿,屋内,却是一片漆黑。我看着屏幕上的那个自己,陌生、迷茫…… 记得有一次凌晨 4 点, Review 完明天要上线的代码,我却不知道该做什么好,就这么静静地坐到天亮,动都不想动。窗外,是一成不变的灯红酒绿,屋内,却是一片漆黑。我看着屏幕上的那个自己,陌生、迷茫……
![この瞬間だけは汚れた街もマシになる](/images/2023/12/2023120213392110.jpg) ![この瞬間だけは汚れた街もマシになる](/images/2023/12/2023120213392110.jpg)

View File

@ -40,7 +40,7 @@ Rest API无论它的名字多么高大上它本质还是一个 HTTP 请求
`API`设计时常会考虑版本这个概念无论是在URI还是在请求参数里面至少有一个地方得指明含版本为什么呢 `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 **所以一开始设计API时就要定义好版本。其次版本化可以骗钱。** 我们可以满怀恶意地猜测1.0为了抢用户免费。2.0老牛逼了你用的爽了想更爽付费。233333333
@ -66,63 +66,63 @@ PS实际的架构远比这个复杂截图选自《大型网站系统与Jav
**1、演变** **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、特点** **2、特点**
1. C/S 结构 1. C/S结构
2. 无状态API 平台无需存储业务状态,只做认证和转发) 2. 无状态API平台无需存储业务状态只做认证和转发
3. 有缓存API 会对指定 URI 的请求转发做缓存,保证并发性,业务系统也对同样的请求针对性缓存。 3. 有缓存API会对指定URI的请求转发做缓存保证并发性业务系统也对同样的请求针对性缓存。
4. 结构分层,每层间无法直接访问。 4. 结构分层,每层间无法直接访问。
API 平台的背后,就是庞大的各个业务子系统。每个 API就相当于一个业务子系统。API 平台要做的事,就非常清晰和简单。就是业务子系统注册发布 API对外部请求校验计费模拟请求内部业务子系统对子系统结果包装序列化为 `JSON` 返回。 API平台的背后就是庞大的各个业务子系统。每个API就相当于一个业务子系统。API平台要做的事就非常清晰和简单。就是业务子系统注册发布API对外部请求校验计费模拟请求内部业务子系统对子系统结果包装序列化为`JSON`返回。
## 2.3 交互流程 ## 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、是不是实际客户端的请求**
1. 设计专门的私有请求头:定义独有的 Request headers标明有此请求头的请求合法。 1. 设计专门的私有请求头定义独有的Request headers标明有此请求头的请求合法。
2. 请求包含请求时间:定义时间,防止中间拦截篡改,只对指定超时范围内(如 10 秒)的请求予以响应。 2. 请求包含请求时间定义时间防止中间拦截篡改只对指定超时范围内如10秒的请求予以响应。
3. 请求URI是否合法 URI 是否在 API 平台注册?防止伪造 URI 攻击 3. 请求URI是否合法此URI是否在API平台注册防止伪造URI攻击
4. 请求是否包含不允许的参数定义:请求此版本的这个 URI 是否允许某些字段,防止注入工具。 4. 请求是否包含不允许的参数定义请求此版本的这个URI是否允许某些字段防止注入工具。
5. 部分竞争资源是否包含调用时版本Etag部分竞争资源使用 If-Match 头提供。如用户资金账户查询 API可以返回此时的账户版本修改扣款时附加版本号类似乐观锁设计 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. 所有请求参数按第一个字符升序排序(先字母后数字),如第一个相同,则看第二个,依次顺延。 1. 所有请求参数按第一个字符升序排序(先字母后数字),如第一个相同,则看第二个,依次顺延。
2. 按请求参数名及参数值相互连接组成一个字符串。param1=value1&param2=value2...(其中包含 App Key 参数) 2. 按请求参数名及参数值相互连接组成一个字符串。param1=value1&param2=value2...其中包含App Key参数
3. 将应用密钥分别添加到以上请求参数串的头部和尾部: secret + 请求参数字符串 + secret。 3. 将应用密钥分别添加到以上请求参数串的头部和尾部:secret + 请求参数字符串 + secret。
4. 对该字符串进行 SHA1 运算,得到一个二进制数组。 4. 对该字符串进行 SHA1 运算,得到一个二进制数组。
5. 将该二进制数组转换为十六进制的字符串,该字符串为此次请求的签名。 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 系统需求 ### 3.1.1 系统需求
1. 支持 rest API 接口动态发布及运营,包括但不限于: 1. 支持rest类API接口动态发布及运营包括但不限于
- 安全认证 - 安全认证
- 会话管理 - 会话管理
- 流量统计及限流 - 流量统计及限流
- 计费收费 - 计费收费
- 熔断 - 熔断
2. 支持现有子系统 RPC 协议的 API 动态发布及运营,外部请求透传。 2. 支持现有子系统RPC协议的API动态发布及运营外部请求透传。
3. 支持 json、xml 响应报文,可以请求时选取所需报文格式。 3. 支持json、xml响应报文可以请求时选取所需报文格式。
4. 支持动态直接将后端 SOA 服务暴露为 API。 4. 支持动态直接将后端SOA服务暴露为API。
5. 支持动态将普通 Web 接口暴露为 API。 5. 支持动态将普通Web接口暴露为API。
6. 支持动态将 MQ 服务暴露为 API。 6. 支持动态将MQ服务暴露为API。
7. 支持多个服务组合编排后暴露为 API。 7. 支持多个服务组合编排后暴露为API。
### 3.1.2 业务需求 ### 3.1.2 业务需求
**1、API 管理** **1、API管理**
所有 API 可后台查询管理包括动态发布、参数映射配置、后端服务接口配置、API 禁用、启用,多版本、分组、分级别等。 所有API可后台查询管理包括动态发布、参数映射配置、后端服务接口配置、API禁用、启用多版本、分组、分级别等。
**2、应用管理** **2、应用管理**
后台管理开放平台接入的应用(第三方应用),包括查询、禁用、启用、审核。 后台管理开放平台接入的应用(第三方应用),包括查询、禁用、启用、审核。
**3、API 鉴权&授权** **3、API鉴权&授权**
1. 应用申请审核通过后生成公钥,开放平台需提供支持分布式系统的密钥管理 1. 应用申请审核通过后生成公钥,开放平台需提供支持分布式系统的密钥管理
2. 服务可设置为两个安全等级需授权访问和无需授权访问后者即任意客户端都可以发起调用默认所有API都需授权访问。 2. 服务可设置为两个安全等级需授权访问和无需授权访问后者即任意客户端都可以发起调用默认所有API都需授权访问。
3. 非正常状态(禁用、停用、黑名单等)的应用直接抛异常不允许访问——**熔断机制** 3. 非正常状态(禁用、停用、黑名单等)的应用直接抛异常不允许访问——**熔断机制**
- 调用次数、调用频率、并发数可运行时控制,避免某请求量过大影响其他应用的调用。 - 调用次数、调用频率、并发数可运行时控制,避免某请求量过大影响其他应用的调用。
- 可对某个应用某个 API 设置强制熔断,所有请求无视阀值直接抛出异常。 - 可对某个应用某个API设置强制熔断所有请求无视阀值直接抛出异常。
4. 易用性 4. 易用性
- 与 SOA 集成SOA 服务一键发布到API平台。 - 与SOA集成SOA服务一键发布到API平台。
- 支持后台动态发布API而不是新上一个API就需上线一次。 - 支持后台动态发布API而不是新上一个API就需上线一次。
**4、计费统计** **4、计费统计**
@ -270,9 +270,9 @@ API 的所有服务请求域名是相同的区别在于Request Path等。请
## 3.3 常见框架 ## 3.3 常见框架
1. Kong: [https://github.com/Mashape/kong](https://github.com/Mashape/kong) 1. Kong[https://github.com/Mashape/kong](https://github.com/Mashape/kong)
2. Zuul: [https://github.com/Netflix/zuul](https://github.com/Netflix/zuul) 2. Zuul[https://github.com/Netflix/zuul](https://github.com/Netflix/zuul)
3. ROP: [https://github.com/itstamen/rop](https://github.com/itstamen/rop) 3. ROP[https://github.com/itstamen/rop](https://github.com/itstamen/rop)
4. Resty: [https://github.com/Dreampie/Resty](https://github.com/Dreampie/Resty) 4. Resty: [https://github.com/Dreampie/Resty](https://github.com/Dreampie/Resty)
# 四、优劣 # 四、优劣

View File

@ -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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= 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= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=