短链接
短链接介绍
短链接是一种缩短长网址的方法,将原始的长网址转换为更短的形式。它通常由一系列的字母、数字和特殊字符组成,比起原始的长网址,短链接更加简洁、易于记忆和分享。
短链接的主要用途之一是在社交媒体平台进行链接分享。由于这些平台对字符数量有限制,长网址可能会占用大量的空间,因此使用短链接可以节省字符数,并且更方便在推特、短信等限制字数的场景下使用。
另外,短链接还可以用于跟踪和统计链接的点击量。通过在短链接中嵌入跟踪代码,网站管理员可以获得关于点击链接的详细统计数据,包括访问量、来源、地理位置等信息。这对于营销活动、广告推广或分析链接的效果非常有用。
实现原理:
利用一个接口生成一个唯一的短码,将短码与真实的url存到数据库里面,然后利用动态路径,重定向到真实的url
代码实现
需要依赖的库
- epxress 启动服务提供接口
- mysql2 knex依赖连接数据库
- knex orm框架操作mysql
- shortid 生成唯一短码
import knex from "knex"
import express from "express"
import mysql2 from "mysql2"
import shortid from "shortid"
const db = knex({
client: "mysql2",
connection: {
host: "localhost",
user: "root",
password: "..",
database: "lianxi"
}
})
const app = express()
app.use(express.json())
// 解析www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: true }))
// 使用短链接技术
app.post("/create_url", async (req, res) => {
const url = req.body.url
if(!url) return res.send("请输入要缩短的链接")
const short_id = shortid.generate()
const result = await db("short").insert({ short_id, url })
res.send(`http://localhost:3000/${short_id}`)
})
app.get("/:short_id", async (req, res) => {
const short_id = req.params.short_id
if(!short_id) return res.send("请输入要访问的链接")
const url = await db("short").where({ short_id }).first()
if(!url) return res.send("链接不存在")
res.redirect(url.url)
})
app.listen(3000, () => {
console.log("服务器已启动")
})
SDL单设备登录
SDL(Single Device Login)是一种单设备登录的机制,它允许用户在同一时间只能在一个设备上登录,当用户在其他设备上登录时,之前登录的设备会被挤下线。
应用场景
- 视频影音,防止一个账号共享,防止一些账号贩子
- 对于在线购物和电子支付平台,用户的支付信息和订单详情是敏感的。通过单设备登录,可以在用户进行支付操作时增加额外的安全层级,确保只有授权设备可以进行支付操作
实现思路
设计数据结构
connection = {
id: {
socket, // wss实例
fingerprint // 浏览器指纹
}
}
- 初次登录,那么就使用用户id为key存储wss实例以及浏览器指纹
- 二次登录,先使用存好的wss实例给前端发信息,告诉说被挤下线,然后关闭之前的连接,存储新的wss实例
浏览器指纹
这里采用canvas指纹技术
需要利用的库
express ,cors解决跨域,ws 一个webSocket的库
如果不了解webSocket清看:万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)写的不错_websocket用法-CSDN博客
实现代码
node.js端
import express from 'express';
import cors from 'cors';
import {WebSocketServer} from "ws"
const app = express();
app.use(cors());
app.use(express.json());
const server = app.listen(3000,()=> {
console.log("Server is running on port 3000");
})
// 创建websocket服务器
const wss = new WebSocketServer({server});
let connection = {}
// 监听连接事件
wss.on("connection", (ws) => {
ws.on("message", (message) => {
const data = JSON.parse(message)
if(data.action === "login") {
// 查看是否为第一次登录,
if(connection[data.id] && connection[data.id].fingerprint) {
console.log("已登录")
// 提示旧设备别处登录
connection[data.id].ws.send(JSON.stringify({
action: "logout",
message: `您的账号在${new Date().toLocaleDateString()}于别处登录`
}))
// 断开连接
connection[data.id].ws.close()
// 更新ws
connection[data.id].ws = ws
}else { // 第一次登录
console.log("初次登录")
connection[data.id] = {
ws,
fingerprint: data.fingerprint
}
}
}
})
})
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="./md5.js"></script>
<script>
// 生成浏览器指纹函数
function getFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = "18px Arial";
ctx.fillText("Hello, world!", 10, 50);
return hex_md5(canvas.toDataURL());
}
// 创建wss连接
const ws = new WebSocket('ws://localhost:3000');
// 监听连接成功
ws.addEventListener("open",()=> {
ws.send(JSON.stringify({
action: "login",
id: 1, // 用户id
fingerprint: getFingerprint() // 浏览器指纹
}))
})
// 监听消息
ws.addEventListener("message", (message) => {
const data = JSON.parse(message.data);
if (data.action === "logout") {
alert(data.message);
}
})
</script>
</body>
</html>
md5.js
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
function core_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Calculate the HMAC-MD5, of a key and some data
*/
function core_hmac_md5(key, data)
{
var bkey = str2binl(key);
if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
return core_md5(opad.concat(hash), 512 + 128);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
function str2binl(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
return bin;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
return str;
}
/*
* Convert an array of little-endian words to a hex string.
*/
function binl2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
}
return str;
}
/*
* Convert an array of little-endian words to a base-64 string
*/
function binl2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
}
}
return str;
}
SCL 扫码登陆
SCL (Scan Code Login) 是一种扫码登录的技术,它允许用户通过扫描二维码来进行登录操作。这种登录方式在许多应用和网站中得到广泛应用,因为它简单、方便且安全。
SCL 扫码登录的优点包括:
- 方便快捷:用户只需打开扫码应用程序并扫描二维码即可完成登录,无需手动输入用户名和密码。
- 安全性高:扫码登录采用了加密技术,用户的登录信息在传输过程中得到保护,降低了密码被盗取或泄露的风险。
- 避免键盘记录:由于用户无需在登录过程中输入敏感信息,如用户名和密码,因此不会受到键盘记录软件的威胁。
- 适用性广泛:SCL 扫码登录可以与不同的应用和网站集成,提供统一的登录方式,使用户无需记住多个账户的用户名和密码。
安装依赖
- express
- jsonwebtoken
- qrcode 生成二维码
实现思路
- 需要有一个接口去生成二维码,里面应该初始化信息,token标定用户是否登录,id识别用户身份
- 确认授权接口,当点击确认授权之后,生成token
- 需要检查二维码是否过期的接口,前端去轮询这个接口,当过期或者已登录,就停止轮询
实现代码
node.js
import express from 'express';
import cors from 'cors';
import qrcode from 'qrcode';
import jwt from 'jsonwebtoken';
const app = express();
app.use(cors());
app.use(express.json());
// 静态文件
app.use(express.static('public'));
const user = {
}
const userId = 1
// 生成二维码接口
app.get('/qrcode', async (req, res) => {
// 1. 初始化数据
user[userId] = {
token: null,
time: Date.now()
}
// 2. 生成二维码
const code = await qrcode.toDataURL(`http://192.168.253.1:3000/manData?userId=${userId}`);
// 3. 返回二维码
res.json({
code,
userId
})
})
// 扫码接口
app.post("login/:userId", (req, res) => {
const userId = req.params.userId;
user[userId].token = jwt.sign({ userId }, 'secret', { expiresIn: '1h' });
user[userId].time = Date.now();
res.json({
token: user[userId].token
})
})
// 轮询二维码是否有效
app.get("/check/:userId", (req, res) => {
const userId = req.params.userId;
if (user[userId].time + 1000 * 60 * 3 < Date.now()) {
res.json({
status: 2 // 超时
})
}else if (user[userId].token) {
res.json({
status: 1 // 有效
})
}else {
res.json({
status: 0 // 默认值
})
}
})
app.listen(3000, () => console.log('Server is running on port 3000'));
qrcode.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img id="qrcode" src="" alt="">
<div id="status-div"></div>
<script>
const status = {
0: '未授权',
1: '已授权',
2: '超时'
}
const qrcode = document.getElementById('qrcode')
const statusDiv = document.getElementById('status-div')
let userId = null
statusDiv.innerText = status[0]
fetch('http://localhost:3000/qrcode').then(res => res.json()).then(res => {
qrcode.src = res.code //获取二维码
userId = res.userId //获取用户id
let timer = setInterval(() => {
//轮询调用检查接口
fetch(`http://localhost:3000/check/${userId}`).then(res => res.json()).then(res => {
statusDiv.innerText = status[res.status]
//如果返回的状态是 超时 或者是已授权 就停止轮训
if (res.status != 0) {
clearInterval(timer)
}
})
}, 1000)
})
</script>
</body>
</html>
manData.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div> <button id="btn" style="width: 100%;height: 50px;">同意授权</button></div>
<div> <button style="width: 100%;height: 50px;margin-top: 20px;">拒绝授权</button></div>
<script>
const btn = document.getElementById('btn')
let userId = location.search.slice(1).split('=')[1]
btn.onclick = () => {
//点击授权按钮
fetch(`/login/${userId}`, {
method: 'POST',
}).then(res => res.json()).then(res => {
alert(`授权成功`)
}).catch(err => {
alert(err)
})
}
</script>
</body>
</html>