From 8fab48f849aad69bf8b6dcea53c1db24ace79bca Mon Sep 17 00:00:00 2001 From: oiov Date: Mon, 26 May 2025 15:57:34 +0800 Subject: [PATCH] test --- actions/redirect.ts | 75 +++++++++++++++++++++++++++ app/ss/[slug]/page.tsx | 115 +++++++++++++++++++++-------------------- lib/geo.ts | 2 +- middleware.ts | 26 ++++------ public/sw.js.map | 2 +- 5 files changed, 146 insertions(+), 74 deletions(-) create mode 100644 actions/redirect.ts diff --git a/actions/redirect.ts b/actions/redirect.ts new file mode 100644 index 0000000..52b3e20 --- /dev/null +++ b/actions/redirect.ts @@ -0,0 +1,75 @@ +"use server"; + +import { redirect } from "next/navigation"; + +import { siteConfig } from "@/config/site"; +import { createUserShortUrlMeta, getUrlBySuffix } from "@/lib/dto/short-urls"; +import { redirectMap } from "@/app/ss/[slug]/page"; + +export async function RedirectsTo(data: any) { + const { + slug, + referer, + ip, + city, + region, + country, + latitude, + longitude, + lang, + device, + browser, + password, + } = data; + + if (!slug || !ip) + return redirect(`${siteConfig.url}${redirectMap["Missing[0000]"]}${slug}`); + + const res = await getUrlBySuffix(slug); + + if (!res) + return redirect(`${siteConfig.url}${redirectMap["Disabled[0002]"]}${slug}`); + + if (res.active !== 1) + return redirect(`${siteConfig.url}${redirectMap["Disabled[0002]"]}${slug}`); + + if (res.password !== "") { + if (!password) { + return redirect( + `${siteConfig.url}${redirectMap["PasswordRequired[0004]"]}${slug}`, + ); + } + if (password !== res.password) { + return redirect( + `${siteConfig.url}${redirectMap["IncorrectPassword[0005]"]}${slug}`, + ); + } + } + + const now = Date.now(); + const createdAt = new Date(res.updatedAt).getTime(); + const expirationMilliseconds = Number(res.expiration) * 1000; + const expirationTime = createdAt + expirationMilliseconds; + + if (res.expiration !== "-1" && now > expirationTime) { + return redirect(`${siteConfig.url}${redirectMap["Expired[0001]"]}`); + } + + await createUserShortUrlMeta({ + urlId: res.id, + click: 1, + ip, + city, + region, + country, + latitude, + longitude, + referer, + lang, + device, + browser, + }); + console.log("到这了"); + + redirect(res.target); +} diff --git a/app/ss/[slug]/page.tsx b/app/ss/[slug]/page.tsx index f81b1b2..8251031 100644 --- a/app/ss/[slug]/page.tsx +++ b/app/ss/[slug]/page.tsx @@ -1,12 +1,11 @@ import { headers } from "next/headers"; -import { redirect } from "next/navigation"; +import { RedirectsTo } from "@/actions/redirect"; import UAParser from "ua-parser-js"; import { siteConfig } from "@/config/site"; -import { createUserShortUrlMeta, getUrlBySuffix } from "@/lib/dto/short-urls"; import { extractRealIP, getClientGeolocation } from "@/lib/geo"; -const redirectMap = { +export const redirectMap = { "Missing[0000]": "/docs/short-urls#missing-links", "Expired[0001]": "/docs/short-urls#expired-links", "Disabled[0002]": "/docs/short-urls#disabled-links", @@ -31,10 +30,7 @@ export default async function ShortUrlPage({ const { slug } = params; const { password = "" } = searchParams; - if (!slug) redirect(redirectMap["Missing[0000]"]); - try { - // 获取请求头信息 const headersList = headers(); const userAgent = headersList.get("user-agent") || ""; const referer = headersList.get("referer") || "(None)"; @@ -48,63 +44,70 @@ export default async function ShortUrlPage({ const geo = await getClientGeolocation(); - // const trackingData = { - // slug, - // referer, - // ip: realIP, - // city: geo?.city, - // region: geo?.region, - // country: geo?.country, - // latitude: geo?.latitude, - // longitude: geo?.longitude, - // // flag: geo?.flag, - // lang: acceptLanguage?.split(",")[0], - // device: device.model || "Unknown", - // browser: browser.name || "Unknown", - // password, - // }; - - const data = await getUrlBySuffix(slug); - if (!data || data.active !== 1) redirect(redirectMap["Disabled[0002]"]); - - if (data.password !== "") { - if (!password) { - redirect(redirectMap["PasswordRequired[0004]"]); - } - if (password !== data.password) { - redirect(redirectMap["IncorrectPassword[0005]"]); - } - } - - const now = Date.now(); - const createdAt = new Date(data.updatedAt).getTime(); - const expirationMilliseconds = Number(data.expiration) * 1000; - const expirationTime = createdAt + expirationMilliseconds; - - if (data.expiration !== "-1" && now > expirationTime) { - redirect(redirectMap["Expired[0001]"]); - } - - await createUserShortUrlMeta({ - urlId: data.id, - click: 1, - ip: realIP, - city: geo?.city || "", - region: geo?.region || "", - country: geo?.country || "", - latitude: geo?.latitude || "", - longitude: geo?.longitude || "", + const trackingData = { + slug, referer, - lang: acceptLanguage?.split(",")[0] || "", + ip: realIP, + city: geo?.city, + region: geo?.region, + country: geo?.country, + latitude: geo?.latitude, + longitude: geo?.longitude, + // flag: geo?.flag, + lang: acceptLanguage?.split(",")[0], device: device.model || "Unknown", browser: browser.name || "Unknown", - }); + password, + }; + + // await RedirectsTo(trackingData); + + // await fetch(`${siteConfig.url}/api/s`, { + // method: "POST", + // // headers: { "Content-Type": "application/json" }, + // body: JSON.stringify(trackingData), + // }); + + // const data = await getUrlBySuffix(slug); + // if (!data || data.active !== 1) redirect(redirectMap["Disabled[0002]"]); + + // if (data.password !== "") { + // if (!password) { + // redirect(redirectMap["PasswordRequired[0004]"]); + // } + // if (password !== data.password) { + // redirect(redirectMap["IncorrectPassword[0005]"]); + // } + // } + + // const now = Date.now(); + // const createdAt = new Date(data.updatedAt).getTime(); + // const expirationMilliseconds = Number(data.expiration) * 1000; + // const expirationTime = createdAt + expirationMilliseconds; + + // if (data.expiration !== "-1" && now > expirationTime) { + // redirect(redirectMap["Expired[0001]"]); + // } + + // await createUserShortUrlMeta({ + // urlId: data.id, + // click: 1, + // ip: realIP, + // city: geo?.city || "", + // region: geo?.region || "", + // country: geo?.country || "", + // latitude: geo?.latitude || "", + // longitude: geo?.longitude || "", + // referer, + // lang: acceptLanguage?.split(",")[0] || "", + // device: device.model || "Unknown", + // browser: browser.name || "Unknown", + // }); // 重定向到目标URL - window.location.href = data.target; + // window.location.href = data.target; // redirect(data.target); } catch (error) { console.error("Short URL redirect error:", error); - redirect("/"); } } diff --git a/lib/geo.ts b/lib/geo.ts index 8ee9714..1ff5f92 100644 --- a/lib/geo.ts +++ b/lib/geo.ts @@ -40,7 +40,7 @@ export function extractRealIP(headers: Headers): string { } } - return ""; + return "::1"; } function isValidIP(ip: string): boolean { diff --git a/middleware.ts b/middleware.ts index 463123a..86765e5 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,8 +1,7 @@ -import { NextResponse } from "next/server"; +import { NextResponse, userAgent } from "next/server"; import { geolocation } from "@vercel/functions"; import { auth } from "auth"; import { NextAuthRequest } from "next-auth/lib"; -import UAParser from "ua-parser-js"; import { siteConfig } from "./config/site"; @@ -19,7 +18,6 @@ const redirectMap = { "IncorrectPassword[0005]": "/password-prompt?error=1&slug=", }; -// 提取短链接处理逻辑 async function handleShortUrl(req: NextAuthRequest) { if (!req.url.includes("/s/")) return NextResponse.next(); @@ -29,7 +27,7 @@ async function handleShortUrl(req: NextAuthRequest) { const geo = geolocation(req); const headers = req.headers; - const { browser, device } = parseUserAgent(headers.get("user-agent") || ""); + const ua = userAgent(req); const url = new URL(req.url); const password = url.searchParams.get("password") || ""; @@ -45,11 +43,17 @@ async function handleShortUrl(req: NextAuthRequest) { longitude: geo?.longitude, flag: geo?.flag, lang: headers.get("accept-language")?.split(",")[0], - device: device.model || "Unknown", - browser: browser.name || "Unknown", + device: ua.device.model || "Unknown", + browser: ua.browser.name || "Unknown", + engine: ua.engine.name || "", + os: ua.os.name || "", + cpu: ua.cpu.architecture || "", + isBot: ua.isBot, password, }; + console.log("Tracking data:", trackingData, siteConfig.url); + const res = await fetch(`${siteConfig.url}/api/s`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -96,16 +100,6 @@ function extractSlug(url: string): string | null { return match ? match[1] : null; } -// 解析用户代理 -const parser = new UAParser(); -function parseUserAgent(ua: string) { - parser.setUA(ua); - return { - browser: parser.getBrowser(), - device: parser.getDevice(), - }; -} - export default auth(async (req) => { try { return await handleShortUrl(req); diff --git a/public/sw.js.map b/public/sw.js.map index 7a90796..a7a7b41 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/adcbde2b5020f2f3dc80af4ecb4ef150/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/288116b4ca05341ee7f85d96d99e9758/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file