Skip to content

Commit c9aaf34

Browse files
committed
添加密码验证和统计白名单
1 parent ac15be8 commit c9aaf34

34 files changed

+832
-93
lines changed

.prettierrc.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"singleQuote": false,
3+
"semi": true,
4+
"quote": "double",
5+
"tabWidth": 2,
6+
"printWidth": 566,
7+
"trailingComma": "none",
8+
"quoteProps": "preserve"
9+
}

functions/api.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import { vh_INIT } from './utils/init.js';
1+
import { vh_INIT } from "./utils/init.js";
22
export async function onRequest({ request, env }) {
33
try {
4-
let { time, siteID, type } = await request.json();
5-
if (!env.CLOUDFLARE_ACCOUNT_ID || !env.CLOUDFLARE_API_TOKEN) return Response.json({ success: false, message: '请设置 CLOUDFLARE_ACCOUNT_ID 和 CLOUDFLARE_API_TOKEN' }, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': '*' } });
6-
// TZ
7-
const tz = request.cf.timezone || 'Asia/Shanghai';
8-
// // 时间校验
9-
const timeArr = ['today', '1d', '7d', '30d', '90d'];
10-
if (!timeArr.includes(time)) time = 'today';
4+
let { time, siteID, type, session } = await request.json();
5+
// 是否开启密码登录
6+
if (env.CLOUDFLARE_WEBSITE_PWD) {
7+
// 校验登录
8+
if (!session || session != env.CLOUDFLARE_WEBSITE_PWD) {
9+
return Response.json({ success: false, code: 401, message: "密码校验失败" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*" } });
10+
} else if (type == "Login") {
11+
return Response.json({ success: true, message: "登录成功" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*" } });
12+
}
13+
}
14+
// 是否配置Cloudflare信息
15+
if (!env.CLOUDFLARE_ACCOUNT_ID || !env.CLOUDFLARE_API_TOKEN) return Response.json({ success: false, message: "请设置 CLOUDFLARE_ACCOUNT_ID 和 CLOUDFLARE_API_TOKEN" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*" } });
16+
// 时区
17+
const tz = request.cf.timezone || "Asia/Shanghai";
18+
// 周期校验
19+
const timeArr = ["today", "1d", "7d", "30d", "90d"];
20+
if (!timeArr.includes(time)) time = "today";
1121
const data = await vh_INIT(env, time, siteID, tz, type);
12-
return Response.json({ success: true, data }, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': '*' } });
22+
return Response.json({ success: true, data }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*" } });
1323
} catch (error) {
14-
return Response.json({ success: false, error }, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': '*' } });
24+
return Response.json({ success: false, error }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*" } });
1525
}
1626
}

functions/send.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import { UAParser } from 'ua-parser-js';
1+
import { UAParser } from "ua-parser-js";
22
export async function onRequest({ request, env }) {
33
try {
44
const { host, path, referrer, website, visitor, visit } = await request.json();
5+
// 校验统计白名单
6+
if (env.CLOUDFLARE_WEBSITE_WHITELIST) {
7+
const websiteArr = env.CLOUDFLARE_WEBSITE_WHITELIST.split("|");
8+
const currentWebsite = websiteArr.find(i => i.includes(website) && i.includes(host));
9+
if (!currentWebsite) return Response.json({ success: false, message: "当前网站不在白名单内" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" } });
10+
}
511
// UA
6-
const userAgent = request.headers.get('user-agent') || undefined;
12+
const userAgent = request.headers.get("user-agent") || undefined;
713
const parsedUserAgent = new UAParser(userAgent);
814
const { browser, os } = parsedUserAgent.getResult();
915
// Area
10-
const area = request.cf ? request.cf.country : '-';
11-
// Referrer
12-
let referrerUrl = '';
16+
const area = request.cf ? request.cf.country : "-";
17+
// Referrer
18+
let referrerUrl = "";
1319
try {
14-
referrerUrl = new URL(referrer).host == host ? '' : referrer;
20+
referrerUrl = new URL(referrer).host == host ? "" : referrer;
1521
} catch (error) {
16-
referrerUrl = referrer
22+
referrerUrl = referrer;
1723
}
1824
// 写数据
1925
website &&
@@ -24,15 +30,15 @@ export async function onRequest({ request, env }) {
2430
host, //Host - blob2
2531
path, //path - blob3
2632
referrerUrl, //referrer - blob4
27-
os.name || 'Windows', //osName - blob5
28-
browser.name == 'Chrome WebView' ? 'Chrome' : browser.name || 'Chrome', //browserName - blob6
33+
os.name || "Windows", //osName - blob5
34+
browser.name == "Chrome WebView" ? "Chrome" : browser.name || "Chrome", //browserName - blob6
2935
area, //areaCode - blob7
3036
userAgent //UA - blob8
3137
],
3238
doubles: [visitor ? 1 : 0, visit ? 1 : 0]
33-
}); // Response
39+
}); // Response
3440
} catch (error) {
35-
return Response.json({ success: false, error, message: 'Han Analytics Send Error' }, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' } });
41+
return Response.json({ success: false, error, message: "Han Analytics Send Error" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" } });
3642
}
37-
return Response.json({ success: true, message: 'Hello Han Analytics' }, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' } });
43+
return Response.json({ success: true, message: "Hello Han Analytics" }, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" } });
3844
}

functions/utils/index.js

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
import dayjs from 'dayjs';
2-
import utc from 'dayjs/plugin/utc.js';
3-
import timezone from 'dayjs/plugin/timezone.js';
1+
import dayjs from "dayjs";
2+
import utc from "dayjs/plugin/utc.js";
3+
import timezone from "dayjs/plugin/timezone.js";
44
dayjs.extend(utc);
55
dayjs.extend(timezone);
66

77
// SqlTIme格式化
88
export const formatTime = (timeStr, tz) => {
9-
const defaultTz = new Intl.DateTimeFormat([], { timeZone: undefined }).resolvedOptions().timeZone || 'UTC';
9+
const defaultTz = new Intl.DateTimeFormat([], { timeZone: undefined }).resolvedOptions().timeZone || "UTC";
1010
const startDay = dayjs()
1111
.tz(tz)
12-
.subtract(Number(timeStr.replace('d', '')), 'day')
13-
.startOf('day')
12+
.subtract(Number(timeStr.replace("d", "")), "day")
13+
.startOf("day")
1414
.tz(defaultTz)
15-
.format('YYYY-MM-DD HH:mm:ss');
15+
.format("YYYY-MM-DD HH:mm:ss");
1616
const endDay = dayjs()
1717
.tz(tz)
18-
.add(timeStr == '1d' ? 0 : 1, 'day')
19-
.startOf('day')
18+
.add(timeStr == "1d" ? 0 : 1, "day")
19+
.startOf("day")
2020
.tz(defaultTz)
21-
.format('YYYY-MM-DD HH:mm:ss');
22-
let sqlTime = '';
21+
.format("YYYY-MM-DD HH:mm:ss");
22+
let sqlTime = "";
2323
switch (timeStr) {
24-
case '1d':
25-
case '7d':
26-
case '30d':
27-
case '90d':
24+
case "1d":
25+
case "7d":
26+
case "30d":
27+
case "90d":
2828
sqlTime = `toDateTime('${startDay}') AND timestamp < toDateTime('${endDay}')`;
2929
break;
3030
default:
@@ -36,7 +36,7 @@ export const formatTime = (timeStr, tz) => {
3636
// 次数统计
3737
export const countData = (arr, key, keyType, status = true) => {
3838
// 处理JS中对象无序排列问题
39-
const _StringKey = status ? '' : `-_-www.vvhan.com-_-`;
39+
const _StringKey = status ? "" : `-_-www.vvhan.com-_-`;
4040
let res = arr.reduce((_arr, v) => {
4141
_arr[`${v[key]}${_StringKey}`] ? (_arr[`${v[key]}${_StringKey}`] += 1) : (_arr[`${v[key]}${_StringKey}`] = 1);
4242
return _arr;
@@ -45,27 +45,27 @@ export const countData = (arr, key, keyType, status = true) => {
4545
// 数据处理
4646
const timeArr = {};
4747
switch (keyType.key) {
48-
case 'today':
48+
case "today":
4949
Array.from({ length: keyType.now.hour() }).forEach((i, idx) => {
50-
timeArr[`${String(idx).padStart(2, '0')}${_StringKey}`] = 0;
50+
timeArr[`${String(idx).padStart(2, "0")}${_StringKey}`] = 0;
5151
});
5252
break;
5353

54-
case '1d':
54+
case "1d":
5555
Array.from({ length: 24 }).forEach((i, idx) => {
56-
timeArr[`${String(idx).padStart(2, '0')}${_StringKey}`] = 0;
56+
timeArr[`${String(idx).padStart(2, "0")}${_StringKey}`] = 0;
5757
});
5858
break;
5959

60-
case '7d':
61-
case '30d':
62-
case '90d':
63-
Array.from({ length: Number(String(keyType.key).replace('d', '')) }).forEach((i, idx) => {
60+
case "7d":
61+
case "30d":
62+
case "90d":
63+
Array.from({ length: Number(String(keyType.key).replace("d", "")) }).forEach((i, idx) => {
6464
timeArr[
6565
`${keyType.now
66-
.subtract(Number(String(keyType.key).replace('d', '')), 'day')
67-
.add(idx, 'day')
68-
.format('MM.DD')}${_StringKey}`
66+
.subtract(Number(String(keyType.key).replace("d", "")), "day")
67+
.add(idx, "day")
68+
.format("MM.DD")}${_StringKey}`
6969
] = 0;
7070
});
7171
break;
@@ -75,7 +75,7 @@ export const countData = (arr, key, keyType, status = true) => {
7575
}
7676
res = { ...timeArr, ...res };
7777
return Object.entries(res)
78-
.map(([name, value]) => ({ name: name.replace(_StringKey, ''), value }))
78+
.map(([name, value]) => ({ name: name.replace(_StringKey, ""), value }))
7979
.sort((a, b) => (status ? b.value - a.value : Number(a.name) - Number(b.name)));
8080
};
8181

@@ -88,27 +88,27 @@ export const echartsData = (data, key, tz) => {
8888
// key=90 过去90天
8989
let timeArr = [];
9090
switch (key) {
91-
case 'today':
92-
case '1d':
93-
timeArr = data.map((i) => {
94-
i.t_str = dayjs.utc(i.timestamp).tz(tz).format('HH');
91+
case "today":
92+
case "1d":
93+
timeArr = data.map(i => {
94+
i.t_str = dayjs.utc(i.timestamp).tz(tz).format("HH");
9595
return i;
9696
});
9797
break;
98-
case '7d':
99-
case '30d':
100-
case '90d':
101-
timeArr = data.map((i) => {
102-
i.t_str = dayjs.utc(i.timestamp).tz(tz).format('MM.DD');
98+
case "7d":
99+
case "30d":
100+
case "90d":
101+
timeArr = data.map(i => {
102+
i.t_str = dayjs.utc(i.timestamp).tz(tz).format("MM.DD");
103103
return i;
104104
});
105105
break;
106106
default:
107-
timeArr = data.map((i) => {
108-
i.t_str = dayjs.utc(i.timestamp).tz(tz).format('HH');
107+
timeArr = data.map(i => {
108+
i.t_str = dayjs.utc(i.timestamp).tz(tz).format("HH");
109109
return i;
110110
});
111111
}
112112
const now = dayjs().tz(tz);
113-
return countData(timeArr, 't_str', { key, now }, false);
113+
return countData(timeArr, "t_str", { key, now }, false);
114114
};

functions/utils/init.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
import { AREAS } from './area.js';
2-
import { formatTime, countData, echartsData } from './index.js';
1+
import { AREAS } from "./area.js";
2+
import { formatTime, countData, echartsData } from "./index.js";
33
export const vh_INIT = async (env, time, siteID, tz, type = null) => {
44
// 查询
5-
const query = type == 'list' ? `SELECT blob1 FROM AnalyticsDataset GROUP BY blob1` : `SELECT blob2, blob3, blob4, blob5, blob6, blob7, double1, double2 ,timestamp FROM AnalyticsDataset WHERE timestamp >= ${formatTime(time, tz)} AND blob1 = '${siteID}' ORDER by timestamp`;
5+
const query = type == "list" ? `SELECT blob1 FROM AnalyticsDataset GROUP BY blob1` : `SELECT blob2, blob3, blob4, blob5, blob6, blob7, double1, double2 ,timestamp FROM AnalyticsDataset WHERE timestamp >= ${formatTime(time, tz)} AND blob1 = '${siteID}' ORDER by timestamp`;
66
const defaultUrl = `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/analytics_engine/sql`;
7-
const defaultHeaders = { 'content-type': 'application/json;charset=UTF-8', 'X-Source': 'Cloudflare-Workers', Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}` };
8-
const res = await fetch(defaultUrl, { method: 'POST', body: query, headers: defaultHeaders });
7+
const defaultHeaders = { "content-type": "application/json;charset=UTF-8", "X-Source": "Cloudflare-Workers", Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}` };
8+
const res = await fetch(defaultUrl, { method: "POST", body: query, headers: defaultHeaders });
99
const { data } = await res.json();
10-
if (type == 'list') return data.map((i) => i.blob1).reverse();
10+
if (type == "list") {
11+
// 校验白名单
12+
if (env.CLOUDFLARE_WEBSITE_WHITELIST) {
13+
const websiteArr = env.CLOUDFLARE_WEBSITE_WHITELIST.split("|");
14+
const websiteIDArr = websiteArr.map((i) => i.trim().split(",")[1]);
15+
return data
16+
.filter((i) => websiteIDArr.includes(i.blob1))
17+
.map((i) => i.blob1)
18+
.reverse();
19+
}
20+
return data.map((i) => i.blob1).reverse();
21+
}
1122
// 独立访客
1223
const visitor = data.filter((i) => i.double1 == 1).length;
1324
// 访问次数
1425
const visit = data.filter((i) => i.double2 == 1).length;
1526
// 总访问量
1627
const views = data.length;
1728
// 数据分析
18-
const resObj = { path: '', referrer: '', os: '', soft: '', area: '' };
29+
const resObj = { path: "", referrer: "", os: "", soft: "", area: "" };
1930
Object.keys(resObj).forEach((i, idx) => {
2031
resObj[i] = countData(data, `blob${idx + 3}`, {});
2132
});

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
},
1515
"dependencies": {
1616
"@radix-icons/vue": "^1.0.0",
17+
"@vueuse/core": "^11.1.0",
1718
"class-variance-authority": "^0.7.0",
1819
"clsx": "^2.1.1",
1920
"dayjs": "^1.11.13",
2021
"echarts": "^5.5.1",
22+
"lucide-vue-next": "^0.445.0",
2123
"radix-vue": "^1.9.6",
2224
"tailwind-merge": "^2.5.2",
2325
"tailwindcss-animate": "^1.0.7",
@@ -42,5 +44,6 @@
4244
"typescript": "~5.4.5",
4345
"vite": "^5.4.7",
4446
"vue-tsc": "^2.1.6"
45-
}
47+
},
48+
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247"
4649
}

0 commit comments

Comments
 (0)