作者选择了 自由和开源基金作为 写给捐款计划的一部分接受捐款。
介绍
Unique identifiers(UIDs),或 identifiers,可以是一个字符串值或整数,API开发人员通常会用它们来在API中寻找独特资源。API消费者然后会使用这些标识符来从资源集合中获取单个资源。没有一个独特的标识符,将资源分开并按需要命名几乎是不可能的。标识符可以参考数据库的结构元素,例如表的名称、表中的字段(列)或限制,并且可以进一步指定数据库中的单一项目。
在 API Design Patterns中,John J. Geewax 确定了一个好的标识符的七个基本特征。
- 易于使用:标识符应避免预留字符(
/
),因为这些字符在URL中具有特定含义。 - 独特:标识符应该能够在API中引用单个资源。
- 快速生成:标识符的生成过程应在缩放时以可预测的方式进行一致性。
- 不可预测:当标识符不可预测时,它为漏洞管理提供了安全优势。
- 可读性:标识符应该是人读的,这是通过避免数字
1
、下面的L``、上面的
I`或管道字符的实现,因为这些字符可能导致混淆,如果有人需要手动检查标识符。
<$>[注]
** 注:** 更改标识符可能会造成意外的混淆. 如果您有一个标识符指定为 酒店(id=1234, name="Hyatt")
,然后更改为 酒店(id=5678, name="Hyatt")
,那么以前的标识符可能可重复使用。 如果以前的标识符可用,并且新酒店被创建为 酒店(id=1234, name="Grand Villa")
,这个新酒店会重复使用原始标识符(1234
)。
在本教程中,您将使用 Node.JS 生成满足这些特征的独特自定义资源标识符和相关的检查总和。 A checksum是使用数字对象的 hash 函数获得的文件或数字数据的数字指纹的哈希。
前提条件
在您开始本教程之前,您将需要以下内容:
- Node.js 已安装在您的计算机上,您可以通过以下 How To Install Node.js来设置。本教程已与 Node.JS 版本 16.16.0.
- 熟悉 Node.js. 在 How To Code in Node.js 系列中了解更多。
- 熟悉 APIs. 有关使用 APIs 的全面教程,您可以查看 [How to Use Web APIs in Python(3)https://andsky.com/tech/tutorials/how-to-use-web-apis-in-python-3])。 虽然为 Python 撰写,但这篇文章将帮助您了解与 APIs 工作的核心概念。
- 一个文本编辑器支持 JavaScript 语法突出,如 Atom, [Visual Code Studio]([L
步骤 1 – 生成编码ID
在此步骤中,您将写一个函数,将从随机字节生成一个独特的字符串的标识符。您的标识符将使用 base32 编码进行编码,但它将不会在教程中稍后附加到检查总数。
首先,为此项目创建一个新文件夹,然后移动到该文件夹:
1mkdir checksum
2cd checksum
该项目文件夹将被称为这个教程的checksum
。
在项目文件夹中创建并打开package.json
文件(使用您最喜欢的编辑器):
1nano package.json
然后添加以下代码行:
1[label package.json]
2{
3 "name": "checksum",
4 "version": "1.0.0",
5 "main": "index.js",
6 "type": "module"
7}
在此文件中,您将项目名称定义为checksum
,并考虑代码版本为1.0.0
。您将主JavaScript文件定义为index.js
。当您在package.json
文件中有type
:module
,您的源代码应该使用 import syntax。
保存并关闭文件。
您将使用几个 Node.js 模块来生成 ID: crypto
和 base32-encode
,其相应的解码器 base32-decode
)。 该 crypto
模块包装了 Node.JS,但您将需要安装 base32-encode
和 base32-decode
以便在本教程中稍后使用。 编码是将字母序列(字母,数字,点数和某些符号)放入一个专门的格式,以便进行高效的传输或存储。 解码是相反的过程:将编码的格式转换回原始字符序列。 Base32 编码使用
在终端会话中,使用以下命令在项目文件夹中安装这些模块包:
1npm i base32-encode base32-decode
您将收到一个输出,表明这些模块已被添加:
1[secondary_label Output]
2added 3 packages, and audited 5 packages in 2s
3
4found 0 vulnerabilities
如果安装过程中遇到问题,请参阅 如何使用 npm 和 package.json 的 Node.js 模块以获得支持。
在项目文件夹中,创建一个名为index.js
的新文件:
1nano index.js
将以下JavaScript代码列入index.js
文件:
1[label index.js]
2import crypto from 'crypto';
3import base32Encode from 'base32-encode';
4import base32Decode from 'base32-decode';
5
6function generate_Id(byte_size) {
7 const bytes = crypto.randomBytes(byte_size);
8 return base32Encode(bytes, 'Crockford');
9}
10
11console.log('ID for byte size = 1:',generate_Id(1), '\n');
12console.log('ID for byte size = 12:',generate_Id(12), '\n');
13console.log('ID for byte size = 123:',generate_Id(123), '\n');
命令导入
加载所需的模块. 要从数字中生成字节,您可以定义一个generate_Id
函数来取代字节的大小,然后使用crypto
模块的randomBytes
函数创建这种大小的随机字节。
用于教学目的,会生成几个ID,然后登录到控制台. 在接下来的步骤中,将使用base32-decode
模块来解码资源ID。
保存您的 index.js 文件,然后使用此命令在终端会话中运行代码:
1[environment second]
2node index.js
您将收到类似于此的输出响应:
1[environment second]
2[secondary_label Output]
3ID for byte size = 1: Y8
4
5ID for byte size = 12: JTGSEMQH2YZFD3H35HJ0
6
7ID for byte size = 123: QW2E2KJKM8QZ7174DDB1Q3JMEKV7328EE8T79V1KG0TEAE67DEGG1XS4AR57FPCYTS24J0ZRR3E6TKM28AM8FYZ2AZTZ55C9VVQTABE0R7QRH7QBY7V3GBYBNN5D9JK0QMD9NXSWZN95S0772DHN43Q003G0QNTPA2J3AFA3P7Q167C1VNR92Z85PCDXCMEY0M7WA
您的 ID 值可能因生成字节的随机性而有所不同. 生成的 ID 可能更短或更长,取决于您选择的字节大小。
在index.js
中,使用JavaScript评论功能评论控制台输出(在行前添加//
双片):
1[label index.js]
2...
3//console.log('ID for byte size = 1:',generate_Id(1), '\n');
4//console.log('ID for byte size = 12:',generate_Id(12), '\n');
5//console.log('ID for byte size = 123:',generate_Id(123), '\n');
这些行展示了编码将如何根据相关字节输出不同的标识符,因为这些行不会在以下部分中使用,您可以评论它们,如本代码块所示,或者完全删除它们。
在此步骤中,您通过编码随机字节创建了一个编码的 ID。
步骤 2 – 生成资源标识符
现在,您将创建具有支票总数字符的ID。生成支票总数字符是一个两步的过程。 为指导目的,创建组合函数的每个函数将在以下子节中单独构建。 首先,您将编写一个运行 modulo operation的函数。 然后,您将编写另一个函数,将结果绘制为支票总数字符,这就是您如何生成资源ID的支票总数。
运行一个模块操作
在本节中,您将将数字 ID 对应的字节转换为 0-36 之间的数字(包括限制,这意味着从 0 到 36 之间的任何数字,包括 0 到 36)。 数字 ID 对应的字节通过 modulo 操作转换为整数。
要执行此步骤,请将以下代码行添加到 index.js 文件的底部:
1[label index.js]
2...
3
4function calculate_checksum(bytes) {
5 const intValue = BigInt(`0x${bytes.toString('hex')}`);
6 return Number(intValue % BigInt(37));
7}
函数 calculate_checksum
与文件中早些时候定义的字节一起工作. 此函数将字节转换为六进制值,这些值进一步转换为 BigInteger BigInt
值. BigInt
数据类型代表比在 Javascript' 中原始数据类型
number' 所代表的数字更大的数字。 例如,虽然整数 37' 相对较小,但它被转换为
BigInt' 用于模块操作。
要实现这种转换,您首先使用BigInt
转换方法设置intValue
变量,使用toString
方法将bytes
设置为hex
。然后,您使用Number
构建器返回一个数字值,在其中您使用%
符号运行模块操作,以使用37
的样本值找到intValue
和BigInt
之间的剩余部分。
如果「intValue」值为「123」(取决于「byte」),则模块操作将是「123 % 37」。
此函数将输入的字节绘制到 modulo 结果,接下来,您将写一个函数将 modulo 结果绘制到一个 checksum 字符。
获取支票金额特征
在上一节中获取modulo结果后,您可以将其绘制为检查总数字符。
添加以下代码行到前代码下面的 index.js 文件:
1[label index.js]
2...
3
4function get_checksum_character(checksumValue) {
5 const alphabet = '0123456789ABCDEFG' +
6 'HJKMNPQRSTVWXYZ*~$=U';
7 return alphabet[Math.abs(checksumValue)]; //
8}
对于get_checksum_character
函数,您将checksumValue
称为参数。在该函数中,您将名为alphabet
的字符串常数定义为自定义的字符串。 根据checksumValue
的值设置,此函数将返回一个值,该值将定义的字符串从alphabet
常数与checksumValue
的绝对值对齐。
接下来,您将编写一个函数,该函数使用这些部分中所写的两个函数来生成一个ID,从字节的编码结合到检查总数字符。
添加以下代码行到 index.js 文件中:
1[label index.js]
2...
3
4function generate_Id_with_checksum(bytes_size) {
5 const bytes = crypto.randomBytes(bytes_size);
6 const checksum = calculate_checksum(bytes);
7 const checksumChar = get_checksum_character(checksum);
8 console.log("checksum character: ", checksumChar);
9 const encoded = base32Encode(bytes, 'Crockford');
10 return encoded + checksumChar;
11}
12
13const Hotel_resource_id =generate_Id_with_checksum(132)
14console.log("Hotel resource id: ",Hotel_resource_id)
此代码部分将您之前的两个函数calculate_checksum
和get_checksum_character
(用于生成计数字符)相结合,并将编码函数组成一个名为generate_Id_with_checksum
的新函数,该函数将创建具有计数字符的 ID。
保存文件,然后在单独的终端会话中运行代码:
1[environment second]
2node index.js
您将获得类似于此的输出:
1[environment second]
2[secondary_label Output]
3
4checksum character: B
5Hotel resource id: 9V99B9P55K7M4DN5XYP4VTJYJGENZKJ0F9Q6EEEZ07X49G0V14AXJS3RYXBT3J1WJZXWGM76C6H7G895TJT27AW77BHBX2D16QNQ2ZNBY9MQHWG9NJ1WWVTNRCKRBX6HC3M7BB3JG0V413VJ767JN6FT0GFS5VQJ9X7KSP1KM29B02NAGXN3FP30WA8Y63N1XJAMGDPEE1RNHRTWH6P0B
同样的支票总数字符出现在 ID 的末尾,表示支票总数匹配。
此方案图提供了该复合函数如何工作的结构性表示:
此流程图展示了如何通过编码和模块化过程将产品ID,即由资源计数器手动创建的标识符转化为唯一的资源ID。
您根据字节大小创建了一个 ID,该 ID 将包含检查总数字符,在下一节中,您将执行一个标识符
函数以通过base32 解码
验证 ID 的完整性。
检查标识符的完整性
为了确保完整性,您将使用名为verify_Id
的新函数将支票总数字符(标识符的最后一个字符)与生成的支票总数进行比较。
将这些行添加到您的 index.js 文件中:
1[label index.js]
2...
3function verify_Id(identifier) {
4 const value = identifier.substring( 0, identifier.length-1);
5 const checksum_char = identifier[identifier.length-1];
6 const buffer = Buffer.from( base32Decode(value, 'Crockford'));
7 const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
8 console.log(calculated_checksum_char);
9 const flag =calculated_checksum_char== checksum_char;
10 return (flag);
11 }
12console.log('\n');
13console.log("computing checksum")
14const flag = verify_Id(Hotel_resource_id);
15if (flag) console.log("Checksums matched.");
16else console.log("Checksums did not match.");
verify_Id
函数通过检查支票总和来检查ID的完整性,其余的标识符符被解码为 buffer,然后在这个缓冲器上运行calculate_checksum
和get_checksum_character
,以提取比较的支票总和符号(使用calculated_checksum_char== checksum_char
)。
此方案图显示了复合函数的运作方式:
在此图表中, slicing 指的是将 ID 值(‘值’)与支票总数字符(‘checksum’)分开。 在您之前的代码块中,函数 identifier.substring( 0, identifier.length-1)
会选取 ID 值,而 identifier[identifier.length-1]
会从资源 ID 选取最后一个字符。
您的 index.js 文件现在应该与以下代码匹配:
1[label index.js]
2import crypto from 'crypto'; // for generating bytes from the number
3import base32Encode from 'base32-encode'; // for encoding the bytes into Unique ID as string type
4import base32Decode from 'base32-decode';// for decoding the ID into bytes
5
6function generate_Id(byte_size) {
7 const bytes = crypto.randomBytes(byte_size);
8 return base32Encode(bytes, 'Crockford');
9}
10
11//console.log('ID for byte size = 1:',generate_Id(1), '\n');
12//console.log('ID for byte size = 12:',generate_Id(12), '\n');
13//console.log('ID for byte size = 123:',generate_Id(123), '\n');
14
15function calculate_checksum(bytes) {
16 const intValue = BigInt(`0x${bytes.toString('hex')}`);
17 return Number(intValue % BigInt(37));
18}
19
20function get_checksum_character(checksumValue) {
21 const alphabet = '0123456789ABCDEFG' +
22 'HJKMNPQRSTVWXYZ*~$=U'; // custom-built string consisting of alphanumeric character
23 return alphabet[Math.abs(checksumValue)]; // picking out an alphanumeric character
24}
25
26function generate_Id_with_checksum(bytes_size) {
27 const bytes = crypto.randomBytes(bytes_size);
28 const checksum = calculate_checksum(bytes);
29 const checksumChar = get_checksum_character(checksum);
30 console.log("checksum character: ", checksumChar);
31 const encoded = base32Encode(bytes, 'Crockford');
32 return encoded + checksumChar;
33}
34
35const Hotel_resource_id =generate_Id_with_checksum(132)
36console.log("Hotel resource id: ",Hotel_resource_id)
37
38function verify_Id(identifier) {
39 const value = identifier.substring( 0, identifier.length-1);
40 const checksum_char = identifier[identifier.length-1];
41 //console.log(value,checksum_char);
42 const buffer = Buffer.from( base32Decode(value, 'Crockford'));
43 const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
44 console.log(calculated_checksum_char);
45 const flag =calculated_checksum_char== checksum_char;
46
47 return (flag);
48
49 }
50
51console.log('\n');
52console.log("computing checksum")
53const flag = verify_Id(Hotel_resource_id);
54if (flag) console.log("Checksums matched.");
55else console.log("Checksums did not match.");
现在你可以运行这个代码:
1[environment second]
2node index.js
您将获得以下输出:
1[environment second]
2[secondary_label Output]
3...
4computing checksum
5AW75SY7FVC7TKT7VP5ZF0M8C67CN36YZK27BXHVFHSDXJFKH54HK2AXQFMPN89Q5YQRPGNHGAYQ5JFKVD40EKTXCET97Q0FEPX6MX1ZTNWGCA08SBRSHP8B0037ACJG6F6472FEVARCAWM6P5MRJ2F6WTRPXHYS9N1JEDZVH41D33RA5365VNFC5G5VYEFPFJJD8151B28XXDBRHAF80 H
6H
7Checksums matched.
您现在有一个名为verify_Id
的函数,该函数使用检查总数字符来检查您的标识符的完整性,接下来,为指导目的,您可以更改资源 ID,以便该函数提供不匹配的结果,以评估检查失败时会发生什么。
(可选)步骤 3 — 更改标识符以获取不匹配的结果
现在,您将更改标识符的值,以检查检查检查总数是否匹配。本步骤中的更改将始终导致不匹配的检查总数,因为如果对ID中的任何字符进行操纵,就不会保持完整性。这样的更改可能是由于传输错误或恶意行为造成的。
在index.js
文件中,通过添加突出的行来修改Hotel_resource_id
:
1[label index.js]
2...
3const altered_Hotel_resource_id= Hotel_resource_id.replace('P','H');
4console.log("computing checksum")
5const flag = verify_Id(altered_Hotel_resource_id);
6if (flag) console.log("Checksum matched.");
7else console.log("Checksums did not match.");
在上述代码中,您将 ID 中的任何P
替换为H
,并将变量从Hotel_resource_ID
更名为altered_Hotel_resource_id
。
保存文件,然后重启代码,并对资源 ID 进行更改:
1[environment second]
2node index.js
您将收到一个输出,支票总数不匹配:
1[environment second]
2[secondary_label Output]
3Checksums did not match.
在此步骤中,您创建了一个功能来验证支票总数是否通过了完整性测试,并且您遇到过两种情况。不匹配的支票总数表明资源 ID 已被操纵。
若要将函数返回匹配的计数结果,请删除此步骤开始时添加的额外代码,以便该代码匹配步骤 2 结束时的文件。
当您需要一个自定义的独特ID和支票金额时,您可以使用本教程来帮助您生成数据模型,版本您的API等等。
结论
在本教程中,您开发了与一个好的标识符的特征一致的资源 ID. 您还创建了一个独特的资源 ID,在 Node.js 环境中使用base32-encoding
。
对于交叉确认,您可以将最终文件与 DigitalOcean 社区存储库中的文件进行比较。如果您熟悉git
版本系统,您也可以将存储库git-clone
或遵循 Introduction to GitHub and Open-Source Projects系列。
现在你已经了解了检查总量的基本知识,你可以尝试其他编码算法,例如 MD5。