feat(project): init

This commit is contained in:
2026-02-03 18:05:47 +08:00
commit f37a119eff
188 changed files with 29246 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
export const validatePhoneNumber = (str: string) => {
const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
return reg.test(str)
}
// 获取url中的某个参数值
export const getQueryVariable = (variable: string) => {
const query = window.location.search.substring(1)
const vars = query.split("&")
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split("=")
if (pair[0] === variable) {
return pair[1]
}
}
return false
}
export const APP = "basis"
export const PLATFORM = "basis"
export const TENANT = "cowarobot"

View File

@@ -0,0 +1,93 @@
import { ResultData } from "@/api/typing"
import redis from "@/components/login/libs/redis"
import { encrypt, generateKeyFromEnv } from "@/components/login/libs/session"
import { cookies } from "next/headers"
import { Login } from "@/components/login/api/types"
export const handleLoginData = async (props: {
clientIP: string
fingerprint: string
data: ResultData<Login.ResLogin>
}) => {
const { clientIP, fingerprint, data } = props
const sessionKey = {
clientIP,
fingerprint: fingerprint,
}
const cookieStore = await cookies()
const oldSession = cookieStore.get("session")?.value || ""
if (oldSession) {
redis.del(JSON.parse(oldSession) as any)
}
const key = await generateKeyFromEnv(process.env.ENCRYPTION_KEY || "")
const encryptedSessionData = await encrypt(JSON.stringify(sessionKey), key)
// save redis
await redis.set(
encryptedSessionData,
JSON.stringify({
clientIP,
fingerprint: fingerprint,
user_name: data?.message?.user_info?.user_name,
access_token: data.message.access_token,
refresh_token: data.message.refresh_token,
}),
"EX",
172800
)
// set cookie
cookieStore.set("session", JSON.stringify(encryptedSessionData), {
httpOnly: true,
secure: process.env.NEXT_PUBLIC_COOKIE_ENV === "production",
maxAge: 60 * 60 * 24 * 30, // 30天
path: "/",
})
return {
...data,
message: {
...data.message,
access_token: "",
refresh_token: "",
},
}
}
export const handleRefreshToken = async (props: {
data: ResultData<Login.ResRefreshKey>
userData: any
}) => {
const { userData, data } = props
const { redisKey, ...rest } = userData
await redis.set(
JSON.parse(redisKey),
JSON.stringify({
...rest,
access_token: data?.message?.access,
refresh_token: data?.message?.refresh,
}),
"EX",
172800
)
return {
...data,
message: {
...data.message,
access_token: "",
refresh_token: "",
},
}
}
export const handleDeleteCookie = async () => {
const cookieStore = await cookies()
const sessionData = cookieStore.get("session")?.value || ""
// 删cookie和redis中用户
if (sessionData) {
await redis.del(JSON.parse(sessionData) as any)
cookieStore.delete("session")
}
}

View File

@@ -0,0 +1,23 @@
import Redis from "ioredis"
const redisConfig = {
port: process.env.NEXT_PUBLIC_REDIS_PORT
? Number(process.env.NEXT_PUBLIC_REDIS_PORT)
: 6379, // Redis port
host: process.env.NEXT_PUBLIC_REDIS_URL || "", // Redis host
username: process.env.NEXT_PUBLIC_REDIS_USERNAME || "", // needs Redis >= 6
password: process.env.NEXT_PUBLIC_REDIS_PASSWORD || "",
db: process.env.NEXT_PUBLIC_REDIS_DB
? Number(process.env.NEXT_PUBLIC_REDIS_DB)
: 0, // Defaults to 0
}
const redis = new Redis({
port: redisConfig.port, // Redis port
host: redisConfig.host, // Redis host
username: redisConfig.username, // needs Redis >= 6
password: redisConfig.password,
db: redisConfig.db, // Defaults to 0
})
export default redis

View File

@@ -0,0 +1,156 @@
"use server"
import { Base64 } from "js-base64"
import { headers } from "next/headers"
import redis from "./redis"
// 生成加密密钥
export async function generateKey(): Promise<CryptoKey> {
return crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 256,
},
true,
["encrypt", "decrypt"]
)
}
export async function generateKeyFromEnv(
envPassword: string
): Promise<CryptoKey> {
const encoder = new TextEncoder()
const password = encoder.encode(envPassword)
const keyMaterial = await crypto.subtle.importKey(
"raw",
password,
{
name: "PBKDF2",
},
false,
["deriveKey"]
)
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new Uint8Array(16), // 随机盐值
iterations: 100000, // 迭代次数
hash: "SHA-256",
},
keyMaterial,
{
name: "AES-CBC",
length: 256,
},
true,
["encrypt", "decrypt"]
)
}
// 将ArrayBuffer转换为Uint8Array
function arrayBufferToUint8Array(buffer: ArrayBuffer) {
return new Uint8Array(buffer)
}
// 加密函数
export async function encrypt(data: any, key: CryptoKey): Promise<string> {
const encodedData = new TextEncoder().encode(JSON.stringify(data))
const iv = crypto.getRandomValues(new Uint8Array(16)) // 16 bytes for AES block size
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: iv,
},
key,
encodedData
)
// 将Uint8Array转换为Base64字符串以便存储和传输
const encryptedBase64 = Base64.fromUint8Array(
arrayBufferToUint8Array(encrypted)
)
const ivBase64 = Base64.fromUint8Array(new Uint8Array(iv)) // 使用js-base64进行编码
// 将IV和加密数据拼接在一起以便解密时使用
return `${ivBase64}::${encryptedBase64}`
}
// 解密函数
export async function decrypt(
encryptedData: string,
key: CryptoKey
): Promise<any> {
// 将加密数据从Base64字符串转换回Uint8Array
const [ivBase64, encryptedBase64] = encryptedData.split("::")
const ivArray = Buffer.from(ivBase64, "base64")
const encrypted = Buffer.from(encryptedBase64, "base64")
// 使用AES-CBC算法进行解密
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: ivArray,
},
key,
encrypted
)
// 将ArrayBuffer转换为字符串
return new TextDecoder().decode(decrypted)
}
// 校验用户信息
export async function genAccessToken(req: any) {
try {
// 校验用户信息
// const fingerprint = req.headers.get("Fingerprint")
const sessionData = req.cookies.get("session")?.value
// 无session 直接重定向到登录页面
if (!sessionData) {
return "客户端请求中,未包含用户标识"
}
let redisKey = JSON.parse(sessionData)
const key = await generateKeyFromEnv(process.env.ENCRYPTION_KEY || "")
const decryptedSessionData = JSON.parse(await decrypt(redisKey, key))
const fingerprint = JSON.parse(decryptedSessionData)?.fingerprint
// 获取用户信息
let userData: any = await redis.get(redisKey)
userData = JSON.parse(userData)
// redis中无用户信息 直接重定向到登录页面
if (!userData) {
return "未获取到用户信息"
}
const header = headers()
let clientIP =
(await header).get("x-forwarded-for")?.split(",")[0] ||
req?.socket?.remoteAddress
// ip v6地址
if (clientIP.startsWith("::")) {
clientIP = clientIP.slice(7)
}
// 登录ip与用户请求ip不匹配
if (
clientIP !== userData?.clientIP ||
fingerprint !== userData?.fingerprint
) {
return `客户端信息与缓存用户信息不一致 \n 客户端: ${clientIP}, ${fingerprint} | redis存储: ${userData?.clientIP}, ${userData?.fingerprint}`
}
return {
...userData,
redisKey: sessionData,
}
} catch (err: any) {
return `获取用户信息报错, 报错原因:${err?.message}`
}
}