-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
191 lines (171 loc) · 5.7 KB
/
script.js
File metadata and controls
191 lines (171 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
const EPOCH = 1288834974657n;
const MAX_DATACENTER = 31n;
const MAX_WORKER = 31n;
const MAX_SEQUENCE = 4095n;
let lastTimestamp = -1n;
let sequence = 0n;
const form = document.querySelector("#generator-form");
const countInput = document.querySelector("#count");
const datacenterInput = document.querySelector("#datacenter");
const workerInput = document.querySelector("#worker");
const generatedOutput = document.querySelector("#generated-output");
const generatorMessage = document.querySelector("#generator-message");
const parseInput = document.querySelector("#parse-input");
const parserMessage = document.querySelector("#parser-message");
const copyGenerated = document.querySelector("#copy-generated");
const copyParsed = document.querySelector("#copy-parsed");
const metricTimestamp = document.querySelector("#metric-timestamp");
const metricDate = document.querySelector("#metric-date");
const metricDatacenter = document.querySelector("#metric-datacenter");
const metricWorker = document.querySelector("#metric-worker");
const metricSequence = document.querySelector("#metric-sequence");
const binaryOutput = document.querySelector("#binary-output");
function readNumber(input, min, max, label) {
const value = Number(input.value);
if (!Number.isInteger(value) || value < min || value > max) {
throw new Error(`${label} 需要在 ${min} 到 ${max} 之间`);
}
return BigInt(value);
}
function currentSnowflakeTimestamp() {
return BigInt(Date.now()) - EPOCH;
}
function nextId(datacenterId, workerId) {
let timestamp = currentSnowflakeTimestamp();
if (timestamp < lastTimestamp) {
throw new Error("系统时间发生回拨,暂时无法生成 ID");
}
if (timestamp === lastTimestamp) {
sequence = (sequence + 1n) & MAX_SEQUENCE;
if (sequence === 0n) {
while (timestamp <= lastTimestamp) {
timestamp = currentSnowflakeTimestamp();
}
}
} else {
sequence = 0n;
}
lastTimestamp = timestamp;
return (timestamp << 22n) | (datacenterId << 17n) | (workerId << 12n) | sequence;
}
function parseSnowflakeId(id) {
return {
id,
timestamp: (id >> 22n) + EPOCH,
datacenterId: (id & 0x3E0000n) >> 17n,
workerId: (id & 0x1F000n) >> 12n,
sequence: id & 0xFFFn,
};
}
function formatDate(timestamp) {
return new Date(Number(timestamp)).toLocaleString("zh-CN", {
hour12: false,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
fractionalSecondDigits: 3,
});
}
function renderParsed(info) {
metricTimestamp.textContent = info.timestamp.toString();
metricDate.textContent = formatDate(info.timestamp);
metricDatacenter.textContent = info.datacenterId.toString();
metricWorker.textContent = info.workerId.toString();
metricSequence.textContent = info.sequence.toString();
binaryOutput.textContent = info.id.toString(2).padStart(64, "0").replace(/(.{8})/g, "$1 ").trim();
}
function clearParsed(message = "") {
metricTimestamp.textContent = "—";
metricDate.textContent = "等待输入";
metricDatacenter.textContent = "—";
metricWorker.textContent = "—";
metricSequence.textContent = "—";
binaryOutput.textContent = "输入 ID 后显示 64-bit 二进制";
parserMessage.textContent = message;
}
function parseInputValue() {
const raw = parseInput.value.trim();
if (!raw) {
clearParsed("");
return null;
}
if (!/^\d+$/.test(raw)) {
throw new Error("请输入非负整数 ID");
}
const id = BigInt(raw);
if (id > 0x7FFFFFFFFFFFFFFFn) {
throw new Error("ID 超出 signed 64-bit 正整数范围");
}
return parseSnowflakeId(id);
}
function copyText(text, messageElement, successText) {
if (!text.trim()) {
messageElement.textContent = "没有可复制的内容";
return;
}
navigator.clipboard.writeText(text).then(
() => {
messageElement.textContent = successText;
},
() => {
messageElement.textContent = "当前浏览器不允许直接复制,请手动选择文本";
}
);
}
form.addEventListener("submit", (event) => {
event.preventDefault();
try {
const count = Number(readNumber(countInput, 1, 1000, "数量"));
const datacenterId = readNumber(datacenterInput, 0, Number(MAX_DATACENTER), "数据中心 ID");
const workerId = readNumber(workerInput, 0, Number(MAX_WORKER), "工作节点 ID");
const ids = Array.from({ length: count }, () => nextId(datacenterId, workerId).toString());
generatedOutput.value = ids.join("\n");
generatorMessage.textContent = `已生成 ${ids.length} 条 ID`;
parseInput.value = ids[0] ?? "";
renderParsed(parseSnowflakeId(BigInt(ids[0])));
parserMessage.textContent = "已解析第一条生成结果";
} catch (error) {
generatorMessage.textContent = error.message;
}
});
parseInput.addEventListener("input", () => {
try {
const info = parseInputValue();
if (info) {
renderParsed(info);
parserMessage.textContent = "";
}
} catch (error) {
clearParsed(error.message);
}
});
copyGenerated.addEventListener("click", () => {
copyText(generatedOutput.value, generatorMessage, "生成结果已复制");
});
copyParsed.addEventListener("click", () => {
try {
const info = parseInputValue();
if (!info) {
parserMessage.textContent = "没有可复制的解析结果";
return;
}
copyText(
[
`id: ${info.id}`,
`timestamp: ${info.timestamp}`,
`datetime: ${formatDate(info.timestamp)}`,
`datacenterId: ${info.datacenterId}`,
`workerId: ${info.workerId}`,
`sequence: ${info.sequence}`,
].join("\n"),
parserMessage,
"解析结果已复制"
);
} catch (error) {
clearParsed(error.message);
}
});
form.requestSubmit();